diff --git a/.github/labeler.yml b/.github/labeler.yml
index 035a7cbb3b..4bbdeca759 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -1,3 +1,6 @@
+# Copyright (c) HashiCorp, Inc.
+# SPDX-License-Identifier: BUSL-1.1
+
# Add a Terraform label to changes under the related folders:
#
# Folder | Labels
@@ -92,4 +95,17 @@ WAF:
- changed-files:
- any-glob-to-any-file: [
'content/well-architected-framework/**'
+ ]
+
+# Add 'Sentinel' label to changes under 'content/sentinel'
+#
+# Label | Rule
+# --------------- | ------------------------------------------------------------
+# Sentinel | Default; applies to all doc updates
+
+Sentinel:
+- any:
+ - changed-files:
+ - any-glob-to-any-file: [
+ 'content/sentinel/**'
]
\ No newline at end of file
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/changelog/index.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/changelog/index.mdx
new file mode 100644
index 0000000000..3a3af4bea6
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/changelog/index.mdx
@@ -0,0 +1,314 @@
+---
+page_title: Sentinel Runtime Release Notes
+sidebar_title: Release Notes
+sidebar_current: docs-changelog
+description: >-
+ This are the public release notes for the Sentinel runtime. See this document
+ for the latest updates.
+layout: docs
+---
+
+# Sentinel Runtime Release Notes
+
+These are the release notes for the Sentinel runtime.
+
+Each version of the runtime is released with a corresponding version of
+[Sentinel CLI](/sentinel/commands). To download the CLI, see the [downloads
+page](/sentinel/downloads).
+
+Sentinel integrations and embedded runtimes may not always have the latest
+version installed, depending on the product's individual release cycle. For more
+information, contact the support team for your specific integration.
+
+
+
+## 0.12.1 (Unreleased)
+
+## 0.12.0 (October 7, 2019)
+
+LANGUAGE CHANGES:
+
+- **New `case` statement**. This statement is a selection control mechanism to
+ conditionally execute a branch based on expression equality, allowing
+ simplification of complex conditional chains that may otherwise need to be
+ written with `else if`. See the [Case
+ Statements](https://docs.hashicorp.com/sentinel/language/spec#case-statements)
+ section of the Sentinel language specification for more details.
+
+IMPROVEMENTS:
+
+- `lang/semantic`: Added a semantic check to ensure usage of append is not using
+ a return value.
+
+BUG FIXES:
+
+- `runtime/eval`: Corrected an issue where calling a method on an import object
+ value that was the result of a method call on another import object value
+ would have erroneously tried to call an import of the name of the "parent"
+ import object value. Example: in `a = subject.new(); b = a.call(); c = b.call()`, `b.call()` would attempt to call a method named `call` on the root
+ namespace for an import named `a`. This has now been corrected so that
+ `b.call()` will now correctly call the `call` method for the respective object
+ namespace residing in the import named `subject`.
+- `imports`: Some standard imports may have been returning null for some unknown
+ keys in objects when they should have been returning undefined. This was due
+ to an SDK issue which was corrected in SDK version 0.3.2, which has now been
+ corrected.
+- `cmd/test`: `sentinel test` will now correctly fail a policy if it encounters
+ an error.
+- `cmd/test`: `sentinel test` will now correctly display errors and other output
+ that were missing due to a formatting issue.
+- `runtime/eval`: The `append` builtin now correctly returns undefined for all
+ calls, as called for by the Specification. Note that in most cases, the
+ semantic check outlined above will trigger an error if the return value is
+ used.
+
+## 0.11.0 (September 5, 2019)
+
+LANGUAGE CHANGES:
+
+- **New builtin function:** `range()`. This function existed in the spec in
+ earlier versions but was removed as it lacked an implementation. This has now
+ been implemented and re-added to the spec. See the
+ [Range](https://docs.hashicorp.com/sentinel/language/spec#range) section of
+ the Sentinel Specification for more details.
+- Lists are now comparable. Lists are equal if their corresponding elements are
+ comparable and equal.
+- Method calls on values returned by imports are now supported. See the
+ [Imports](https://docs.hashicorp.com/sentinel/language/spec#imports) section
+ of the Sentinel Specification for more details.
+
+FEATURES:
+
+- `imports/decimal`: This is a new import designed to do exact precision
+ mathematical calculations.
+- `runtime/eval`: Compound call expressions that refer to imports (example:
+ foo.bar().baz() when `foo` is a loaded import) will now function as expected.
+ Previously, this was only supported up to the first call (example:
+ `foo.bar()`).
+- `imports/time`: Timespaces returned by calls such as `time.now` are now
+ callable. Example: `t = time.now; t.after(some_previous_time)` will now
+ function.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: The implementation of comparison of non-comparable types has
+ now changed. Rather than triggering a runtime error, non-comparable types will
+ now return false when attempting to compare.
+
+## 0.10.4 (August 15, 2019)
+
+BUG FIXES:
+
+- `runtime/encoding`: Fixed an issue with conversion of null values that could
+ lead to crashes in imports.
+
+## 0.10.3 (July 15, 2019)
+
+BUG FIXES:
+
+- `command/config`: Parsing a configuration file where malformed data is
+ encountered after apparently properly-formed data will now report an error. An
+ example would be a situation where misplaced braces would cause a JSON object
+ to only parse part of the configuration file.
+
+## 0.10.2 (June 25, 2019)
+
+BUG FIXES:
+
+- `lang/parser`: Corrected an issue where parsing certain compound binary
+ expressions where the negation predicate (`not`) was in use would cause the
+ negation to have no effect. Example: `foo else "bar" not in "baz"` would have
+ been parsed and evaluated as `foo else "bar" in "baz"`, effectively producing
+ the opposite result.
+
+## 0.10.1 (May 9, 2019)
+
+BUG FIXES:
+
+- `command/test`: `sentinel test` now displays policies without
+ tests as an unknown result with [no test files], instead of the somewhat
+ erroneous behavior of displaying it as a PASS. A full test run with a mixture
+ of passing tests and no tests still results in an overall successful result.
+
+## 0.10.0 (April 18, 2019)
+
+LANGUAGE CHANGES:
+
+- Mixed-number arithmetic operations are now allowed. Addition (`+`),
+ subtraction (`-`), multiplication (`*`), division (`/`), and remainder
+ operations (`%`) are no longer restricted to number values of the same
+ type, and can mix integer and floating point. The result of these operations
+ is always a floating-point number.
+- Remainder (modulo, `%`) operations are now allowed on floating-point numbers.
+
+BUG FIXES:
+
+- `lang/parser`: Fixed a bug that affected the use of `not` with `contains`,
+ `matches`, or `in` in a compound expression.
+- `runtime/eval`: Fixed error messages on evaluation errors with `contains` and
+ `in` to indicate that strings are an allowed type along with lists and maps.
+
+## 0.9.2 (March 15, 2019)
+
+This is a dependency update related to the changes mentioned in 0.9.1. No other
+changes have been made.
+
+## 0.9.1 (March 14, 2019)
+
+This is a patch release that is required to integrate with the latest versions
+of the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). No other
+changes have been made.
+
+## 0.9.0 (January 28, 2019)
+
+FEATURES:
+
+- `imports/strings`: Added a `join` function to the `strings` import. This can
+ be used to join a list into a string with a specific separator.
+ Multi-dimensional lists and all Sentinel primitives are supported.
+
+## 0.8.1 (January 17, 2019)
+
+BUG FIXES:
+
+- `lang/eval`: Fixed a bug that prevents the effective use of `return`
+ statements in `for` loops.
+
+## 0.8.0 (January 14, 2019)
+
+FEATURES:
+
+- Mocks can now be represented by Sentinel code. This allows for the mocking of
+ functions and other complex data structures that cannot be represented in
+ JSON. For more information on using this feature via mocks, click
+ [here](https://docs.hashicorp.com/sentinel/commands/config#mocking-with-sentinel-code).
+
+IMPROVEMENTS:
+
+- Import validation has now been moved to the semantic checking phase. This
+ should result in better reporting of validation errors. In addition, import
+ validation will now enforce the use of an `as` identifier when an import path
+ is not a valid identifier on its own (example: `import "foo/bar"`).
+
+## 0.7.0 (December 12, 2018)
+
+BUG FIXES:
+
+- There have been changes to the runtime in how scope is handled over multiple
+ policy executions. Scope is now correctly unique per single policy execution,
+ and values set or builtins that are overridden in one policy will no longer
+ affect those values within another.
+
+## 0.6.0 (November 30, 2018)
+
+FEATURES:
+
+- `imports/runtime`: This new import allows for one to check various aspects of
+ the Sentinel runtime as it may be embedded in the simulator or a specific
+ implementation. For now, it allows the version to be checked.
+
+## 0.5.1 (November 28, 2018)
+
+IMPROVEMENTS:
+
+- `imports/time`: Added the `zone` and `zone_string` attributes to assist with
+ validation of a timespace's zone.
+- `command/fmt`: Added a new -check flag. This option does not commit changes,
+ but instead checks to see what files need formatting and outputs them on
+ stdout.
+
+BUG FIXES:
+
+- `command/test`: Ensure that passing test results are correctly output one per
+ line. Tests are also now run in a deterministic fashion based on
+ lexicographical (alphabetical) order.
+- `imports/time`: `month_name` and `weekday_name` will now show up correctly in
+ a returned timespace result.
+
+## 0.5.0 (November 5, 2018)
+
+IMPROVEMENTS:
+
+- `spec`: Selectors can now contain any reserved word (example: `rule`) or
+ keyword operator (example: `any`, `all`, `is`, `not`). This only works for the
+ _selector_ part of the expression (after the first period) - the first primary
+ expression (before the first period) still needs to be an identifier that does
+ not conflict with reserved words.
+
+BUG FIXES:
+
+- The simulator should now display import function call names correctly in
+ import errors.
+
+## 0.4.0 (October 1, 2018)
+
+FEATURES:
+
+- `builtin`: Added the `bool` built-in type conversion function. Booleans will
+ also now accepted as conversion into other values as well, with the full list
+ of behaviors available in the spec.
+
+## 0.3.2 (September 27, 2018)
+
+FEATURES:
+
+- `command/apply`: `sentinel apply` now prints out messages output by the
+ `print()` function when a trace is output on policy failure, or when a trace
+ is forced with `-trace`.
+- `imports/time`: Added the `month_name` and `weekday_name` keys to the
+ timespace, which return full-English names for the month and day of the week.
+
+BUG FIXES:
+
+- `command/fmt`: `sentinel fmt -` Will no longer print out the filter status
+ message on the output stream when `-write=false` Is not explicitly stated.
+ This brings the behavior of the command in line with the help text.
+- `runtime`: Index operations on the right-hand-side that have negative indexes
+ that go out of range (example: `length(list) * -1 - 1`) now correctly return
+ `undefined`. left-hand-side index assignments with a out-of-range negative
+ index still return runtime errors.
+
+## 0.3.1 (August 3, 2018)
+
+BUG FIXES:
+
+- `runtime`: Basic index assignment has been implemented as per the spec.
+- `runtime`: Index expressions for lists with negative indexes will no longer panic if the
+ list index is less than `length(list) * -1`.
+
+## 0.3.0 (July 20, 2018)
+
+FEATURES:
+
+- **New standard import: `types`.** This can be used to dynamicaly detect the
+ type of some value.
+
+## 0.2.0 (April 11, 2018)
+
+FEATURES:
+
+- **New standard import: `json`.** Marshal and unmarshal JSON documents and
+ access their contents as native Sentinel values.
+- **`break` and `continue`.** These are now both specified and implemented.
+ `break` allows loop exiting and `continue` allows immediate execution of the
+ next iteration.
+
+IMPROVEMENTS:
+
+- runtime: `print()` map values are now ordered alphabetically by keys.
+
+BUG FIXES:
+
+- command/test: If no `test` block exists, test behaves like it is asserting
+ `main: true`.
+- runtime: default maximum stack depth to 500
+- runtime: `print()` map values now appear like more typical maps.
+- runtime: division by zero is an error, not a crash
+- runtime: plugins that send map values with `null` values now decode properly
+ into native Sentinel values.
+
+## 0.1.0 (September 19, 2017)
+
+Initial release.
+
+
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/commands/apply.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/commands/apply.mdx
new file mode 100644
index 0000000000..b715c5c77f
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/commands/apply.mdx
@@ -0,0 +1,47 @@
+---
+page_title: 'Command: apply'
+sidebar_title: 'apply'
+sidebar_current: docs-commands-apply
+description: >-
+ The `sentinel apply` command is used to execute a policy locally for
+ development purposes.
+layout: docs
+---
+
+# Command: `apply`
+
+The `sentinel apply` command is used to execute a policy locally for development
+purposes.
+
+## Usage
+
+Usage: `sentinel apply [options] POLICY`
+
+This command executes the policy file at the path specified by POLICY.
+
+Use the exit code of this command to determine the exact status of the policy
+evaluation. `0` is pass, `1` is fail, `2` is undefined (fail, but because the
+result was undefined), and `3` is a runtime error. Errors unrelated to the
+policy status itself are returned with an exit status of `9`.
+
+To control the behavior of the `apply` command, create a [configuration
+file](/sentinel/commands/config). With this, you can define available
+[import plugins](/sentinel/concepts/imports), mock data, and global values.
+This can help you simulate a policy embedded within an application.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-config=path` - Path to a configuration file specifying available imports,
+ mock data, globals, etc. The default is `sentinel.json`.
+
+- `-global key=value` - Set global values. This is the same as setting `global`
+ in the configuration file, and will override any of these respective values
+ set in the configuration. The value is either a string, or a JSON number,
+ array, or object. To force strings, use quotes.
+
+- `-param key=value` - Set parameters, the same as setting `param` in the
+ configuration file. Values are handled in the same way they are with the
+ `-global` flag.
+
+- `-trace` - Always show the execution trace. This shows intermediate
+ boolean expression values. This always shows for failed policies.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/commands/config.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/commands/config.mdx
new file mode 100644
index 0000000000..26c6fca28d
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/commands/config.mdx
@@ -0,0 +1,248 @@
+---
+page_title: Sentinel CLI Configuration File Syntax
+sidebar_title: Configuration File Syntax
+sidebar_current: docs-commands-config
+description: >-
+ The Sentinel CLI's configuration file can be used to control the
+ behavior of the simulator during apply and test operations.
+layout: docs
+---
+
+# Sentinel CLI Configuration File Syntax
+
+The Sentinel CLI's configuration file can be used to control the behavior
+of the simulator during [`apply`](/sentinel/commands/apply) and
+[`test`](/sentinel/commands/test) operations.
+
+## Usage
+
+The configuration file is used in different ways depending on what operation you
+are trying to execute.
+
+### Apply
+
+When using `sentinel apply`, supply the configuration file by using the
+`-config=FILE` flag, where `FILE` is the path to the configuration file. The
+default is `sentinel.json`.
+
+See the [apply command](/sentinel/commands/apply) reference for more
+details.
+
+### Test
+
+When using sentinel test, each file matching `test//*.json` is a
+configuration file representing a single test case, where `` is the name
+of the policy being tested. Configure assertions for each [test
+case](#test-cases) using the test section.
+
+See the [test command](/sentinel/commands/test) reference for more details.
+
+## Configuration File Reference
+
+The format of the configuration file is JSON. The available keys are:
+
+- `mock` - A map of mock import data.
+- `imports` - A map of available imports via [import plugins](/sentinel/extending).
+- `global` - Data that is inserted into the global scope.
+- `param` - Values for parameters defined in the policy.
+- `test` - Test cases for the [test command](/sentinel/command/test).
+
+### Mock Imports
+
+Mock imports allow running a policy with an
+[import](/sentinel/concept/import) that you may not have access to or is
+too difficult to run. For example, it can be easier to mock data for [Terraform
+Enterprise](https://www.terraform.io/docs/enterprise/sentinel/index.html)
+locally instead of having to run a workspace through a plan/policy check cycle.
+
+Mock imports are specified in the `mock` key, as a map to mock data, keyed by
+the import name that you want to mock. For example, if you wanted to mock the
+`time` import, you could create an entry with the `time` key pointing to the
+data you want to mock.
+
+The data can take one of two forms:
+
+#### Mocking static data
+
+Static data can be mocked directly by supplying a JSON object as the mock data.
+
+Example:
+
+```json
+{
+ "mock": {
+ "time": {
+ "now": {
+ "hour": 9,
+ "minute": 42
+ }
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```sentinel
+import "time"
+
+main = time.now.hour == 9
+```
+
+#### Mocking with Sentinel code
+
+There are some Sentinel types that static JSON data cannot mock, such as
+functions, and maps that don't have string keys. To mock this data, you can use
+a Sentinel file itself. In this case, the parameter to your import key is the
+file with the Sentinel code in it, relative to the current working directory in
+the event of `apply`, or relative to the configuration file location in the
+event of `test`.
+
+Note that `main` is not required in a mock data file.
+
+Example:
+
+```json
+{
+ "mock": {
+ "foo": "mock-foo.sentinel"
+ }
+}
+```
+
+`mock-foo.sentinel` would contain:
+
+```sentinel
+bar = func() {
+ return "baz"
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```sentinel
+import "foo"
+
+main = foo.bar() == "baz"
+```
+
+### Imports
+
+Imports allow you to run a real [import plugin](/sentinel/extending)
+with a policy. The `sentinel` binary will launch the plugin, connect to it,
+configure it, and execute it as needed by the policy.
+
+Note the difference between this and mock imports above is that this
+configuration specifies real import plugins to execute. The mock data
+fakes an import with static data.
+
+Imports are specified by the `import` key. The value of this is a map
+where the key is the name of the import and the value is another map with
+configuration about that import. The configuration map has the following keys:
+
+- `path` (string, required) - Path to the import plugin executable.
+- `args` (list of string, optional) - A list of arguments to pass to the
+ executable when starting it.
+- `env` (map of string to string, optional) - A set of environmental variables
+ to set when launching the plugin.
+- `config` (map, optional) - Configuration for the plugin itself. This is
+ specific to each individual plugin. Please reference the documentation for
+ the plugin for more information.
+
+Example:
+
+```json
+{
+ "imports": {
+ "time": {
+ "path": "/path/to/sentinel-import-time",
+ "config": { "fixed_time": 1504155600 }
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass assuming
+the import configuration is valid:
+
+```sentinel
+import "time"
+
+main = time.now.day == 31
+```
+
+### Global
+
+Global data is injected directly into the global scope of the policy.
+This can be used to simulate global data that may be injected by a
+Sentinel-enabled application.
+
+Global data is specified by the `global` key. The value is a map of
+variables to inject directly into the running policy.
+
+Example:
+
+```json
+{
+ "global": {
+ "time": {
+ "now": {
+ "day": 31
+ }
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass. Notice
+that we don't have to do any `import`. The values of `global` are
+injected directly into the global scope.
+
+```sentinel
+main = time.now.day == 31
+```
+
+### Parameters
+
+The `param` section allows you to supply values for the
+[parameters](/sentinel/language/parameters) found in a policy. Values
+entered here will satisfy required parameters in a policy, in addition to
+override any defaults. Note that any of the manual parameter value methods
+supported by `sentinel apply` will override the values set here.
+
+```json
+{
+ "param": {
+ "foo": "bar"
+ }
+}
+```
+
+### Test Cases
+
+Test cases specify the test cases for the [test command](/sentinel/commands/test).
+
+Tests are specified by the `test` key. The value of this is a map of
+string to boolean. The key is the name of a rule and the value is the
+expected value of that rule. If a rule is not specified, than any value is
+allowed.
+
+Example:
+
+```json
+{
+ "test": {
+ "main": true,
+ "valid_day": false
+ }
+}
+```
+
+For the policy:
+
+```sentinel
+valid_day = rule { 2 < 1 } # This is just an example!
+main = rule { not valid_day }
+```
+
+For more information, read about the [test command](/sentinel/commands/test).
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/commands/fmt.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/commands/fmt.mdx
new file mode 100644
index 0000000000..6e9660fb43
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/commands/fmt.mdx
@@ -0,0 +1,30 @@
+---
+page_title: 'Command: fmt'
+sidebar_title: 'fmt'
+sidebar_current: docs-commands-fmt
+description: The `sentinel fmt` command formats a policy source to a canonical format.
+layout: docs
+---
+
+# Command: `fmt`
+
+The `sentinel fmt` command formats a policy source to a canonical format.
+
+## Usage
+
+Usage: `sentinel fmt [options] FILE ...`
+
+This command formats all the specified policy files to a canonical format.
+
+By default, policy files are overwritten in place. This behavior can be
+changed with the `-write` flag. If a specified FILE is `-` then stdin is
+read and the output is always written to stdout.
+
+The command-line flags are all optional. The list of available flags are:
+
+* `-write=true` - Write formatted policy to the named source file. If false,
+ output will go to stdout. If multiple files are specified, the output will
+ be concatenated directly.
+* `-check=false` - Don't format, only check if formatting is necessary. Files
+ that require formatting are printed, and a non-zero exit code is returned if
+ changes are required.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/commands/index.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/commands/index.mdx
new file mode 100644
index 0000000000..7177093cec
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/commands/index.mdx
@@ -0,0 +1,23 @@
+---
+page_title: Commands (CLI)
+sidebar_current: docs-commands
+description: >-
+ Sentinel provides a `sentinel` command-line interface (CLI) for developing and
+ testing policies.
+layout: docs
+---
+
+# Sentinel CLI Commands
+
+The Sentinel command-line interface (CLI) allows for the developing and testing
+of policies outside of a particular Sentinel implementation. Having a standard
+workflow to develop policies is critical for our mission of [policy as
+code](/sentinel/concepts/policy-as-code).
+
+The CLI takes a subcommand to execute. The complete list of subcommands is in
+the navigation to the left.
+
+The Sentinel CLI is a well-behaved command line application. In erroneous cases,
+a non-zero exit status will be returned. It also responds to -h and --help as
+you'd expect. To view a list of the available commands at any time, just run
+`sentinel` with no arguments.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/commands/test.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/commands/test.mdx
new file mode 100644
index 0000000000..ec96d3a72e
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/commands/test.mdx
@@ -0,0 +1,40 @@
+---
+page_title: 'Command: test'
+sidebar_title: 'test'
+sidebar_current: docs-commands-test
+description: The `sentinel test` command runs policies against the Sentinel test framework.
+layout: docs
+---
+
+# Command: `test`
+
+The `sentinel test` command runs policies against the Sentinel test framework.
+
+To learn more about testing, see the [testing
+policies](/sentinel/writing/testing) page.
+
+## Usage
+
+Usage: `sentinel test [options] [PATH] ...`
+
+This command runs the defined test cases against a set of policies.
+
+The test cases are expected to live at the path `test//*.json` relative
+to the policy being tested. `` should be the name of the policy
+excluding the file extension. The JSON files should be in the [configuration
+file structure](/sentinel/commands/config). The `test` key is used for
+assertions.
+
+The PATH arguments specified may be a list of zero or more files or directories.
+When no arguments are given, it defaults to the current directory. When a
+directory is given, all policies ending with the extension `.sentinel` are
+tested.
+
+The command-line flags are all optional. The list of available flags are:
+
+* `-run=regexp` - Run only tests matching the regular expression pattern.
+ A `/` splits policy name and test case name.
+
+* `-verbose` - Show tracing and log output for tests that succeed in addition
+ to tests that fail. By default, traces and logs are only shown for tests that
+ fail.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/concepts/enforcement-levels.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/concepts/enforcement-levels.mdx
new file mode 100644
index 0000000000..7c7fbcf996
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/concepts/enforcement-levels.mdx
@@ -0,0 +1,45 @@
+---
+page_title: Enforcement Levels
+sidebar_title: Enforcement Levels
+sidebar_current: docs-concepts-levels
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Enforcement Levels
+
+Enforcement levels are a first class concept in Sentinel allowing pass/fail
+behavior to be associated separately from the policy logic. This enables
+any policy to be a warning, allow overrides, or be absolutely mandatory.
+Because this level is not part of the policy body itself, different uses of
+the same policy can have different enforcement levels.
+
+Sentinel has three enforcement levels:
+
+* **Advisory:** The policy is allowed to fail. However, a warning should be
+ shown to the user or logged.
+
+* **Soft Mandatory:** The policy must pass unless an override is specified.
+ The semantics of "override" are specific to each Sentinel-enabled application.
+ The purpose of this level is to provide a level of privilege separation
+ for a behavior. Additionally, the override provides non-repudiation since
+ at least the primary actor was explicitly overriding a failed policy.
+
+* **Hard Mandatory:** The policy must pass no matter what. The only way to
+ override a hard mandatory policy is to explicitly remove the policy.
+ Hard mandatory is the default enforcement level. It should be used in
+ situations where an override is not possible.
+
+## Configuring Enforcement Levels
+
+Enforcement levels are configured when a policy is deployed to a
+Sentinel-enabled application. The exact mechanism that the level is specified
+is determined by each application. Please reference the documentation for
+your Sentinel-enabled application for more information.
+
+Enforcement levels are not configured and are not known by the policy body
+itself. All policies should be written to describe exactly the behavior
+they're attempting to control. For example, a policy that restricts deploys
+to business hours should be written exactly like so. When that policy is
+configured on an application, the operator may specify that it is advisory,
+soft, or hard mandatory.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/concepts/imports.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/concepts/imports.mdx
new file mode 100644
index 0000000000..844a06ed59
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/concepts/imports.mdx
@@ -0,0 +1,34 @@
+---
+page_title: Imports
+sidebar_title: Imports
+sidebar_current: docs-concepts-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom import](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only
+local context for making policy decisions.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that the
+import calendar allows access to engineer calendars. This import only exists
+for the purpose of this example and at the time of writing is not a real
+available import.
+
+```sentinel
+import "calendar"
+
+// Get the calendar for Bob for today
+bob_calendar = calendar.for("bob").today
+
+// Allow this policy to pass if Bob is not on vacation.
+main = rule { not bob_calendar.has_event("vacation") }
+```
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/concepts/index.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/concepts/index.mdx
new file mode 100644
index 0000000000..ce7ac51454
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/concepts/index.mdx
@@ -0,0 +1,15 @@
+---
+page_title: Basic Concepts
+sidebar_current: docs-concepts
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Basic Concepts
+
+This section covers some high level basic concepts that are important
+to understand for day to day Sentinel usage. Every page in
+this section is recommended reading for anyone consuming
+or extending Sentinel.
+
+Please use the navigation to the left to learn more about a topic.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/concepts/language.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/concepts/language.mdx
new file mode 100644
index 0000000000..af287f1da6
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/concepts/language.mdx
@@ -0,0 +1,92 @@
+---
+page_title: Policy Language
+sidebar_title: Policy Language
+sidebar_current: docs-concepts-language
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Policy Language
+
+Sentinel defines and uses its own [policy language](/sentinel/language).
+
+The language was designed to be approachable by non-programmers, since
+there are many use cases where the individual defining policy may not
+be a developer. However, the language includes constructs that
+are familiar to developers to enable powerful policies.
+
+To learn more about the language, please see the
+[writing policy section](/sentinel/writing)
+or the [language reference](/sentinel/language).
+
+An example of the policy language is shown below:
+
+```sentinel
+import "time"
+
+# Validate time is between 8 AM and 4 PM
+valid_time = rule { time.now.hour >= 8 and time.now.hour < 16 }
+
+# Validate day is M - Th
+valid_day = rule {
+ time.now.weekday_name in ["Monday", "Tuesday", "Wednesday", "Thursday"]
+}
+
+main = rule { valid_time and valid_day }
+```
+
+## Why?
+
+To explain why Sentinel defines and uses its own language, the question
+can be more easily split into roughly two types of languages: _configuration_ languages
+and _programming_ languages.
+
+### Configuration Languages
+
+Configuration languages are formats such as JSON, YAML, XML, etc. Many applications
+use configuration languages as their ACL format. Configuration languages
+are good for static, declarative information. ACL systems are a good fit
+for this. ACL systems are focused, providing the limiting options necessary
+to restrict access to a system.
+
+Configuration languages are not good for dynamic or logical rules. They
+typically only contain limited conditions, loops, or functions. Further
+configuration is often declarative, which can introduce complexities to
+logical statements that depend on ordering.
+
+The use cases that Sentinel was built for require the ability to perform
+complex behavior that didn't model well into a configuration format or a
+declarative form.
+
+### Programming Languages
+
+The Sentinel language was designed with the following goals:
+
+* **Non-programmer friendly.** Sentinel was built to be used by non-programmers.
+ For the use cases we discovered, non-programmers needed the ability to
+ enforce certain rules within a system. For example, a person responsible for
+ compliance may need to insert rules into a system.
+
+* **Programmer friendly.** At the same time, the language needed to support
+ programmer-friendly constructs such as conditionals, loops, and functions
+ for complex policies that a programmer may be writing.
+
+* **Embeddable.** Sentinel is embedded in existing software. The language
+ itself needs to be easily embeddedable. Further, Sentinel was designed
+ to be embedded in [HashiCorp](https://www.hashicorp.com) software
+ written in Go, so it needed to be easily embeddable in Go.
+
+* **Safe.** The language is used in highly security-sensitive environments.
+ It must not be able to crash its host system or access system resources
+ without explicit approval.
+
+Prior to building our own language, Sentinel evaluated other languages.
+Some languages worked quite well. As we continued to develop Sentinel, we
+found that the flexibility and control over designing our own language
+outweighed the costs.
+
+Because the language itself doesn't need to be general purpose, building
+a language focused on policy proved to be the best route for Sentinel.
+It allows us to build powerful tracing capabilities for debugging, make
+interesting performance choices, and extend the language as needed in the
+future.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/concepts/policy-as-code.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/concepts/policy-as-code.mdx
new file mode 100644
index 0000000000..d9b51f04ad
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/concepts/policy-as-code.mdx
@@ -0,0 +1,73 @@
+---
+page_title: Policy as Code
+sidebar_title: Policy as Code
+sidebar_current: docs-concepts-pac
+description: >-
+ Policy as code is the idea of writing code in a high-level language to manage
+ and automate policies. By representing policies as code in text files, proven
+ software development best practices can be adopted such as version control,
+ automated testing, and automated deployment.
+layout: docs
+---
+
+# Policy as Code
+
+Policy as code is the idea of writing code in a high-level language to
+manage and automate policies. By representing policies as code in text files,
+proven software development best practices can be adopted such as version
+control, automated testing, and automated deployment.
+
+Many existing policy or ACL systems do not practice policy as code. Many
+policies are set by clicking in a GUI, which isn't easily repeatable nor
+versionable. They usually don't provide any system for testing policies
+other than testing an action that would violate the policy. This makes it
+difficult for automated testing. And the policy language itself varies by
+product.
+
+Sentinel is built around the idea and provides all the benefits of policy as code.
+
+## Benefits
+
+Policy as code provides a number of benefits:
+
+* **Sandboxing.** Policies provide the guardrails for other automated systems.
+ As the number of automated systems grow, there is also a growing need to
+ protect those automated systems from performing dangerous actions. Manual
+ verification is too slow; policies need to be represented as code to
+ keep up with other automated systems.
+
+* **Codification.** By representing policy logic as code, the information
+ and logic about a policy is directly represented in code and can be augmented
+ with comments rather than relying on oral tradition to learn about the
+ reason for policies.
+
+* **Version Control.** Policies are encouraged to be stored as simple text
+ files managed by a version control system. This lets you gain all the
+ benefits of a modern VCS such as history, diffs, pull requests, and more.
+
+* **Testing.** Policies are just code. Their syntax and behavior can be
+ [easily validated with Sentinel](/sentinel/commands/test). This also encourages
+ automated testing such as through a CI. Paired with a VCS system, this
+ allows a pull request workflow to verify that a policy keeps the system
+ behavior as expected before merging.
+
+* **Automation.** With all policies as code in simple text files, various
+ automation tools can be used. For example, it is trivial to create tools
+ to automatically deploy the policies into a system.
+
+## Sentinel and Policy as Code
+
+Sentinel fully embraces policy as code in a number of ways:
+
+* **Language.** All Sentinel policies are written using the
+ [Sentinel language](/sentinel/concepts/language). This language is
+ made to inputted directly to text files. As an additional benefit,
+ all Sentinel-enabled applications share the same policy language.
+
+* **Development.** Sentinel provides a [CLI](/sentinel/commands)
+ for development and testing. This local CLI can be used to verify policies
+ before deploying them to a system.
+
+* **Testing.** Sentinel provides a [test framework](/sentinel/commands/test)
+ designed specifically for automation. This allows developers and CI systems
+ to further verify policies.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/consul/index.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/consul/index.mdx
new file mode 100644
index 0000000000..16b3f502a6
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/consul/index.mdx
@@ -0,0 +1,49 @@
+---
+page_title: Consul and Sentinel
+sidebar_title: Consul
+sidebar_current: docs-app-consul
+description: >-
+ Consul Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies are applied during writes to
+ the KV Store.
+layout: docs
+---
+
+# Consul
+
+[Consul Enterprise](https://www.hashicorp.com/products/consul/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies are applied during writes to the KV Store.
+
+Sentinel policies have access to the key/value being written. They can be used to
+allow or deny the modification. The information that Sentinel policies have access
+to will expand over time.
+
+The Consul integration with Sentinel is documented in depth in the
+[Consul Enterprise documentation](https://www.consul.io/docs/guides/sentinel.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Input validation depending on the name of the key.**
+
+```sentinel
+
+main = rule { valid_key() }
+
+required = [
+ ["port", "\\d+"], # ports must be integers
+ ["name", "\\w+"], # name must be a word
+]
+
+valid_key = func() {
+ for required as v {
+ if key is v[0] {
+ return value matches v[1]
+ }
+ }
+
+ return false
+}
+```
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/extending/dev.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/extending/dev.mdx
new file mode 100644
index 0000000000..1146690d17
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/extending/dev.mdx
@@ -0,0 +1,456 @@
+---
+page_title: Import Plugins
+sidebar_title: Develop
+sidebar_current: docs-extending-dev
+description: >-
+ Import plugins are installed by configuring the Sentinel-enabled application
+ with the path to the plugin, the name of the import, and any arguments needed
+ to launch the plugin.
+layout: docs
+---
+
+# Developing an Import Plugin
+
+Anyone can develop a Sentinel Import plugin using the [Sentinel
+SDK](https://github.com/hashicorp/sentinel-sdk). The SDK contains a high-level
+framework for writing plugins in [Go](https://golang.org) including a test
+framework. Additionally, the SDK contains the low-level [gRPC protocol buffers
+definition](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/import.proto)
+for writing plugins in other languages.
+
+The primary reasons to write an import plugin are:
+
+* You want to expose or access new information within your Sentinel policies.
+ For example, you may want to query information from an external service.
+
+* You want to expose new functions to Sentinel policies.
+
+This page will document how to write a new Sentinel import in Go using the
+Sentinel SDK and the high-level framework provided. You are expected to already
+know the basics of Go and have a properly configured Go installation.
+
+Throughout this page, we'll be developing a simple import to access time values.
+
+-> **NOTE:** The example here is not reflective of the actual implementation of
+the [`time`](/sentinel/imports/time) standard import, so make sure to not
+get the two confused.
+
+## Import Framework
+
+The Sentinel SDK provides a high-level
+[framework](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework)
+for writing imports. We recommend using this framework.
+
+### Basic Concepts
+
+This framework works by implementing the correct Go interfaces.
+
+As a general overview:
+[`framework.Import`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Import)
+implements the
+[`sdk.Import`](https://godoc.org/github.com/hashicorp/sentinel-sdk#Import)
+interface and therefore is a valid import plugin. You must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface to configure the import. The root may then delegate nested access to
+various
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+implementations. Other interfaces are implemented to provide functionality past
+what is provided by the basic `framework.Namespace` implementation - these are
+documented below.
+
+This all may sound like a lot of interfaces, but each interface typically only
+requires a single function implementation and you're only required to implement
+a single namespace (`framework.Namespace`).
+
+As we move on, we'll use real examples to make this clear.
+
+### Implementing framework.Root
+
+To begin, you must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface. This is the interface representing the root of your import. The root
+itself must be implement
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace),
+which allows values to be accessed. Note that the root can optionally implement
+other interfaces, but they're more advanced and omitted here for simplicity.
+
+For our time example, our root may look like this:
+
+```sentinel
+type root struct {
+ time time.Time
+}
+
+// framework.Root impl.
+func (m *root) Configure(raw map[string]interface{}) error {
+ if _, ok := raw["timestamp"]; !ok {
+ raw["timestamp"] = time.Now().Unix()
+ }
+
+ v := raw["timestamp"]
+ timestamp, ok := v.(int)
+ if !ok {
+ return fmt.Errorf("invalid timestamp type %T", v)
+ }
+
+ m.time = time.Unix(timestamp, 0).UTC()
+ return nil
+}
+
+// framework.Namespace impl.
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "minute":
+ return m.time.Minute(), nil
+ }
+
+ return nil, nil
+}
+```
+
+This example implements `framework.Root` and `framework.Namespace` and would
+enable the following within a Sentinel policy once the completed import is
+installed.
+
+```sentinel
+import "time"
+
+print(time.minute)
+main = true
+```
+
+### framework.Namespace
+
+The
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+interface implements a namespace of values. You saw above that
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+must itself be a namespace. However, a namespace `Get` implementation may
+further return namespaces to access nested values.
+
+This enables behavior such as `time.month.string` vs. `time.month.index`.
+Notice each of these examples accesses a nested value within `month`. This can
+be modeled as namespaces within the framework.
+
+In the example below, we return a new namespace for `month` to do just this:
+
+```sentinel
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "month":
+ return &namespaceMonth{Month: m.time.Month()}, nil
+ }
+
+ // ...
+}
+
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil nil
+}
+```
+
+### Primitive Types and Structs
+
+A namespace may also return any primitive Go type as well as structs. The
+framework automatically exposes primitive values as you would expect. For
+structs, exported fields are lowercased and exposed to the policies. The
+`sentinel` struct tag can be used to control how Sentinel can access the field.
+
+In the example below, we expose a struct directly from the root namespace:
+
+```sentinel
+type Location struct {
+ Name string
+ TimeZone string `sentinel:"time_zone"`
+}
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "location":
+ return &Location{Name: "somewhere", TimeZone: "some zone"}, nil
+ }
+
+ // ...
+}
+```
+
+This makes the following values work within a Sentinel policy:
+`time.location.name`, `time.location.time_zone`.
+
+If a field has an empty `sentinel` struct tag (example: `sentinel:""`),
+then that field will not be accessible from a policy.
+
+### Optional Interfaces
+
+The following are optional interfaces that can be implemented on top of
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace).
+All of these interfaces can be implemented at any level, except for
+[`framework.New`](#framework-new), which only works at the root level.
+
+#### framework.Call
+
+The
+[`framework.Call`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Call)
+interface can be implemented to support function calls.
+
+Implementing this interface is very similar to attribute access, except instead
+of returning the attribute value, you return a function. The framework uses Go
+reflection to determine the argument and result types and calls it. If the
+argument types do not match or the signature is otherwise invalid, an error is
+returned.
+
+For example, let's implement a function to add months to the current month:
+
+```sentinel
+func (m *root) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *root) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.time.AddDate(0, n, 0).Month()}
+}
+```
+
+You can now call the function. If today was in the month of September,
+the policy below would pass:
+
+```sentinel
+import "time"
+
+main = time.add_month(4).string == "January"
+```
+
+#### framework.Map
+
+The optional
+[`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+interface allows an alternative method to to present a namespace back to a
+Sentinel policy as a map.
+
+`framework.Map` is best paired with
+[`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys),
+which allows you to quickly fetch the necessary data via `Get` calls on the
+namespace:
+
+```
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil, nil
+}
+
+func (m *namespaceMonth) Map() (map[string]interface{}, error) {
+ return framework.MapFromKeys(m, []string{"string", "index"})
+}
+```
+
+#### framework.New
+
+The optional
+[`framework.New`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#New)
+interface can be implemented on a root namespace to add _methods_ to your
+namespaces.
+
+Data returned from a namespace is memoized and returned as a map, and is
+normally not callable - to call a function to operate on the data, you would
+need to create a new namespace from the top-level of the import, and then make a
+function call on the result within the same expression.
+
+Let's consider the [root example](#implementing-framework-root). Let's say,
+instead of hard-coding the time value at configuration, we wanted to load it
+on-demand using a `time.now` key, and allow `add_month` to be callable on that
+value. Our root and de-coupled time namespace would now look like:
+
+```
+type root struct{}
+
+func (m *root) Configure(raw map[string]interface{}) error { return nil }
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "now":
+ return &namespaceTime{Time: time.Now()}
+ }
+
+ return nil
+}
+
+func (m *root) New(data map[string]interface{}) (framework.Namespace, error) {
+ if v, ok := data["unix"]; ok {
+ if t, ok := v.(int64); !ok {
+ return &namespaceTime{Time: time.Unix(t, 0)}
+ }
+
+ return nil, fmt.Errorf("expected timestamp to be int64, got %T", v)
+ }
+
+ return nil, nil
+}
+
+type namespaceTime struct {
+ Time time.Time
+}
+
+func (m *namespaceTime) Get(key string) interface{} {
+ switch key {
+ case "unix":
+ return m.Time.Unix()
+
+ // case ...
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) Get(key string) (interface{}, error) {
+ return framework.MapFromKeys(m, []string{"unix", "..."})
+}
+
+func (m *namespaceTime) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.Time.AddDate(0, n, 0).Month()}
+}
+```
+
+Via this example, we can now create and assign a `namespaceTime` using `t = time.now`, and then call `t.add_month(months_to_add)` to return a
+`namespaceMonth` for the corresponding month.
+
+Methods on a namespace can also _modfiy_ the receiver data. So you can, for
+example, add a method named `increment_month` that takes no arguments, but
+increments the time stored in `namespaceTime.Time` by a month. Subsequent
+statements using the receiver would see the new value.
+
+Note that there are some restrictions and guidelines that you should take into
+account when using `framework.New`:
+
+* Only data that makes it back to a policy can be used as receiver data to
+ instantiate new namespaces. As such it's recommended to make use of
+ [`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+ and
+ [`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys)
+ to return all of the attribute data you need to construct new objects.
+* `framework.New` is only supported on the root namespace and has no effect on
+ namespaces below the root. Check all possible cases of receiver data within
+ the top-level `New` function and return the appropriate namespace based on the
+ input data.
+* Do not return general errors from your `New` method. When an unknown lookup is
+ made off of memoized data, it will hit your `New` method for possible
+ instantiation and key calls. This will ensure `undefined` is correctly
+ returned for these cases.
+* `framework.New` is designed to add utility to imports where calling methods on
+ a value is more intuitive than just making a function call, or just returning
+ all of a data set as a memoized map. Use it sparingly and avoid using it on
+ recursively complex data sets.
+
+## Testing
+
+The SDK exposes a testing framework to verify your plugin works as
+expected. This test framework integrates directly into `go test` so it
+is part of a familiar workflow.
+
+The test framework works by dynamically building your import and
+running it against the `sentinel` CLI to verify it behaves as expected.
+This ensures that your plugin builds, your plugin communicates via RPC
+correctly, and that the behavior within a policy is also correct.
+
+The example below shows a complete ready table-driven test for our time plugin.
+You can run this with a normal `go test`.
+
+```sentinel
+package main
+
+import (
+ "os"
+ "testing"
+
+ "github.com/hashicorp/sentinel-sdk"
+ plugintesting "github.com/hashicorp/sentinel-sdk/testing"
+)
+
+func TestMain(m *testing.M) {
+ exitCode := m.Run()
+ plugintesting.Clean()
+ os.Exit(exitCode)
+}
+
+func TestImport(t *testing.T) {
+ cases := []struct {
+ Name string
+ Data map[string]interface{}
+ Source string
+ }{
+ {
+ "month",
+ map[string]interface{}{"timestamp": 1495483674},
+ `main = subject.month.string == "September"`,
+ },
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.Name, func(t *testing.T) {
+ plugintesting.TestImport(t, plugintesting.TestImportCase{
+ Config: tc.Data,
+ Source: tc.Source,
+ })
+ })
+ }
+}
+```
+
+## Building Your Import
+
+To build your import, you must first implement the `main` function.
+The main function should just use the `rpc.Serve` method to serve the
+plugin over the Sentinel RPC layer:
+
+```sentinel
+package main
+
+import (
+ "github.com/hashicorp/sentinel-sdk"
+ "github.com/hashicorp/sentinel-sdk/rpc"
+)
+
+func main() {
+ rpc.Serve(&rpc.ServeOpts{
+ ImportFunc: func() sdk.Import {
+ return &framework.Import{Root: &root{}}
+ },
+ })
+}
+```
+
+You can then build this using `go build`. The output can be named
+anything. Once your plugin is built, [install it](/sentinel/extending/install)
+and try it out!
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/extending/index.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/extending/index.mdx
new file mode 100644
index 0000000000..8b99ad5e63
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/extending/index.mdx
@@ -0,0 +1,14 @@
+---
+page_title: Import Plugins
+sidebar_current: docs-extending
+description: Imports allow a Sentinel policy to access external data and add new functions.
+layout: docs
+---
+
+# Import Plugins
+
+Sentinel is built on a plugin-based architecture. [Imports](/sentinel/language/imports)
+can be implemented as plugins. Users of Sentinel are able to write new
+imports in order to access external data and add new functions.
+This section of the documentation explains how to use plugins, how to
+write plugins, how Sentinel internals work with plugins, and more.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/extending/install.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/extending/install.mdx
new file mode 100644
index 0000000000..bc36d0585a
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/extending/install.mdx
@@ -0,0 +1,65 @@
+---
+page_title: Import Plugins
+sidebar_title: Install
+sidebar_current: docs-extending-install
+description: >-
+ Import plugins are installed by configuring the Sentinel-enabled application
+ with the path to the plugin, the name of the import, and any arguments needed
+ to launch the plugin.
+layout: docs
+---
+
+# Installing Import Plugins
+
+Import plugins are installed by configuring the Sentinel-enabled application
+with the path to the plugin, the name of the import, and any arguments needed
+to launch the plugin.
+
+-> **Not all Sentinel-enabled applications support plugins.** Please refer
+to the documentation of the specific Sentinel-enabled application. Some
+applications disable plugins for security reasons. For those that do
+enable plugins, the method to configure plugins may vary.
+
+## Installing Plugins
+
+Sentinel plugins are standalone executables. The Sentinel-enabled application
+launches these plugins and communicates with them via
+[RPC](https://en.wikipedia.org/wiki/Remote_procedure_call). To install
+a plugin, the first step is to download the plugin executable for the
+platform that the Sentinel-enabled application is running on.
+
+After downloading the plugin, you must configure the Sentinel-enabled
+application. This may be via a configuration file, an API call, or some
+other method. Please refer to the application-specific documentation for
+more details.
+
+Sentinel imports are configured with the following settings:
+
+* `name` (required) - The name of the import within Sentinel policies.
+ For example, `import "foo"`, where "foo" is the import name.
+ This is often specified as the key to the configuration.
+
+* `path` (required) - This is the path to the plugin executable.
+
+* `args` (optional) - This is a list of arguments to pass to the executable when
+ launched. By default, no arguments are given.
+
+* `env` (optional) - This is a set of environment variables to expose to
+ the plugin. By default, the same environment variables given to the
+ application are inherited by the plugin. This key can be used to add
+ additional variables.
+
+* `config` (optional) - This is a map of key/value pairs for configuring
+ the plugin. Please refer to the plugin documentation for available
+ configuration options. Some plugins may not require any configuration.
+
+After configuring the plugin, you may have to restart the application
+for it to become available.
+
+## Debugging Plugins
+
+Sentinel logs when it launches, configures, closes a plugin and more.
+
+To debug issues with a plugin, please refer to the logs of the
+Sentinel-enabled application. You may have to configure the application
+to show Sentinel logs. Please refer to the application-specific documentation.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/functions/append.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/functions/append.mdx
new file mode 100644
index 0000000000..1b3e54b8e5
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/functions/append.mdx
@@ -0,0 +1,47 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Append'
+sidebar_title: 'append'
+sidebar_current: docs-funcs-append
+description: The built-in function `append` appends a value to the end of a list.
+layout: docs
+---
+
+# Builtin Function: append
+
+The built-in function `append` appends a value to the end of a list. The list
+is modified in-place. The return value will always be [undefined](/sentinel/language/undefined).
+
+`append` called on any value other than a list or undefined will result
+in an immediate error.
+
+Examples:
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+### Why does this function return undefined?
+
+This function returns `undefined` to avoid unintended side effects of the function in the context
+of a [rule](/sentinel/language/rules).
+
+For example, if `append` returned the list value as a result, the following policy would depend
+on the order in which rules are referenced:
+
+```sentinel
+list = []
+a = rule { append(list, 1) contains 1 }
+b = rule { append(list, 2) contains 2 }
+
+// Scenario 1: list becomes [1, 2]
+main = rule { a and b }
+
+// Scenario 2: list becomes [2, 1]
+main = rule { b and a }
+```
+
+This sort of side effect behavior introduces complex and difficult to track logical errors in programs.
+As a result, functions like this return `undefined` and modify values in-place.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/functions/delete.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/functions/delete.mdx
new file mode 100644
index 0000000000..0a6340876d
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/functions/delete.mdx
@@ -0,0 +1,26 @@
+---
+page_title: 'Sentinel Language - Builtin Function: delete'
+sidebar_title: 'delete'
+sidebar_current: docs-funcs-delete
+description: The built-in function `delete` deletes an element from a map.
+layout: docs
+---
+
+# Builtin Function: delete
+
+The built-in function `delete` deletes an element from a map.
+
+If the element to delete does not exist, the map is left unchanged.
+Calling `delete` on a value that is not a map or
+[undefined](/sentinel/language/undefined)
+is an immediate error. The result of `delete` is always `undefined`.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/functions/error.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/functions/error.mdx
new file mode 100644
index 0000000000..1d06dfc432
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/functions/error.mdx
@@ -0,0 +1,16 @@
+---
+page_title: 'Sentinel Language - Builtin Function: error'
+sidebar_title: 'error'
+sidebar_current: docs-funcs-error
+description: >-
+ The built-in function `error` is used to exit immediately with an error
+ message.
+layout: docs
+---
+
+# Builtin Function: error
+
+The built-in function `error` is used to exit immediately with an error message.
+
+Please see the [page on errors](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/functions/index.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/functions/index.mdx
new file mode 100644
index 0000000000..0d900b33d0
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/functions/index.mdx
@@ -0,0 +1,14 @@
+---
+page_title: Sentinel Language - Builtin Functions
+sidebar_title: Builtin Functions
+sidebar_current: docs-funcs
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Language: Builtin Functions
+
+Builtin functions are
+[functions](/sentinel/language/functions)
+that are available in all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in function.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/functions/keys.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/functions/keys.mdx
new file mode 100644
index 0000000000..36d99bc2b7
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/functions/keys.mdx
@@ -0,0 +1,23 @@
+---
+page_title: 'Sentinel Language - Builtin Function: keys'
+sidebar_title: 'keys'
+sidebar_current: docs-funcs-keys
+description: The built-in function `keys` returns the keys of a map.
+layout: docs
+---
+
+# Builtin Function: keys
+
+The built-in function `keys` returns the keys of a map.
+
+`keys` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned keys are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+```
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/functions/length.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/functions/length.mdx
new file mode 100644
index 0000000000..efe184c0f1
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/functions/length.mdx
@@ -0,0 +1,24 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Length'
+sidebar_title: 'length'
+sidebar_current: docs-funcs-length
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Builtin Function: length
+
+The built-in function `length` returns the length of a collection or string.
+
+The length of a string is the number of bytes in the string. It is not
+necessarilly the number of characters. The length of a collection is the
+number of elements in that collection. The length of
+[undefined](/sentinel/language/undefined) is `undefined`.
+
+Examples:
+
+```sentinel
+length("foo") // 3
+length([1, 2, 3, 4]) // 4
+length({ "key": "value" }) // 1
+```
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/functions/print.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/functions/print.mdx
new file mode 100644
index 0000000000..9d714195db
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/functions/print.mdx
@@ -0,0 +1,14 @@
+---
+page_title: 'Sentinel Language - Builtin Function: print'
+sidebar_title: 'print'
+sidebar_current: docs-funcs-print
+description: The built-in function `print` is used for logging and debugging.
+layout: docs
+---
+
+# Builtin Function: print
+
+The built-in function `print` is used for logging and debugging.
+
+Please see the [page on logging](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/functions/range.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/functions/range.mdx
new file mode 100644
index 0000000000..0a9a07aa6f
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/functions/range.mdx
@@ -0,0 +1,50 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Range'
+sidebar_title: 'range'
+sidebar_current: docs-funcs-range
+description: The built-in function `range` returns a list of numbers in a range.
+layout: docs
+---
+
+# Builtin Function: range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it
+defaults to `1`.
+
+Negative steps are permitted and count down from `start` towards `end`, instead
+of up.
+
+The range ends when the next value given by the sum of the last value and `step`
+would exceed `end`, regardless of if it has been reached.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4] - starts at 1 instead of 0
+range(1, 5, 2) // [1,3] - 3+2 does not satisfy r[-1] < end
+range(0, -3, -1) // [0,-1,-2] - example of negative range
+```
+
+Impossible ranges, or ranges where `start` will never reach `end` given `step`,
+yield an empty list.
+
+```
+range(1, 1) // [] - already starting at 1
+range(0, 1, -1) // [] - negative step will never reach 1
+```
+
+Supplying an undefined value to any argument of `range` yields an undefined
+value back.
+
+A range with a zero step is a runtime error.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/functions/values.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/functions/values.mdx
new file mode 100644
index 0000000000..95b448f6c7
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/functions/values.mdx
@@ -0,0 +1,23 @@
+---
+page_title: 'Sentinel Language - Builtin Function: values'
+sidebar_title: 'values'
+sidebar_current: docs-funcs-values
+description: The built-in function `values` returns the values of a map.
+layout: docs
+---
+
+# Builtin Function: values
+
+The built-in function `values` returns the values of a map.
+
+`values` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned values are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+values(data) // [3, 2]
+```
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/guides/index.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/guides/index.mdx
new file mode 100644
index 0000000000..8577828033
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/guides/index.mdx
@@ -0,0 +1,12 @@
+---
+page_title: Documentation
+sidebar_current: guides-index
+description: TODO Guides
+layout: docs
+---
+
+# Sentinel Guides
+
+Welcome to the Sentinel Guides!
+
+TODO
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/imports/decimal.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/imports/decimal.mdx
new file mode 100644
index 0000000000..e1455940aa
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/imports/decimal.mdx
@@ -0,0 +1,212 @@
+---
+page_title: 'Import: decimal'
+sidebar_title: 'decimal'
+sidebar_current: docs-imports-decimal
+description: |-
+ The decimal import provides functions for operating on numbers represented
+ as decimals.
+layout: docs
+---
+
+# Import: decimal
+
+The decimal import provides functions for operating on numbers represented
+as decimals.
+
+Binary floating-point numbers provided by the `float` type are unable to
+fully represent numbers like `1.1` and `2.2`. The decimal import can
+represent these numbers exactly, and so should be used when exactness is
+required.
+
+Decimals are created through the `new` function, which takes any type that
+can represent a number. Arguments to the decimal operators can also take
+a value of any of these types. The full list is:
+
+* float
+* int
+* string
+* existing decimal value
+
+Decimals are represented internally by a sign, coefficient, and exponent.
+The value is calculated with the formula `sign * coefficient * 10^exponent`.
+Exponent is limited to 32 bits, and the coefficient is limited by the total
+precision.
+
+The maximum precision a decimal can represent is 100 digits. This includes
+both before and after the decimal place.
+
+### decimal.infinity(sign)
+
+Creates an infinite-value decimal. Infinite-value decimals are always larger
+or smaller, depending on sign, than any non-infinite decimals.
+
+```sentinel
+decimal.infinity(1) // +Infinity
+decimal.infinity(-1) // -Infinity
+```
+
+Also available as `decimal.inf`.
+
+### decimal.nan
+
+A decimal representing a "not-a-number" value. NaNs are not equal to any
+other number, even themselves.
+
+```sentinel
+decimal.new(-1).sqrt() // NaN
+decimal.new(0).mul(decimal.inf(1)) // NaN
+decimal.nan.is(decimal.nan) // false
+```
+
+### decimal.isinfinite(d)
+
+Evaluates to true if the decimal is infinite.
+
+```sentinel
+decimal.isinfinite(100) // false
+decimal.isinfinite("Infinity") // true
+```
+
+Also available as `decimal.isinf`.
+
+### decimal.isnan(d)
+
+Evaluates to true if the decimal is not-a-number.
+
+```sentinel
+decimal.isnan("NaN") // true
+```
+
+### decimal.new(v)
+
+Constructs a decimal from another value.
+
+```sentinel
+decimal.new("1.1") // Constructs a decimal from a string.
+decimal.new(2.2) // Constructs a decimal from a float.
+decimal.new(3) // Constructs a decimal from an integer.
+decimal.new("1.1234E+400") // Constructs a decimal from a string using E-notation.
+```
+
+## Type: number
+
+### number.string
+
+A string representation of the decimal.
+
+### number.sign
+
+A number between `-1` and `+1` representing the sign of the decimal.
+
+### number.coefficient
+
+The coefficient component of the decimal.
+
+### number.exponent
+
+The exponent component of the decimal.
+
+### number.float
+
+A floating-point representation of the decimal.
+
+### number.int
+
+An integer representation of the decimal. This always truncates the decimal
+to the nearest integer less than or equal to itself.
+
+### number.is(v)
+
+Test for equality with another value.
+
+### number.is_not(v)
+
+Test for inequality with another value.
+
+### number.less_than(v)
+
+Test that the decimal is less than another value.
+Can also be called as `number.lt(v)`
+
+### number.less_than_or_equals(v)
+
+Test that the decimal is less than or equals to another value.
+Can also be called as `number.lte(v)`
+
+### number.greater_than(v)
+
+Test that the decimal is greater than another value.
+Can also be called as `number.gt(v)`
+
+### number.greater_than_or_equals(v)
+
+Test that the decimal is greater than or equals to another value.
+Can also be called as `number.gte(v)`
+
+### number.add(v)
+
+Add another number to the decimal.
+
+### number.subtract(v)
+
+Subtract another number from the decimal.
+Can also be called as `number.sub(v)`
+
+### number.multiply(v)
+
+Multiply the decimal by a number.
+Can also be called as `number.mul(v)`
+
+### number.divide(v)
+
+Divide the decimal by a number.
+Can also be called as `number.div(v)`
+
+### number.modulo(v)
+
+Find the remainder after dividing the decimal by a number.
+Can also be called as `mod`, `remainder`, or `rem`.
+
+### number.power(v)
+
+Raise the decimal to a power.
+Can also be called as `number.pow(v)`
+
+### number.exp()
+
+The natural exponent of the decimal. This calculates `e^number`.
+
+### number.loge()
+
+The natural logarithm of the decimal.
+Can also be called as `number.ln()`
+
+### number.log10()
+
+The base-10 logarithm of the decimal.
+Can also be called as `number.log()`
+
+### number.square_root()
+
+The square root of the decimal.
+Can also be called as `number.sqrt()`
+
+### number.ceiling()
+
+Round the decimal to the smallest integer greater or equal to itself.
+Can also be called as `number.ceil()`
+
+### number.floor()
+
+Round the decimal to the largest integer less than or equal to itself.
+
+### number.absolute()
+
+The absolute value of the decimal.
+Can also be called as `number.abs()`
+
+### number.negate()
+
+The negated value of the decimal. A positive decimal will become negative,
+and a negative decimal will become positive.
+Can also be called as `number.neg()`
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/imports/http.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/imports/http.mdx
new file mode 100644
index 0000000000..c00c62eeeb
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/imports/http.mdx
@@ -0,0 +1,305 @@
+---
+page_title: 'Import: http'
+sidebar_title: 'http'
+sidebar_current: docs-imports-http
+description: The http import enables the use of HTTP-accessible data from outside the runtime in Sentinel policy rules.
+layout: docs
+---
+
+# Import: http
+
+The http import enables the use of HTTP-accessible data from outside
+the runtime in Sentinel policy rules.
+
+At the center of the import is the [`get()`](#clientgeturl_or_request) function, which issues a GET
+request and returns a `response` type:
+
+```sentinel
+import "http"
+
+resp = http.get("https://example.hashicorp.com")
+main = rule { resp.body contains "something" }
+```
+
+By default, any request response that is not a successful "200 OK" HTTP
+response causes a policy error. See
+[`accept_status_codes`](#clientaccept_status_codeslist) for more information and
+how to customize this behavior.
+
+For more advanced requests which require setting request headers, use a
+[`request` type](#type-request):
+
+```sentinel
+import "http"
+import "json"
+
+param token
+
+req = http.request("https://example.hashicorp.com").with_header("Authorization", "token "+token)
+resp = json.unmarshal(http.get(req).body)
+main = rule { resp["some_key"] is true }
+```
+
+The `with_header` function above returns the `request` object with the
+`Authorization` HTTP header set to value of the variable `some_value`. Many
+of the methods within the http import API are designed around this concept
+of method chaining, allowing for complex building of HTTP requests
+encapsulated in functions. The following is an idiomatic example further
+demonstrating this pattern:
+
+```sentinel
+import "http"
+import "json"
+
+param github_user
+param github_token
+
+client = func(req) {
+ return http.with_timeout(5).with_retries(3)
+}
+
+github_request = func(path) {
+ full_url = "https://api.github.com" + path
+ return http.request(full_url).
+ with_basic_auth(github_user, github_token).
+ with_header("Accept", "application/vnd.github.v3+json").
+ with_header("Custom", "foo"),
+}
+
+default_response = client.get(github_request("/example"))
+
+# Override the Custom header for this single request
+custom_header_response = json.unmarshal(
+ client.get(
+ github_request("/example").with_header("Custom", "bar"),
+ ),
+)
+
+# Override the timeout value for a known slower request
+slow_response = json.unmarshal(
+ client.with_timeout(15).get(github_request("/slow-endpoint")),
+)
+
+some_key_is_true = rule { json.unmarshal(default_response.body)["some_key"] is true }
+body_contains_text = rule { custom_header_response.body contains "something" }
+response_is_ok = rule { slow_response.status_code is 200 }
+
+main = rule { some_key_is_true and body_contains_text and response_is_ok }
+```
+
+### http.client
+
+Retrieve the base HTTP client with default settings. The returned client may be
+configured by calling configuration functions on it; all of these configuration
+functions on are also aliased as top-level functions in this namespace. See
+[`client`](#type-client) below.
+
+### http.request(url)
+
+Create a new [`request`](#type-request) to the specified `url` (string). A
+`request` type enables customization of headers and other concerns before then
+providing the constructed `request` to [`get()`](#clientgeturl_or_request).
+
+```sentinel
+req = http.request("example.hashicorp.com/foo").with_header("Foo", "bar")
+resp = http.get(req)
+```
+
+## Type: client
+
+All of the following configuration functions on the default client are also
+aliased as top-level functions in the import. This allows a short-hand (and
+idiomatic) way of configuring a new client without having to directly access
+`http.client` each time:
+
+```sentinel
+# The following two lines are semantically the same:
+resp = http.client.with_timeout(5).get(url)
+resp = http.with_timeout(5).get(url)
+```
+
+### client.get(url_or_request)
+
+Issue a GET request with a URL string or a `request` type. Returns a `response`
+type.
+
+When `url_or_request` is a string, a GET request is made with the URL
+specified by the string. To customize HTTP headers and other settings, pass
+a request. By default, a request has a timeout value of 10 seconds and 1
+retry. These settings can be adjusted with [`with_timeout()`](#clientwith_timeoutinteger) and
+[`with_retries()`](#clientwith_retriesinteger), respectively.
+
+If the response is one of the following redirect codes, `get()` follows the
+redirect, up to a maximum of 10 redirects:
+
+301 (Moved Permanently)
+302 (Found)
+303 (See Other)
+307 (Temporary Redirect)
+308 (Permanent Redirect)
+
+If the redirect limit is exceeded, the result is a policy error.
+
+By default, after any redirects are followed (up to the maximum), any request
+response that is not a successful "200 OK" response causes a policy error. See
+[`accept_status_codes`](#clientaccept_status_codeslist) for more information
+and how to customize this behavior.
+
+### client.accept_status_codes(list)
+
+Configures a client to not automatically cause a policy failure if
+the resulting response's status code is in `list`. Any status code received
+that is not present in this list will trigger a runtime error.
+
+By default, any request response that is not a successful "200 OK" response
+causes a policy error. This is for convenience, as the most common scenario
+by far is to receive a response and immediately signal a policy error if the
+status code is not 200. Use this scoping to specify a list of non-redirect
+HTTP codes that you wish to accept without error and evaluate the response
+yourself.
+
+Redirects are not considered final status codes in this context. That is,
+redirects are always followed up to the maximum allowed value (see [`get()`](#clientgeturl_or_request))
+and the final response code in the redirect chain is validated against
+`list`.
+
+```sentinel
+resp = http.accept_status_codes([200, 404]).get(url)
+main = rule { resp.status_code is 404 }
+```
+
+### client.accepted_status_codes
+
+Returns a list of the client's accepted status codes.
+
+```sentinel
+client = http.accept_status_codes([200, 404])
+main = rule { client.accepted_status_codes contains 404 }
+```
+
+### client.with_retries(integer)
+
+Sets the maximum number of retries, specified by `integer`, that should be
+attempted on an unsuccessful request. An unsuccessful request is considered
+to be any 5xx response except `501 (Not Implemented)` or a request timeout.
+By default, one retry is attempted.
+
+The maximum number of retries is 10, for a total of 11 request attempts.
+When a request exceeds the maximum number of retries without a response, the
+result is a policy error. Specifying a number larger than this will result
+in a runtime error.
+
+Between each retry, an exponentially increasing delay is observed, allowing
+time for the server to recover. This delay starts at 1 second for the first
+retry and increases each attempt thereafter. The maximum delay for a single
+attempt is 30 seconds.
+
+Retries can be disabled by specifying 0.
+
+### client.retries
+
+Returns the client's configured number of retries as an integer.
+
+### client.with_timeout(integer)
+
+Sets the request timeout to the number of seconds indicated by `integer`.
+
+Each individual request performed by the HTTP client will be limited by the
+given request timeout. This timeout includes connection time, any redirects,
+and reading the response body.
+
+A maximum of 30 seconds is allowed. Specifying a number larger than this
+will result in a runtime error.
+
+By default, requests will time out after 10 seconds.
+
+### client.timeout
+
+Returns the client's configured timeout in whole seconds as an integer.
+
+### client.without_certificate_verification()
+
+Skips verification of TLS certificates during secure HTTP operations,
+allowing for requests to `https://` endpoints which are secured with a TLS
+certificate signed by an untrusted certificate authority. Takes no arguments.
+
+By default, the HTTP client will trigger a runtime error if a TLS certificate
+presented by a server is from an untrusted certificate authority.
+
+Do not change this setting unless you are aware of the risks involved.
+Without verification, TLS accepts any certificate presented by the server
+and any host name in that certificate. In this mode, TLS is susceptible to
+man-in-the-middle attacks. This option should only be used for testing or in
+trusted networks with self-signed certificates.
+
+```sentinel
+http.without_certificate_verification().get(url)
+```
+
+### client.certificate_verification
+
+Returns true if the client requires TLS certificates to be verifiable.
+
+## Type: request
+
+### request.url
+
+Returns the request URL as a string.
+
+### request.headers
+
+Returns the configured request headers as a string-keyed map of strings.
+
+### request.with_header(key, value)
+
+Returns a `request` with the header `key` (string) set to `value` (string).
+
+Values are overridden on subsequent calls to the same `key`. To specify
+multiple values, use the existing value when setting the new one.
+
+```sentinel
+original = http.request("http://example.hashicorp.com").with_header("Foo", "bar")
+overridden = original.with_header("Foo", "baz")
+multi_value = original.with_header("Foo", original.headers["Foo"] + ",baz")
+
+main = rule {
+ original.headers["Foo"] is "bar" and
+ overridden.headers["Foo"] is "baz" and
+ multi_value.headers["Foo"] is "bar,baz"
+}
+```
+
+### request.with_basic_auth(username, password)
+
+Returns a `request` with the `Authorization` header set to use HTTP Basic
+Authentication with the provided username and password.
+
+Note that with HTTP Basic Authentication the provided username and password
+are encoded, but not encrypted.
+
+## Type: response
+
+### response.status_code
+
+The response status code as an integer, e.g. `200`.
+
+### response.headers
+
+The response headers as a map.
+
+### response.body
+
+The response body as a string. Callers should unmarshal the content to a useful
+representation as necessary based on the content type. For example, if the
+response is in JSON:
+
+```sentinel
+import "http"
+import "json"
+
+# This example endpoint returns {"some_key": true}
+req = http.request("https://example.hashicorp.com/some-json")
+
+resp = json.unmarshal(http.get(req).body)
+main = rule { keys(resp) contains "some_key" and resp["some_key"] is true }
+```
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/imports/index.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/imports/index.mdx
new file mode 100644
index 0000000000..e7d16b2ccb
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/imports/index.mdx
@@ -0,0 +1,19 @@
+---
+page_title: Sentinel Language - Standard Imports
+sidebar_title: Standard Imports
+sidebar_current: docs-imports
+description: >-
+ Standard Imports are the built-in imports that are available to all Sentinel
+ policies.
+layout: docs
+---
+
+# Standard Imports
+
+Standard Imports are the built-in [imports](/sentinel/language/imports)
+that are available to all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in import.
+
+Sentinel-embedded applications can choose to whitelist or blacklist certain
+standard imports. Please reference the documentation for the Sentinel-enabled
+application you're using to determine if all standard imports are available.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/imports/json.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/imports/json.mdx
new file mode 100644
index 0000000000..45a75d30d5
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/imports/json.mdx
@@ -0,0 +1,32 @@
+---
+page_title: 'Import: json'
+sidebar_title: 'json'
+sidebar_current: docs-imports-json
+description: The json import enables a Sentinel policy to parse and access a JSON document.
+layout: docs
+---
+
+# Import: json
+
+The json import enables a Sentinel policy to parse and access a JSON
+document.
+
+### json.unmarshal(obj)
+
+Unmarshals the JSON object `obj` into a native Sentinel structure.
+
+All native JSON types can be represented perfectly as Sentinel native types.
+
+```sentinel
+// Typically the input for this would come from an external source.
+config = json.unmarshal("{ \"foo\": 42 }")
+config.foo // 42
+config.bar // undefined (as usual for accessing a non-existent map key)
+```
+
+### json.marshal(obj)
+
+Marshals the Sentinel object `obj` into a JSON object encoded as a string.
+
+Functions and `undefined` cannot be natively encoded as JSON. If values of
+either type are found in the structure, this will return undefined.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/imports/runtime.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/imports/runtime.mdx
new file mode 100644
index 0000000000..5bfa156825
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/imports/runtime.mdx
@@ -0,0 +1,17 @@
+---
+page_title: 'Import: runtime'
+sidebar_title: 'runtime'
+sidebar_current: docs-imports-runtime
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: runtime
+
+The runtime import contains various information about the Sentinel runtime. It
+can be used to get information about the runtime as it's embedded in the CLI or
+a specific integration, such as validating a specific runtime version.
+
+### runtime.version
+
+Returns the version of Sentinel within this implementation.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/imports/sockaddr.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/imports/sockaddr.mdx
new file mode 100644
index 0000000000..c200e69796
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/imports/sockaddr.mdx
@@ -0,0 +1,42 @@
+---
+page_title: 'Import: sockaddr'
+sidebar_title: 'sockaddr'
+sidebar_current: docs-imports-sockaddr
+description: The sockaddr import makes working with IP addresses easy.
+layout: docs
+---
+
+# Import: sockaddr
+
+The sockaddr import makes working with IP addresses easy.
+
+### sockaddr.is_contained(outer, inner)
+
+The is_contained function returns true if `inner` is an address that
+is contained within `outer`.
+
+```sentinel
+sockaddr.is_contained("192.168.0.0/24", "192.168.0.32") // true
+sockaddr.is_contained("192.168.0.0/24", "192.174.1.1") // false
+```
+
+### sockaddr.is_equal(addr1, addr2)
+
+The is_equal function returns true if addr1 is equal to addr2.
+
+```sentinel
+sockaddr.is_equal("192.168.12.24", "192.168.12.24") // true
+```
+
+### sockaddr.is_ipv4(addr)
+
+The is_ipv4 function returns true if addr is an IPv4 address.
+
+```sentinel
+sockaddr.is_ipv4("192.168.12.24") // true
+sockaddr.is_ipv4("2001:0db8:85a3:0000:0000:8a2e:0370:7334") // false
+```
+
+### sockaddr.is_ipv6(addr)
+
+The is_ipv6 function returns true if addr is an IPv6 address.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/imports/strings.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/imports/strings.mdx
new file mode 100644
index 0000000000..bdacfc68a1
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/imports/strings.mdx
@@ -0,0 +1,92 @@
+---
+page_title: 'Import: strings'
+sidebar_title: 'strings'
+sidebar_current: docs-imports-strings
+description: The strings import exposes common string operations.
+layout: docs
+---
+
+# Import: strings
+
+The strings import exposes common string operations.
+
+### strings.has_prefix(s, prefix)
+
+Returns true if `s` starts with `prefix`.
+
+```sentinel
+strings.has_prefix("billing-id", "billing-") // true
+strings.has_prefix("bill-id", "billing-") // false
+```
+
+### strings.has_suffix(s, suffix)
+
+Returns true if `s` ends with `suffix`.
+
+```sentinel
+strings.has_suffix("billing-id", "id") // true
+strings.has_suffix("billing-name", "id") // false
+```
+
+### strings.join(a, sep)
+
+Joins a list with the string supplied by `sep`.
+
+Multi-dimensional lists are supported, and non-string primitive
+types will be converted to their string equivalents. Maps and
+other complex non-list types are not supported.
+
+```sentinel
+strings.join(["foo", "bar", "baz"], ".") // "foo.bar.baz"
+strings.join([["foo", "bar"], "baz"], ".") // "foo.bar.baz"
+strings.join(["a", 1, 1.01, true], ",") // "a,1,1.01,true"
+```
+
+### strings.trim_prefix(s, prefix)
+
+Trim the prefix from the string `s`. If the string doesn't have the
+prefix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_prefix("billing-id", "billing-") // "id"
+strings.trim_prefix("bill-id", "billing-") // "bill-id"
+```
+
+### strings.trim_suffix(s, suffix)
+
+Trim the suffix from the string `s`. If the string doesn't have the
+suffix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_suffix("billing-id", "-id") // "billing"
+strings.trim_suffix("bill-id", "-foo") // "bill-id"
+```
+
+### strings.to_lower(s)
+
+Lowercase the string s.
+
+```sentinel
+strings.to_lower("FoO") // "foo"
+```
+
+### strings.to_upper(s)
+
+Uppercase the string s.
+
+```sentinel
+strings.to_upper("FoO") // "FOO"
+```
+
+### strings.split(s, sep)
+
+Split the string 's' via a substring 'sep'. If 'sep' is not contained in
+'s', the return value will be a single-element list containing only 's'.
+As a special-case, if the input is already a list, it will be returned
+as-is. This allows calling the split function when not knowing if the input
+is already a list or not.
+
+```sentinel
+strings.split("foo,bar,baz", ",") // ["foo", "bar", "baz"]
+strings.split(["foo", "bar", "baz"], ",") // ["foo", "bar", "baz"]
+```
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/imports/time.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/imports/time.mdx
new file mode 100644
index 0000000000..e01dfa9f11
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/imports/time.mdx
@@ -0,0 +1,155 @@
+---
+page_title: 'Import: time'
+sidebar_title: 'time'
+sidebar_current: docs-imports-time
+description: The time import provides access to the execution time and time functions.
+layout: docs
+---
+
+# Import: time
+
+The time import provides access to the execution time and time functions.
+
+During the execution of a policy the time is fixed to the moment of
+execution. This gives the illusion that a policy instantly executes at a
+single moment of time. As an example of this, if a policy accessed
+`time.now.second` multiple times, for each access the same value would be
+returned even if they are several seconds apart. This is the execution
+`timespace`, which is mapped to `time.now`.
+
+It is also possible to run functions on a custom time by using the
+`time.load()` function to create a new timespace from the specified value.
+See the documentation for available actions on timespaces.
+
+Times specified as the reference time or via the `time.load()` function can
+be specified in a `timeish` fashion. This is either one of:
+
+* The number of seconds since the Unix epoch (Thursday, 1 January 1970,
+ 00:00:00 UTC),
+* A timestamp matching the format outlined in RFC3339, either in the
+ format `2006-01-02T15:04:05+07:00`, which includes a timezone offset, or
+ `2006-01-02T15:04:05Z`, which indicates UTC.
+
+**NOTE:** Due to limitations in the runtime, the following usage scenarios
+are not supported when using this import:
+
+* Multiple function calls in the same expression, example:
+ `time.load(1136239445).before(1136239446)`.
+* Making function calls on a returned timespace. Working off of the above
+ example, if you assigned the first value to `t` in the form `t = time.load(1136239445)`, `t.before(1136239446)` will not be supported.
+
+To work within the limitations surrounding function calls, times can be compared
+by converting them to Unix time using the `unix` function. These limitations
+may be addressed in future releases.
+
+### time.now
+
+Create a `timespace` locked either to execution time or, if set, the
+reference time. In a given execution, this will always produce the same
+value.
+
+### time.load(timeish)
+
+Create a timespace using the given value. Please see the import
+documentation for valid values for "timeish".
+
+### time.nanosecond
+
+A nanosecond duration unit for use with the `add` timespace function.
+
+### time.microsecond
+
+A microsecond duration unit for use with the `add` timespace function.
+
+### time.millisecond
+
+A millisecond duration unit for use with the `add` timespace function.
+
+### time.second
+
+A second duration unit for use with the `add` timespace function.
+
+### time.minute
+
+A minute for use with the `add` timespace function.
+
+### time.hour
+
+An hour duration unit for use with the `add` timespace function.
+
+## Type: timespace
+
+### timespace.hour
+
+The hour from 0 to 23.
+
+### timespace.minute
+
+The minute from 0 to 59.
+
+### timespace.second
+
+The second from 0 to 59.
+
+### timespace.day
+
+The day of the month, starting from 1.
+
+### timespace.month
+
+The month of the year as an integer, starting from 1.
+
+### timespace.month_name
+
+The name of the month of the year, in English. Example: `January`.
+
+### timespace.weekday
+
+The weekday as an integer, where 0 is Sunday and 6 is Saturday.
+
+### timespace.weekday_name
+
+The name of the day of the week, in English. Example: `Sunday`.
+
+### timespace.year
+
+The year as an integer.
+
+### timespace.unix
+
+The number of seconds since the Unix epoch.
+
+### timespace.unix_nano
+
+The number of nanoseconds since the Unix epoch.
+
+### timespace.zone
+
+The timezone offset, in seconds. This can be a negative number to indicate
+time west of UTC.
+
+### timespace.zone_string
+
+The timezone offset, in the format `+HH:MM` (example: `+00:00` for UTC).
+
+### timespace.before(timeish_or_timespace)
+
+Check if the time in the timespace is before the given time
+
+### timespace.after(timeish_or_timespace)
+
+Check if the time in the timespace is after the given time
+
+### timespace.equal(timeish_or_timespace)
+
+Check if two times are equivalent
+
+### timespace.add(duration)
+
+Add a duration to the time in the timespace and return a new timespace. The
+duration can be negative. The duration should be specified as an integer
+multiplied with the correct unit.
+
+### timespace.sub(timeish_or_timespace)
+
+Subtract a time from the time in the timespace and return a duration.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/imports/types.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/imports/types.mdx
new file mode 100644
index 0000000000..8fd52cef1e
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/imports/types.mdx
@@ -0,0 +1,27 @@
+---
+page_title: 'Import: types'
+sidebar_title: 'types'
+sidebar_current: docs-imports-types
+description: The types import enables a Sentinel policy to parse an object's type.
+layout: docs
+---
+
+# Import: types
+
+The types import enables a Sentinel policy to parse an object's type.
+
+### types.type_of(obj)
+
+Returns the object's type as a string
+
+```sentinel
+import "types"
+
+// Typically the inputs would come from an external source.
+is_boolean = rule { types.type_of(true) is "bool" }
+is_string = rule { types.type_of("Hello!") is "string" }
+is_integer = rule { types.type_of(42) is "int" }
+is_float = rule { types.type_of(42.123) is "float" }
+
+main = rule { is_boolean and is_string and is_integer and is_float }
+```
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/imports/units.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/imports/units.mdx
new file mode 100644
index 0000000000..d1bff53736
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/imports/units.mdx
@@ -0,0 +1,40 @@
+---
+page_title: 'Import: units'
+sidebar_title: 'units'
+sidebar_current: docs-imports-units
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: units
+
+The units import provides access to quick calculations for various byte
+units.
+
+Note that this import uses base-2 terminology:
+
+* One kilobyte is 1024 bytes
+* One megabyte is 1024^2 = 1048576 bytes
+* One gigabyte is 1024^3 = 1073741824 bytes
+* One terabyte is 1024^4 = 1099511627776 bytes
+* One petabyte is 1024^5 = 1125899906842624 bytes
+
+### units.kilobyte
+
+The base-2 representation of a kilobyte (1024 bytes).
+
+### units.megabyte
+
+The base-2 representation of a megabyte (1048576 bytes).
+
+### units.gigabyte
+
+The base-2 representation of a gigabyte (1073741824 bytes).
+
+### units.terabyte
+
+The base-2 representation of a terabyte (1099511627776 bytes).
+
+### units.petabyte
+
+The base-2 representation of a petabyte (1125899906842624 bytes).
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/index.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/index.mdx
new file mode 100644
index 0000000000..8d3acc4b60
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/index.mdx
@@ -0,0 +1,23 @@
+---
+layout: 'docs'
+page_title: 'Documentation'
+sidebar_current: 'docs-home'
+description: |-
+ Sentinel is a language and framework for policy built to be embedded in existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+[introduction](/sentinel/intro) to understand what Sentinel is, how it
+compares to other software, and more.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/internals/index.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/internals/index.mdx
new file mode 100644
index 0000000000..fa74af613d
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/internals/index.mdx
@@ -0,0 +1,18 @@
+---
+page_title: Sentinel Internals
+sidebar_title: Internals
+sidebar_current: docs-internals
+description: Imports allow a Sentinel policy to access external data and add new functions.
+layout: docs
+---
+
+# Sentinel Internals
+
+This section covers the internals of Sentinel and explains how evaluation
+occurs, plugins are executed, etc. The goal of this section is to remove
+any notion of "magic" from Sentinel to allow you to trust and understand what
+Sentinel is doing.
+
+-> **Note:** Knowledge of Sentinel internals is not required to use Sentinel.
+If you aren't interested in the internals of Sentinel, you may safely skip this
+section.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/internals/plugins.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/internals/plugins.mdx
new file mode 100644
index 0000000000..6c0595fa7a
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/internals/plugins.mdx
@@ -0,0 +1,64 @@
+---
+page_title: Plugin Internals
+sidebar_title: Plugins
+sidebar_current: docs-internals-plugins
+description: Imports allow a Sentinel policy to access external data and add new functions.
+layout: docs
+---
+
+# Plugin Internals
+
+Sentinel is built on a plugin-based architecture to support
+[imports](/sentinel/extending). This allows anybody to extend
+Sentinel to support new sources of data for making policy decisions as well
+as to expose new functions for working with data. This page documents
+the internals of how plugins work, not how to write them.
+
+If you're interested in developing an import plugin, please see the
+[developing an import plugin page](/sentinel/extending/dev).
+
+~> **Advanced Topic!** This page covers technical details of Sentinel. You
+don't need to understand these details to effectively use Sentinel. The details
+are documented here for those who wish to learn about them.
+
+## Basics
+
+Sentinel plugins are built on top of the HashiCorp
+[go-plugin system](https://github.com/hashicorp/go-plugin). This is the
+same plugin system powering all pluggable HashiCorp tools such as
+[Terraform](https://www.terraform.io),
+[Vault](https://www.vaultproject.io), and more. go-plugin is a system
+that has been used in production for millions of users for over 5 years.
+
+Plugins are executable binaries. Sentinel is responsible for launching
+and managing the lifecycle of plugins. Once a plugin is running, Sentinel
+communicates with the plugin via [gRPC](https://grpc.io/). The
+[protocol is open source](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/import.proto)
+to allow anyone to write a Sentinel plugin.
+
+## Lifecycle
+
+Sentinel launches plugins and is responsible for plugin lifecycle.
+
+The runtime optimizes for latency by launching the plugin as soon as
+it is configured, rather than when a policy requires it. This ensures that
+the plugin is ready to be used immediately. A plugin is only closed when
+Sentinel is reconfigured to no longer allow that plugin or if the
+Sentinel-enabled application is closing.
+
+When a plugin is removed from the configuration, Sentinel may wait to
+shut down the plugin until all currently executing policies that are using
+that plugin complete. New policy executions will not be allowed to use
+the old plugins.
+
+Plugins are automatically restarted if they shut down unexpectedly.
+Policies that were executing while this happens may fail. The Sentinel
+system is improving to more gracefully understand locations where it is
+safe to retry an execution.
+
+## Communication
+
+An [initial handshake](https://github.com/hashicorp/go-plugin/blob/master/docs/internals.md)
+is done via stdout to determine the main communication location. Following
+the handshake, communication will either occur on a local Unix domain socket
+or via a local-only TCP socket.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/language/arithmetic.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/language/arithmetic.mdx
new file mode 100644
index 0000000000..3a341db72e
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/language/arithmetic.mdx
@@ -0,0 +1,68 @@
+---
+page_title: Sentinel Language - Arithmetic
+sidebar_title: Arithmetic
+sidebar_current: docs-language-arith
+description: >-
+ Sentinel supports arithmetic operators for integers and floats. Sentinel
+ supports sum, difference, product, quotient, and remainder.
+layout: docs
+---
+
+# Language: Arithmetic
+
+Sentinel supports arithmetic operators for integers and floats. Sentinel
+supports sum, difference, product, quotient, and remainder.
+
+```plaintext
++ sum
+* difference
+* product
+/ quotient
+% remainder
+```
+
+These operators work in a typical infix style:
+
+```sentinel
+4 + 8 // 12
+8 * 2 // 16
+8 / 4 // 2
+8 / 5 // 1
+8 % 5 // 3
+```
+
+## Order of Operations
+
+Arithmetic follows a standard mathematical order of operations.
+Grouping with parentheses can be used to affect ordering.
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+(4 + 5) * 2 // 18
+```
+
+A full table of operator precendence can be found on the
+[boolean expressions page](/sentinel/language/boolexpr). This
+shows how arithmetic operators relate to other operators.
+
+## Integer Division
+
+Integer division with a remainder rounds down to the nearest integer.
+Example: `8 / 3 is 2`.
+
+If the divisor is zero, an error occurs.
+
+## Mixed Numeric Operations
+
+Mixed numeric operations between integer and floating-point values are
+permitted. The result is a floating-point operation with the integer converted
+to a floating-point value for purposes of calculation.
+
+```sentinel
+import "types"
+
+a = 1.1 + 1 // 2.1
+types.type_of(a) // "float"
+```
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/language/boolexpr.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/language/boolexpr.mdx
new file mode 100644
index 0000000000..86432c4153
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/language/boolexpr.mdx
@@ -0,0 +1,193 @@
+---
+page_title: Sentinel Language - Boolean Expressions
+sidebar_title: Boolean Expressions
+sidebar_current: docs-language-boolexpr
+description: >-
+ Boolean expressions are expressions that evaluate to a boolean value from the
+ result of a combination of one or more comparisons and logical operators.
+layout: docs
+---
+
+# Language: Boolean Expressions
+
+Boolean expressions are expressions that evaluate to a boolean value
+from the result of a combination of one or more comparisons and logical
+operators. Boolean expressions form the basis of policy since policy can be broken
+down to a set of logical decisions that turn into true or false.
+
+A single boolean expression is the body of a [rule](/sentinel/language/rules).
+Additionally, boolean expressions are used with [conditionals](/sentinel/language/conditionals),
+and more.
+
+## Order of Operations
+
+The precedence of operators is shown below. Operators with higher
+precedence values (larger numbers) are evaluated first. Operators with
+the same precedence value are evaluated left-to-right.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Examples of this precedence are shown below:
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+```
+
+## Logical Operators
+
+Logical operators are used to change or combine logical expressions.
+There are three binary operators `and`, `or`, and `xor`. There is
+a single unary operator `not` (which can also be specified as `!`).
+
+The binary operators require two boolean operands and the unary
+operator requires a single boolean operand. In both case, the operands
+are values or expressions that are boolean types.
+
+Below is a basic explanation of the logical operators directly from
+the specification:
+
+```sentinel
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+Using parentheses to group boolean expressions, you can combine
+boolean expressions to become more complex or affect ordering:
+
+```sentinel
+(p and q) or r
+p and (q or r)
+```
+
+## Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+For any comparison, the two operands must be equivalent types. The one
+exception are integers and floats. When an integer is compared with
+a float, the integer is promoted to a floating point value.
+
+The available comparison operators are:
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+The behavior of `is` with `==` and `is not` with `!=` is identical.
+
+Example comparisons:
+
+```sentinel
+name is "Mitchell"
+idnumber > 42
+idnumber <= 12
+```
+
+Using the logical operators, these can be combined:
+
+```sentinel
+name is "Mitchell" and idnumber > 42
+```
+
+The language specification provides more detail on exactly
+[how comparison behaves](/sentinel/language/spec#comparison-operators).
+
+## Set Operators
+
+The set operators `contains` and `in` test for inclusion in a collection
+(a list or map).
+
+Set operators may 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.
+
+`contains` tests if the left-hand collection contains the right-hand value.
+`in` tests if the right-hand collection contains the left-hand value. For
+maps, "contains" looks at the keys, not the values.
+
+Examples:
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+```
+
+## Matches Operator
+
+The `matches` operator tests if a string matches a regular expression.
+The `matches` operator can be negated by prefixing the operator with
+`not`, such as `not matches`.
+
+The left-hand value is the string to test. The right-hand value is
+a string value representing a regular expression. The syntax of the regular
+expression is the same general syntax used by Python, Ruby, and other languages.
+The precise syntax accepted is the syntax accepted by RE2.
+
+Regular expressions are not anchored by default; any anchoring must be
+explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+## Any, All Expressions
+
+`any` and `all` expressions allow testing that any or all elements of a
+collection match some boolean expression. The result of an `any` and `all`
+expression is a boolean.
+
+`any` returns the boolean value `true` if any value in the collection expression
+results in the body expression evaluating to `true`. If the body expression
+evalutes to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression
+result in the body expression evaluating to `true`. If any value in the
+collection expression result in the body expression evaluating to `false`,
+the `all` expression returns `false`.
+
+For empty collections, `any` returns `false` and `all` returns `true`.
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" }
+any group.tasks as t { t.driver is "vmware" }
+```
+
+Since `any` and `all` expressions are themselves boolean expressions, they
+can be combined with other operators:
+
+```sentinel
+any ["a", "b"] as char { char is "a" } or
+other_value is "another"
+```
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/language/conditionals.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/language/conditionals.mdx
new file mode 100644
index 0000000000..af85dafcc6
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/language/conditionals.mdx
@@ -0,0 +1,162 @@
+---
+page_title: Sentinel Language - Conditionals
+sidebar_title: Conditionals
+sidebar_current: docs-language-conditional
+description: |-
+ Conditional statements allow your policy to behave differently depending on a condition.
+layout: docs
+---
+
+# Language: Conditionals
+
+Conditional statements allow your policy to behave differently depending
+on a condition.
+
+Conditional statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## If Statements
+
+`if` statements only execute their bodies if a condition is met.
+The syntax of an `if` statement is:
+
+```sentinel
+if condition {
+ // ... this is executed if condition is true
+}
+```
+
+The `condition` must result in a boolean, such as by calling a function
+or evaluating a [boolean expression](/sentinel/language/boolexpr). If
+the `condition` is `true`, the body (within the `{}`) is executed. Otherwise,
+the body is skipped.
+
+Examples:
+
+```sentinel
+// This would execute the body
+value = 12
+if value is 18 {
+ print("condition met")
+}
+
+// Direct boolean values can be used
+value = true
+if value {
+ print("condition met")
+}
+
+// This would not execute the body since the boolean expression will
+// result in undefined.
+value = {}
+if value["key"] > 12 {
+ print("condition met")
+}
+```
+
+### Else, Else If
+
+An `else` clause can be given to an `if` statement to execute a body
+in the case the condition is _not met_. By putting another `if` statement
+directly after the `else`, multiple conditions can be tested for.
+The syntax is:
+
+```sentinel
+if condition {
+ // ...
+} else {
+ // ...
+}
+
+if condition {
+ // ...
+} else if other_condition {
+ // ...
+} else {
+ // ...
+}
+```
+
+### Scoping
+
+The body of an `if` statement does not create a new
+[scope](/sentinel/language/scope). Any variables assigned within the body
+of an if statement will modify the scope that the `if` statement itself
+is in.
+
+Example:
+
+```sentinel
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+```sentinel
+a = 18
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+## Case Statements
+
+`case` statements are a selection control mechanism that execute a clause based
+on matching expressions. It is worth noting that the expression for `case` is
+optional. When no expression is provided, it defaults the expression to `true`.
+Additionally, the order of clauses is important, as they are evaluated from top
+to bottom, executing the first match.
+The syntax of a case statement is:
+
+```sentinel
+case expression {
+ when clause_expression:
+ // executed when clause_expression and expression are equal
+ else:
+ // executed if no clause matches expression
+}
+```
+
+### When Clause
+
+Any clause that has an expression for comparison must use the `when` keyword.
+It accepts a list of expressions, seperated by a `,`.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+}
+```
+
+```sentinel
+case {
+ when x > 40:
+ return true
+}
+```
+
+### Else Clause
+
+The `else` keyword allows for capturing any expressions that have no matching
+`when` clause.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+ else:
+ return false
+}
+```
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/language/functions.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/language/functions.mdx
new file mode 100644
index 0000000000..c55dfe4d81
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/language/functions.mdx
@@ -0,0 +1,174 @@
+---
+page_title: Sentinel Language - Functions
+sidebar_title: Functions
+sidebar_current: docs-language-functions
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Functions
+
+Functions allow you to create reusable code to perform computations.
+
+Below is a trivial example function that doubles a number and shows
+the main rule using the function:
+
+```sentinel
+double = func(x) {
+ return x * 2
+}
+
+main = rule { double(12) is 24 }
+```
+
+Functions may contain any expressions,
+[conditionals](/sentinel/language/conditionals),
+and [loops](/sentinel/language/loops). They must return a value
+at the end of their execution. If no reasonable return value exists,
+the function should return [undefined](/sentinel/language/undefined).
+
+## Creating a Function
+
+A function is created by assigning a variable to a `func`.
+
+A function can have zero or more parameters. These parameters are specified
+within parentheses `()` separated by commas. If a function has no parameters,
+the parentheses must still be present.
+
+A function must terminate with a `return` statement to return a value back to
+the caller. Only a single value can be returned. The type of a return can
+vary between return statements.
+
+Example:
+
+```sentinel
+add1 = func(x) { return x + 1 }
+```
+
+This example creates a function that adds 1 to the parameter `x`. It is
+assigned to the variable `add1`.
+
+## Calling a Function
+
+Functions are called by accessing their name followed by parentheses with
+a list of comma-separated values for the parameters. If the function takes no
+parameters, an empty set of parentheses must still be used to denote a
+function call.
+
+To call the function in the example above:
+
+```sentinel
+x = 1
+y = add1(x)
+```
+
+## Scoping
+
+The body of a `func` creates a new [scope](/sentinel/language/scope).
+
+The parent scope is the scope in which the function is created. This allows
+for the creation of functions within functions that can access their outer
+scopes.
+
+Example:
+
+```sentinel
+f = func() {
+ a = 42
+ print(a) // 42
+ return undefined
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+f = func() {
+ a = 42
+ return undefined
+}
+
+print(a) // 18
+```
+
+And below is an example of creating a function in a function which uses
+outer values:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double()
+}
+
+print(f()) // 84
+```
+
+A more complex example below shows how scoping works when passing
+functions around as arguments or results:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double
+}
+
+double = f()
+print(double()) // 84
+```
+
+## Pass By Value
+
+The parameters to a function are passed by value, but not deep copied.
+This means that elements of collections can still be modified but the
+root value cannot be changed.
+
+Example:
+
+```sentinel
+f = func(x) {
+ x = "value"
+ return x
+}
+
+x = "outside"
+f(x)
+print(x) // "outside"
+```
+
+```sentinel
+f = func(x) {
+ append(x, "value")
+ return x
+}
+
+x = []
+f(x)
+print(x) // ["value"]
+```
+
+## Recursion
+
+A function is allowed to call other functions, including itself.
+This can be used to implement recursion. For example, the fibonacci sequence
+is implemented below:
+
+```sentinel
+fib = func(x) {
+ if x <= 0 {
+ return undefined
+ }
+
+ if x == 1 {
+ return 1
+ } else {
+ return x + fib(x - 1)
+ }
+}
+```
+
+Note that this example also shows using
+[undefined](/sentinel/language/undefined)
+as a return value in cases where a function has undefined behavior.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/language/imports.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/language/imports.mdx
new file mode 100644
index 0000000000..945bb95930
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/language/imports.mdx
@@ -0,0 +1,117 @@
+---
+page_title: Sentinel Language - Imports
+sidebar_title: Imports
+sidebar_current: docs-language-imports
+description: >-
+ Imports enable a Sentinel policy to access reusable libraries and external
+ data and functions.
+layout: docs
+---
+
+# Language: Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external
+data and functions. The exact list of available imports is dependent on the
+host system. Host systems may also make the available imports configurable
+via plugins.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that
+the import `calendar` allows access to engineer calendars. This import
+only exists for the purpose of this example and at the time of writing is
+not a real available import.
+
+```sentinel
+import "calendar"
+
+// Get the calendar for Bob for today
+bob_calendar = calendar.for("bob").today
+
+// Allow this policy to pass if Bob is not on vacation.
+main = rule { not bob_calendar.has_event("vacation") }
+```
+
+This example shows an import being used to deny a behavior if Bob is on
+vacation. Hopefully real policies do not depend on a single person being
+in the office, but the example shows the power of imports.
+
+In addition to consuming imports, you can also
+[extend Sentinel by writing custom imports](/sentinel/extending)
+This allows you to add any arbitrary behavior to your Sentinel-protected
+systems.
+
+## Importing
+
+Whether accessing a built-in library or an external plugin, the syntax
+for an import is the same:
+
+```sentinel
+import "name"
+```
+
+This adds the import with the name `name`. It can be referenced using
+the identifier `name`. For example, if the import above exposed first
+and last name data, you could access it via `name.first` or `name.last`.
+
+You can shadow imports by assigning a variable. In the example below,
+the import `name` becomes unavailable within the function because it is
+shadowed by a variable of the same name.
+
+Shadowing an import is not the same as overwriting a variable. Imports
+are not part of the [scope](/sentinel/language/scope), meaning that
+the shadow only exists for the scope it is created within. For everything
+else, the import works even after it is shadowed. The example below shows that
+as well.
+
+```sentinel
+import "name"
+
+f = func() {
+ name = "jane"
+ return name
+}
+
+print(f()) // "jane"
+print(name.first) // "bob"
+```
+
+## Aliases
+
+An import can be aliased to another name using the syntax below:
+
+```sentinel
+import "name" as other
+```
+
+This would make the import "name" available as `other`.
+
+An import may only be imported once, regardless of aliases. The following
+would be **invalid** and would result in an error:
+
+```sentinel
+import "name" as one
+import "name" as two
+```
+
+## Accessing Import Data
+
+Imports are accessed with dot-separated identifiers, such as `name.first` or
+`name.stage.first`. These are called _selector expressions_. An import may
+return nested data that further can be accessed via selector expressions.
+
+In the first example on this page with the "calendar" import, we see this:
+
+```sentinel
+import "calendar"
+
+a = calendar.for("bob") // selector expression calendar.for
+b = a.today // selector expression on the result
+c = b.vacation // accessing further nested data
+```
+
+The exact structure of an import is dependent on the import. Please reference
+the documentation for an import to learn more.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/language/index.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/language/index.mdx
new file mode 100644
index 0000000000..fe23ee1144
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/language/index.mdx
@@ -0,0 +1,100 @@
+---
+page_title: Sentinel Language
+sidebar_title: Language
+sidebar_current: docs-language-basics
+description: |-
+ Sentinel policies are written using the Sentinel language. This language
+ is easy to learn and easy to write. You can learn the Sentinel language
+ and be productive within an hour. Learning Sentinel doesn't require any
+ formal programming experience.
+layout: docs
+---
+
+# Sentinel Language
+
+Sentinel policies are written using the Sentinel language. This language
+is easy to learn and easy to write. You can learn the Sentinel language
+and be productive within an hour. Learning Sentinel doesn't require any
+formal programming experience.
+
+This language guide will contain many details that are not necessary to
+be productive with Sentinel or are targeted to those who are looking for
+exact information about the language (such as what types of line endings are
+allowed). You can safely ignore these sentences.
+
+You may also view the
+[official language specification](/sentinel/language/spec)
+This is a specific and detailed document on the syntax and behavior of
+the language primarily intended for implementation creators and to disambiguate
+the language.
+
+## Simplest Example
+
+The example below is about the simplest practical example of Sentinel.
+It is reasonable to imagine this as a realistic policy. This shows that
+in most cases, Sentinel will be extremely simple:
+
+```sentinel
+main = rule { request.method is "GET" and request.headers contains "X-Key" }
+```
+
+## Files
+
+Sentinel policies are single files that end in the `.sentinel` file extension.
+There is currently no built-in mechanism to Sentinel for merging multiple
+files. This is purposefully done to make Sentinel policies easy to submit
+to systems that support Sentinel policies.
+
+Sentinel policy files must be UTF-8 encoded and can end in both
+Unix (LF) or Windows (CRLF) line breaks. When running the
+[auto-formatter](#),
+line endings will always use Unix line breaks.
+
+## Ordering
+
+Sentinel policies are executed top-down. For example:
+
+```sentinel
+a = 1 // a = 1 here
+b = a + 1 // b = 2 here
+a = 3 // a = 3, b = 2
+```
+
+In this example, the value of `a` and `b` is shown at each line. Since Sentinel
+executes values top-down, the final value of `a` is 3 and `b` is 2. `b` _does
+not_ become 4.
+
+## Main
+
+Sentinel expects there to be a `main` [rule](/sentinel/language/rules).
+The value of this rule is the result of the entire policy.
+
+If the result of `main` is true, the policy passes. If the value is anything
+else (false or a non-boolean value), the policy fails. The exact meaning
+of what happens a policy passes or fails is dependent on the host system.
+
+## More Complex Example
+
+The simple example above is a full working example. In our experience with
+Sentinel, many policies can be representing using this simple form. However,
+to show more features of the language, a more complex example is shown below.
+This example is also a realistic example of what Sentinel may be used for.
+
+```sentinel
+import "units"
+
+memory = func(job) {
+ result = 0
+ for job.groups as g {
+ for g.tasks as t {
+ result += t.resources.memory else 0
+ }
+ }
+
+ return result
+}
+
+main = rule {
+ memory(job) < 1 * units.gigabyte
+}
+```
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/language/lists.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/language/lists.mdx
new file mode 100644
index 0000000000..5cbe310242
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/language/lists.mdx
@@ -0,0 +1,131 @@
+---
+page_title: Sentinel Language - Lists
+sidebar_title: Lists
+sidebar_current: docs-language-lists
+description: Lists are a collection of zero or more values.
+layout: docs
+---
+
+# Language: Lists
+
+Lists are a collection of zero or more values.
+
+Lists can be created using by wrapping values in `[]` and separating them
+by commas. An optional trailing comma is allowed. List elements can be
+differing types. Examples:
+
+```sentinel
+[] // An empty list
+["foo"] // Single element list
+["foo", 1, 2, true] // Multi element list with different types
+["foo", [1, 2]] // List containing another list
+```
+
+A list can be [sliced](/sentinel/language/slices) to create sublists.
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for value inclusion in a list.
+
+## Accessing Elements
+
+List elements can be accessed with the syntax `name[index]` where
+`index` is zero-indexed.
+
+A negative index accesses the list in reverse. It is the same as
+reversing a list and then using a positive index. Similar to a positive
+index, it is bounded by the length of the list as a negative value.
+
+Accessing beyond the length of the list results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+list = ["foo", 1, true, [1, 2]]
+
+list[0] // "foo"
+list[2] // true
+list[4] // undefined
+list[-2] // true
+list[-4] // "foo"
+list[-5] // undefined
+list[3][1] // 2
+```
+
+## List Append
+
+Values can be appended to a list using the built-in
+[append](/sentinel/language/funcs/append) function.
+
+This modifies the list in-place and returns
+[undefined](/sentinel/language/undefined). For more information on
+why `append` behaves this way, please read the full documentation for the
+[append](/sentinel/language/funcs/append) function.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append([1,2], "foo") // [1, 2, "foo"]
+append([1,2], [3]) // [1, 2, [3]]
+append(1, 3) // error()
+```
+
+## List Concatenation
+
+Two lists can be concatenated using the `+` operator or the shorthand
+`+=` assignment operator. For the `+` operator, a new list is returned.
+For `+=`, the left-hand list is modified in place.
+
+Examples:
+
+```sentinel
+[1] + [2] // [1, 2]
+[1] + [[1]] // [1, [1]]
+[1] + 1 // error
+
+a = [1]
+a += [2] // a = [1, 2]
+a += 3 // error
+```
+
+## List Length
+
+The length of a list can be retrieved using the
+[length](/sentinel/language/funcs/length) function.
+
+Examples:
+
+```sentinel
+length([]) // 0
+length(["foo"]) // 1
+```
+
+## Removing Items From a List
+
+You can use a combination of [list concatenation](#list-concatenation) and
+[slicing](/sentinel/language/slices) to remove elements from a list:
+
+```sentinel
+a = [0, 1, 2]
+a = foo[:1] + foo[2:] // [0, 2]
+```
+
+## List Comparison
+
+Lists may be [compared](/sentinel/language/boolexpr#comparison-operators)
+for equality. Lists are equal if they are of equal length and their
+corresponding elements are comparable and equal. Lists with the same elements
+but in a different order are not equal.
+
+```sentinel
+[1, 2] is [1, 2] // true
+[1, 2] is [2, 1] // false
+["a"] is ["a", "b"] // false
+["a", ["b", "c"]] is ["a", ["b", "c"]] // true
+```
+
+List comparison speed is O(N), meaning that the speed of the comparison is
+_linearly_ proportional to the number of elements in the list. The more
+elements, the more iterations that are necessary to verify equality.
+
+-> The N-value quoted above should account for the sum of the elements of _all_
+lists in the subjects of comparison, as list comparison will recurse into these
+lists to check for equality.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/language/logging-errors.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/language/logging-errors.mdx
new file mode 100644
index 0000000000..32427697d9
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/language/logging-errors.mdx
@@ -0,0 +1,53 @@
+---
+page_title: Sentinel Language - Logging and Errors
+sidebar_title: Logging and Errors
+sidebar_current: docs-language-log
+description: The built-in functions `print` and `error` can be used for logging and errors.
+layout: docs
+---
+
+# Language: Logging and Errors
+
+The built-in functions `print` and `error` can be used for logging
+and errors. Logging is helpful to understand how a policy is executing
+in development. And errors are useful to halting execution immediately in
+certain scenarios.
+
+## Print
+
+The `print` function is used to output debug information. The exact location
+where this print statements go is dependent on the host application and
+the Sentinel runtime itself.
+
+The `print` function takes one or more Sentinel values as arguments.
+Each value is formatted for human-friendly output and space-separated.
+Example:
+
+```sentinel
+value = 42
+print("the value is", value) // the value is 42
+
+map = { "foo": false }
+print(map) // { "foo": false }
+
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // false
+```
+
+## Errors
+
+Certain actions in Sentinel, such as accessing an undefined variable,
+attempting to add two incompatible types, etc. result in _runtime errors_.
+These errors halt the execution of a policy immediately. The pass/fail result
+of a policy is considered fail.
+
+Runtime errors can be manually triggered using the `error` function.
+The `error` function behaves exactly like the `print` function except that
+execution is halted immediately.
+
+This should only be used in scenarios where the policy _must fail_ due to
+some unexpected or invalid condition. The line between `error` and
+[undefined](/sentinel/language/undefined) can sometimes be unclear. Undefined
+is recommended when a policy can conceivably recover or safely ignore the
+undefined behavior. An error should be used when the policy should fail and
+notify someone regardless.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/language/loops.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/language/loops.mdx
new file mode 100644
index 0000000000..d80064de7a
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/language/loops.mdx
@@ -0,0 +1,93 @@
+---
+page_title: Sentinel Language - Loops
+sidebar_title: Loops
+sidebar_current: docs-language-loops
+description: >-
+ Loop statements allow you to execute a body of code for each element in a
+ collection or for some fixed number of times.
+layout: docs
+---
+
+# Language: Loops
+
+Loop statements allow you to execute a body of code for each element in
+a collection or for some fixed number of times.
+
+Loop statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/docs/language/functions.html) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## For Statements
+
+`for` statements allow repeated execution of a block for each
+element in a collection.
+
+Example:
+
+```sentinel
+// A basic sum
+count = 0
+for [1, 2, 3] as num {
+ count += num
+}
+```
+
+The syntax is `for COLLECTION as value`. This will iterate over the
+collection, assigning each element to `value`. In the example above,
+each element is assigned to `num`. The body is executed for each element.
+In the example above, the body adds `num` to the `count` variable. This
+creates a basic sum of all values in the collection.
+
+For a map, the assigned element is the key in the map. In the example
+below, `name` would be assigned map values.
+
+```sentinel
+list = []
+for { "a": 1, "b": 2 } as name {
+ append(list, name)
+}
+
+print(list) // ["a" "b"]
+```
+
+An alternate syntax is `for COLLECTION as key, value`. This will assign
+both the key and value to a variable. For a list, the key is the element
+index. For a map, it is the key and value is assigned the element value.
+Example:
+
+```sentinel
+count = 0
+for { "a": 1, "b": 2 } as name, num {
+ count += num
+}
+
+print(count) // 3
+```
+
+## Scoping
+
+The body of a `for` statement creates a new
+[scope](/sentinel/language/scope). If a variable is assigned within
+the body of a for statement that isn't assigned in a parent scope,
+that variable will only exist for the duration of the body execution.
+
+Example:
+
+```sentinel
+for list as value {
+ a = 42
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+for list as value {
+ a = 42
+}
+
+print(a) // 18
+```
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/language/maps.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/language/maps.mdx
new file mode 100644
index 0000000000..8dd30ab02c
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/language/maps.mdx
@@ -0,0 +1,100 @@
+---
+page_title: Sentinel Language - Maps
+sidebar_title: Maps
+sidebar_current: docs-language-maps
+description: >-
+ Maps are a collection of zero or more key/value pairs. These are useful for
+ storing values that are looked up by a unique key.
+layout: docs
+---
+
+# Language: Maps
+
+Maps are a collection of zero or more key/value pairs. These are useful
+for storing values that are looked up by a unique key.
+
+Maps can be created using `{}` and specifying key/value pairs. Keys and
+values can be differing types. An optional trailing comma is allowed.
+Examples:
+
+```sentinel
+// Empty map
+{}
+
+// Map with a single value on one line
+{ "key": "value" }
+
+// Map with multiple values with differing types on multiple lines
+{
+ "key": "value",
+ 42: true,
+}
+```
+
+Maps are **unordered**. When
+[looping](/sentinel/language/loops)
+over a map, the key/value pairs can be returned in any order.
+
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for key inclusion in a map.
+
+## Accessing Elements
+
+Map elements can be accessed with the syntax `name[key]`. This looks up
+a value by a key.
+
+Accessing a key that doesn't exist results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+map = { "key": "value", 42: true, }
+
+map["key"] // "value"
+map[42] // true
+map[0] // undefined
+```
+
+## Modifying or Adding Elements
+
+Elements can be added or modified in a map by assigning to `name[key]`.
+If the key doesn't exist, the value is added. If the key already exists,
+the value is overridden.
+
+```sentinel
+map = { "key": "value" }
+
+map[42] = true // Add a new key/value
+map["key"] = 12 // Modify the value of "key"
+```
+
+## Deleting Elements
+
+An element can be deleted from a map using the
+[delete](/sentinel/language/funcs/delete) function.
+
+Examples:
+
+```sentinel
+map = { "key": "value" }
+delete(map, "key") // map is now empty
+delete(map, "other") // no effect for non-existent key
+```
+
+## Keys and Values
+
+The keys and values of a map can be retrieved as
+[lists](/sentinel/language/lists) using the
+[keys](/sentinel/language/funcs/keys) and
+[values](/sentinel/language/funcs/values) functions.
+
+Because maps are unordered, the keys and values are returned in an
+unspecified order. It should not be assumed that keys and values will be
+returned in a consistent order.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/language/parameters.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/language/parameters.mdx
new file mode 100644
index 0000000000..2a73b3eaf9
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/language/parameters.mdx
@@ -0,0 +1,136 @@
+---
+page_title: Sentinel Language - Parameters
+sidebar_title: Parameters
+sidebar_current: docs-language-parameters
+description: >-
+ Parameteres allow a Sentinel policy to describe variables that are expected
+ to be supplied by the calling application, in addition to any default value.
+layout: docs
+---
+
+# Language: Parameters
+
+Sentinel allows a policy author to supply parameters to help facilitate policy
+reuse and ensure sensitive values do not need to be hard-coded in a policy.
+
+Parameters are supplied by using the `param` keyword, followed by an identifier.
+A default value can also be supplied by using the `default` keyword.
+
+```sentinel
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+```
+
+Once declared, parameters can be used like any other variable, including being
+re-assigned.
+
+```sentinel
+param foo default 1 // 1 (default)
+
+foo = foo + 1 // 2
+```
+
+## Variable Descriptions
+
+You can supply a description to a parameter by adding a comment at the top of
+it. This value can be communicated to a specific implementation of Sentinel to
+provide information about what the parameter is for during configuration.
+
+```sentinel
+// An exmaple parameter. Must be supplied or the policy will fail.
+param foo
+```
+
+## Supplying Parameter Values Using the Sentinel CLI
+
+In a production implementation, supplying parameters to a policy is an
+implementation-specific detail - see the documentation for your particular
+implementation for details.
+
+Using the [Sentinel CLI](/sentinel/commands), you can supply parameters one of
+four ways.
+
+### Supplying Parameter Values Using the Configuration File
+
+You can supply parameters using the
+[`param`](/sentinel/commands/config#parameters) section of the
+[configuration file](/sentinel/commands/config).
+
+```json
+{
+ "param": {
+ "foo": "bar"
+ }
+}
+```
+
+This method works for both `sentinel apply` and `sentinel test`.
+
+### Supplying Parameter Values Using the Environment
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can supply a value using environment variables - prefix the parameter with
+`SENTINEL_PARAM_`, using the name of the parameter to supply.
+
+```plaintext
+SENTINEL_PARAM_foo=bar sentinel apply policy.sentinel
+```
+
+### Supplying Parameter Values Using the CLI
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can also use the `-param` CLI argument to supply parameter in a `key=value`
+pair.
+
+```plaintext
+sentinel apply -param foo=bar policy.sentinel
+```
+
+### Interactive CLI Prompting
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+If a required value has not been supplied when a policy is run with `sentinel apply`, it will be prompted for, along with its description:
+
+
+
+ $ sentinel apply policy.sentinel
+
+ policy.sentinel:2:7: requires value for parameter foo
+
+ An example parameter. Must be supplied or the policy will fail.
+
+
+ Values can be strings, floats, or JSON array or object values. To
+ force
+
+ strings, use quotes.
+
+
+ Enter a value: bar
+
+
+
+ Pass
+
+
+
+
+### CLI Value Format
+
+-> **NOTE:** This section contains details for the parameter features supported by `sentinel apply`.
+
+The CLI takes either strings, or JSON numbers, arrays, or maps. If you need a
+literal string value, quote the value.
+
+```sentinel
+foo // string
+42 // number (float)
+"42" // string ("42", without quotes)
+[1, 2] // array (list)
+{"a": "b"} // object (map)
+```
+
+-> **NOTE:** Boolean values are not supported by this method.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/language/rules.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/language/rules.mdx
new file mode 100644
index 0000000000..0dd1438f5b
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/language/rules.mdx
@@ -0,0 +1,165 @@
+---
+page_title: Sentinel Language - Rules
+sidebar_title: Rules
+sidebar_current: docs-language-rules
+description: >-
+ Rules form the basis of a policy by representing behavior that is either
+ passing or failing (true or false).
+layout: docs
+---
+
+# Language: Rules
+
+Rules form the basis of a policy by representing behavior that is either
+passing or failing (true or false). A policy can be broken down into a
+set of rules. Breaking down a policy into a set of rules can make it more
+understandable and aids with testing.
+
+An example is shown below:
+
+```sentinel
+is_sunny = rule { weather is "sunny" }
+is_wednesday = rule { day is "wednesday" }
+
+main = rule {
+ is_sunny and
+ is_wednesday
+}
+```
+
+A rule contains a single [boolean expression](/sentinel/language/boolexpr).
+This boolean expression can be split into multiple lines for readability
+as shown in the example above. Boolean expressions should only be split
+into multiple lines after the operator ("and", "or", etc.).
+
+A rule is also _lazy_ and _memoized_. _Lazy_ means that the rule is only
+evaluated when it is actually required, not at the point that it is
+created. This is covered in more detail in the [lazy](#lazy) section.
+_Memoized_ means that the value is only computed once and then saved. Once
+a rule is evaluated, the result of it is reused anytime the rule is referenced
+again.
+
+## Easing Understandability
+
+Rules are an abstraction that make policies much more understandable
+by making it easier for humans to see what the policy is trying to do.
+
+For example, consider the policy before which doesn't abstract into rules:
+
+```sentinel
+main = rule {
+ ((day is "saturday" or day is "sunday") and homework is "") or
+ (day in ["monday", "tuesday", "wednesday", "thursday", "friday"] and
+ not school_today)
+}
+```
+
+Assume that `day`, `homework`, and `school_day` are available.
+
+In plain english, this policy is trying to say: you can play with your friends
+only on the weekend as long as there is no homework, or during the week if
+there is no school and no homework.
+
+Despite the relatively simple nature of this policy (a few lines, about 5
+logical expressions), it is difficult to read and undertand, especially if
+you've not seen it before.
+
+The same policy using rules as an abstraction:
+
+```sentinel
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+is_valid_weekend = rule { is_weekend and homework is "" }
+is_valid_weekday = rule { not is_weekend and not school_today }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+By reading the names of the rules, its much clearer to see what each individual
+part of the policy is trying to achieve. The readability is improved even
+further when adding comments to the rules to explain them further, which is
+a recommended practice:
+
+```sentinel
+// A weekend is Sat or Sun
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+// A valid weekend is a weekend without homework
+is_valid_weekend = rule { is_weekend and homework is "" }
+
+// A valid weekday is a weekday without school
+is_valid_weekday = rule { not is_weekend and not school_today }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+## "When" Predicates
+
+A rule may have an optional `when` predicate attached to it. When this is
+present, the rule is only evaluated when the predicate results in `true`.
+Otherwise, the rule is not evaluated and the result of the rule is always
+`true`.
+
+"When" predicates should be used to define the context in which the rule
+is semantically meaningful. It is common when translating human language
+policy into Sentinel to have scenarios such as "when the key has a prefix
+of `/account/`, the remainder must be numeric." In that example, when the
+key _does not_ have the specified prefix, the policy doesn't apply.
+
+Assume you have rules `is_prefix` and `is_numeric` to check both the
+conditions in the example above. An example is shown with and without
+the "when" predicate:
+
+```sentinel
+example_no_when = rule { (is_prefix and is_numeric) or not is_prefix }
+
+example_when = rule when is_prefix { is_numeric }
+```
+
+The rules are equivalent in behavior for all values of `is_prefix` and
+`is_numeric`, but the second rule is more succint and easier to understand.
+
+## Testing
+
+To verify a policy works correct, the
+[built-in Sentinel test framework](#)
+uses rules as the point where you can assert behavior. You say that
+you expect certain rules to be true or false. By doing so, you ensure that
+the policy results in the value you expect using the rule flow that you
+expect.
+
+Therefore, in addition to readability, we recommend splitting policies into
+rules to aid testability.
+
+## Lazy and Memoized
+
+A rule is _lazy_ and _memoized_.
+
+_Lazy_ means that the rule is only evaluated when it is actually required,
+not at the point that it is created. And _memoized_ means that the value is
+only computed once and then saved. Once a rule is evaluated, the result of it
+is reused anytime the rule is referenced again.
+
+Both of these properties have important implications for Sentinel
+policies:
+
+**Performance:** Rules are a way to improve the performance of a Sentinel
+policy. If you have a boolean expression that is reused a lot, a rule will
+only be evaluated once. In the example shown above, `is_weekend` is used
+multiple times, but will only have to be evaluated once.
+
+**Behavior:** Rules accessing variables see the value of those variables
+at _the time they are evaluated_. This can lead to surprising behavior
+in some cases. For example:
+
+```sentinel
+a = 1
+b = rule { a == 1 }
+a = 2
+main = b
+```
+
+In this example, `main` will actually result to `false`. It is evaluated
+when it is needed, which is when the policy executes `main`. At this point,
+`a` is now 2 and `b` has not been evaluated yet. Therefore, `b` becomes `false`.
+All future references to `b` will return `false` since a rule is memoized.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/language/scope.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/language/scope.mdx
new file mode 100644
index 0000000000..2fb562a75d
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/language/scope.mdx
@@ -0,0 +1,42 @@
+---
+page_title: Sentinel Language - Scope
+sidebar_title: Scope
+sidebar_current: docs-language-scope
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Scope
+
+Scope is the context in which variables are created and accessed. If a
+variable exists in a parent scope, it is accessed and can be modified.
+Otherwise, the variable is created in your current scope.
+
+Scopes are created with _blocks_. A block is a possibly empty sequence
+of statements within matching brace brackets `{}`. You may nest blocks
+to create nested scopes.
+
+In addition to explicitly created blocks, there are implicit blocks:
+
+1. The policy block encapsulates an entire policy.
+2. Each `any`, `all`, and `for` statement is considered to be in
+ its own block. Note that `if` statements _do not_ create their
+ own block.
+
+The example policy below shows various effects of scopes:
+
+```sentinel
+a = 1
+print(a) // 1
+
+f = func() {
+ print(a) // 1
+ a = 12
+ b = 1
+ return undefined
+}
+f()
+
+print(a) // 12
+print(b) // undefined
+```
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/language/slices.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/language/slices.mdx
new file mode 100644
index 0000000000..d8faa5cece
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/language/slices.mdx
@@ -0,0 +1,33 @@
+---
+page_title: Sentinel Language - Slices
+sidebar_title: Slices
+sidebar_current: docs-language-slices
+description: >-
+ Slices are an efficient way to create a substring or sublist from an existing
+ string or list, respectively.
+layout: docs
+---
+
+# Language: Slices
+
+Slices are an efficient way to create a substring or sublist from an
+existing string or list, respectively.
+
+The syntax for creating a slice is:
+
+```sentinel
+a[low : high]
+```
+
+`low` is the lowest index to slice from. The resulting slice includes
+the value at `low`. `high` is the index to slice to. The resulting slice
+will not include the value at `high`. The length of the resulting list
+or string is always `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+
+a = "hello"
+b = a[1:4] // "ell"
+```
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/language/spec.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/language/spec.mdx
new file mode 100644
index 0000000000..e7ba6ee485
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/language/spec.mdx
@@ -0,0 +1,1273 @@
+---
+page_title: Sentinel Language Specification
+sidebar_title: Specification
+sidebar_current: docs-language-spec
+description: This is the specification for the Sentinel policy language.
+layout: docs
+---
+
+# Sentinel Language Specification
+
+This is the specification for the Sentinel policy language.
+
+The Sentinel language is designed with policy enforcement in mind. It is dynamically typed and garbage collected and has explicit support for _rule_ construction representing boolean logic.
+
+The language is designed to be easy to learn and use by non-programmers. It is expected to be embedded within applications.
+
+## Table of Contents
+
+
+
+
+* [Source code representation](#source-code-representation)
+* [Declarations and Scope](#declarations-and-scope)
+* [Blocks](#blocks)
+* [Lexical Elements](#lexical-elements)
+ * [Comments](#comments)
+ * [Identifiers](#identifiers)
+ * [Keywords](#keywords)
+ * [Operators and Delimiters](#operators-and-delimiters)
+ * [Integer Literals](#integer-literals)
+ * [Floating-point Literals](#floating-point-literals)
+ * [String Literals](#string-literals)
+ * [Implicit Line Joining](#implicit-line-joining)
+ * [Whitespace](#whitespace)
+ * [Semicolons](#semicolons)
+* [Variables](#variables)
+* [Undefined](#undefined)
+* [Expressions](#expressions)
+ * [Operand](#operand)
+ * [Primary Expressions](#primary-expressions)
+ * [Null](#null)
+ * [Booleans](#booleans)
+ * [Boolean Literals](#boolean-literals)
+ * [Boolean Expressions](#boolean-expressions)
+ * [List Literals](#list-literals)
+ * [Map Literals](#map-literals)
+ * [Function Literals](#function-literals)
+ * [Rule Expressions](#rule-expressions)
+ * [Index Expressions](#index-expressions)
+ * [Selectors](#selectors)
+ * [Slice Expressions](#slice-expressions)
+ * [Calls](#calls)
+ * [Operators](#operators)
+ * [Operator Precedence](#operator-precedence)
+ * [Arithmetic Operators](#arithmetic-operators)
+ * [Integer operators](#integer-operators)
+ * [Integer overflow](#integer-overflow)
+ * [Floating-point operators](#floating-point-operators)
+ * [String Concatenation](#string-concatenation)
+ * [List Concatenation](#list-concatenation)
+ * [Comparison Operators](#comparison-operators)
+ * [Logical Operators](#logical-operators)
+ * [Set Operators](#set-operators)
+ * [Matches Operator](#matches-operator)
+ * [Else Operator](#else-operator)
+ * [Any, All Expressions](#any-all-expressions)
+* [Statements](#statements)
+ * [Expression Statements](#expression-statements)
+ * [Assignments](#assignments)
+ * [If Statements](#if-statements)
+ * [Case Statements](#case-statements)
+ * [For Statements](#for-statements)
+ * [Break Statements](#break-statements)
+ * [Continue Statements](#continue-statements)
+ * [Return Statements](#return-statements)
+* [Imports](#imports)
+* [Parameters](#parameters)
+* [Built-in Functions](#built-in-functions)
+ * [Length](#length)
+ * [Collections](#collections)
+ * [List Append](#list-append)
+ * [Map Delete](#map-delete)
+ * [Keys and Values](#keys-and-values)
+ * [Range](#range)
+ * [Type Conversion](#type-conversion)
+ * [Printing](#printing)
+ * [Errors](#errors)
+* [Program Execution](#program-execution)
+
+
+
+## Source code representation
+
+Source code is Unicode text encoded in UTF-8. A "character" referenced in this document refers to a Unicode code point. Each code point is distinct: upper and lower case letters are different characters.
+
+The underscore character \_ (U+005F) is considered a "letter".
+
+```ebnf
+letter = unicode_letter | "_" .
+decimal_digit = "0" … "9" .
+octal_digit = "0" … "7" .
+hex_digit = "0" … "9" | "A" … "F" | "a" … "f" .
+```
+
+## Declarations and Scope
+
+A declaration binds an identifier to a value. An identifier is declared at the point it is first assigned. An assignment is considered a first assignment if the identifier hasn't already been previously declared in the current scope or any parent scopes.
+
+The scope of an identifier is the extent of source text in which the identifier denotes the specified value. Sentinel is lexically scoped using blocks.
+
+An identifier declared in a block may be redeclared in an inner block. While the identifier of the inner declaration is in scope, it denotes the value assigned in the inner declaration.
+
+## Blocks
+
+A block is a possibly empty sequence of statements within matching brace brackets.
+
+```ebnf
+Block = "{" StatementList "}" .
+StatementList = { Statement ";" } .
+```
+
+Blocks nest and affect scoping.
+
+In addition to explicit blocks in the source code, there are implicit blocks:
+
+1. The universe block encompasses all source text.
+2. Each program has a program block containing all source text for that program.
+3. Each "any", "all", and "for" statements is considered to be in its own implicit block. "if" does not create an implicit block.
+
+## Lexical Elements
+
+### Comments
+
+Comments are sections of source text used for documentation.
+
+```plaintext
+# Single line comment
+// Single line comment
+/* multi
+line
+ comment */
+```
+
+Three forms of comments are supported: two single-line forms and one multi-line form. A single-line comment begins with the token `//` or `#`. Everything between the starting token and the end of the line is ignored.
+
+A multi-line comment begins with the token `/*` and ends with the token `*/`. Everything between `/*` and `*/` is ignored.
+
+A comment may not start inside a string literal or inside another comment.
+
+A multi-line comment containing no newlines acts like a space.
+
+### Identifiers
+
+Identifiers name program entities such as rules, variables, and functions. An identifier is a sequence of one or more letters and digits. The first character in an identifier must be a letter.
+
+```ebnf
+identifier = letter { letter | unicode_digit } .
+```
+
+```sentinel
+a
+_a
+_A
+αβ
+```
+
+### Keywords
+
+The following keywords are reserved and may not be used as identifiers:
+
+```plaintext
+all
+any
+as
+for
+func
+if
+import
+else
+null
+return
+rule
+undefined
+when
+```
+
+### Operators and Delimiters
+
+The following character sequences represent operators, delimiters, and other special tokens:
+
+```plaintext
++
+-
+*
+/
+%
++=
+-=
+*=
+/=
+%=
+==
+<
+>
+=
+!
+!=
+<=
+>=
+( )
+[ ]
+{ }
+,
+.
+:
+;
+and
+or
+contains
+in
+is
+matches
+not
+xor
+else
+```
+
+### Integer Literals
+
+An integer literal is a sequence of digits representing an integer constant. An optional prefix sets a non-decimal base: `0` for octal, `0x` or `0X` for hexadecimal. In hexadecimal literals, letters `a-f` and `A-F` represents values 10 through 15.
+
+Integers are signed 64-bit values (-9223372036854775808 to 9223372036854775807).
+
+```ebnf
+int_lit = decimal_lit | octal_lit | hex_lit .
+decimal_lit = ( "1" … "9" ) { decimal_digit } .
+octal_lit = "0" { octal_digit } .
+hex_lit = "0" ( "x" | "X" ) hex_digit { hex_digit } .
+```
+
+```
+42
+0600
+0xBadFace
+170141183460469231731687303715884105727
+```
+
+### Floating-point Literals
+
+A floating-point literal is a decimal representation of a floating-point constant. It has an integer part, a decimal point, a fractional part, and an exponent part. The integer and fractional part comprise decimal digits; the exponent part is an e or E followed by an optionally signed decimal exponent. One of the integer part or the fractional part may be elided; one of the decimal point or the exponent may be elided.
+
+Floating-point numbers are IEEE-754 64-bit floating numbers.
+
+```ebnf
+float_lit = decimals "." [ decimals ] [ exponent ] |
+ decimals exponent |
+ "." decimals [ exponent ] .
+decimals = decimal_digit { decimal_digit } .
+exponent = ( "e" | "E" ) [ "+" | "-" ] decimals .
+```
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+### String Literals
+
+String literals are character sequences between double quotes, as in "bar". Within the quotes, any character may appear except newline and unescaped double quote. The text between the quotes forms the value of the literal.
+
+A multi-character sequence beginning with a backslash encode values in various formats.
+
+Backslash escapes allow arbitrary values to be encoded as ASCII text. There are four ways to represent the integer value as a numeric constant: `\x` followed by exactly two hexadecimal digits; `\u` followed by exactly four hexadecimal digits; `\U` followed by exactly eight hexadecimal digits, and a plain backslash `\` followed by exactly three octal digits. In each case the value of the literal is the value represented by the digits in the corresponding base.
+
+After a backslash, certain single-character escapes represent special values:
+
+```plaintext
+\a U+0007 alert or bell
+\b U+0008 backspace
+\f U+000C form feed
+\n U+000A line feed or newline
+\r U+000D carriage return
+\t U+0009 horizontal tab
+\v U+000b vertical tab
+\\ U+005c backslash
+\" U+0022 double quote
+```
+
+The three-digit octal (\nnn) and two-digit hexadecimal (\xnn) escapes represent individual bytes of the resulting string; all other escapes represent the (possibly multi-byte) UTF-8 encoding of individual characters. Thus inside a string literal \377 and \xFF represent a single byte of value 0xFF=255, while ÿ, \u00FF, \U000000FF and \xc3\xbf represent the two bytes 0xc3 0xbf of the UTF-8 encoding of character U+00FF.
+
+A string value is a (possibly empty) sequence of bytes. Strings are immutable: once created, it is impossible to change the contents of a string.
+
+The length of a string s (its size in bytes) can be discovered using the built-in function `length`. An individual character (of type string) can be accessed by integer indices 0 through `length(s)-1`.
+
+```ebnf
+string_lit = `"` { unicode_value | byte_value } `"` .
+unicode_value = unicode_char | little_u_value | big_u_value | escaped_char .
+byte_value = octal_byte_value | hex_byte_value .
+octal_byte_value = `\` octal_digit octal_digit octal_digit .
+hex_byte_value = `\` "x" hex_digit hex_digit .
+little_u_value = `\` "u" hex_digit hex_digit hex_digit hex_digit .
+big_u_value = `\` "U" hex_digit hex_digit hex_digit hex_digit
+ hex_digit hex_digit hex_digit hex_digit .
+escaped_char = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | `"` ) .
+```
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+### Implicit Line Joining
+
+Expressions can be split over more than one line. For example:
+
+```sentinel
+a or
+b or # This is a comment
+c
+```
+
+Implicitly continued lines can have trailing comments. Blank continued lines are allowed.
+
+### Whitespace
+
+Whitespace is needed to separate tokens, but no distinction is made between the number and combination of whitespace characters. Whitespace characters can be space (U+0020), horizontal tabs (U+0009), carriage returns (U+000D), and newlines (U+000A).
+
+```sentinel
+a or b
+
+a or b
+
+a or
+ b
+
+rule { a or b }
+
+rule {
+ a or b }
+
+rule {
+ a or
+ b
+}
+```
+
+### Semicolons
+
+The formal grammar uses semicolons ";" as terminators in a number of productions.
+It is idiomatic Sentinel source to omit semicolons in most cases.
+
+A semicolon is automatically inserted into the token stream immediately after
+a line's final token if that token is:
+
+* An identifier
+* An integer, float, or string literal
+* The keyword `break`, `continue`, or `return`
+* The delimiter `)`, `]`, or `}`
+
+## Variables
+
+A variable is a storage location for holding a value.
+
+A variable has a dynamic type, which is the concrete type of the value assigned to the variable at run time. The dynamic type may vary during execution and change as a result of further assignments.
+
+A variable's value is retrieved by referring to the variable in an expression; it is the most recent value assigned to the variable. If a variable has not yet been declared (initially assigned), it is an error.
+
+```sentinel
+x = 7 // x is type int
+x = "foo" // x is now type string
+
+x = y // error if y is not previously assigned
+```
+
+## Undefined
+
+The value denoted by the keyword `undefined` represents undefined behavior or values. It can be created directly using the keyword `undefined`. It is also returned as a result of expressions in specified cases.
+
+`undefined` is a valid operand for any operations. Only `undefined or true` will result in true. All other operations result in `undefined`. An exception is if `undefined` is not reached as a result of short-circuit operations.
+
+```sentinel
+undefined OR true = true
+undefined OR false = undefined
+undefined OR undefined = undefined
+undefined AND true = undefined
+undefined AND false = undefined
+undefined AND undefined = undefined
+undefined XOR true = undefined
+undefined XOR false = undefined
+undefined XOR undefined = undefined
+
+// Short-circuit examples
+false OR true OR undefined = true
+false OR undefined OR true = true
+true AND false AND undefined = false
+true AND undefined AND false = undefined
+
+// Non-logical operators
+undefined + 5 = undefined
+-undefined = undefined
+!undefined = undefined
+```
+
+If the result of the main rule is `undefined`, it is treated as `false` but is indicative of erroneous logic.
+
+## Expressions
+
+### Operand
+
+Operands denote the elementary values in an expression. An operand may be a literal, an identifier denoting a variable, rule, or function, or a parenthesized expression.
+
+```ebnf
+Operand = Literal | OperandName | MethodExpr | "(" Expression ")" .
+Literal = BasicLit | CompositeLit | FunctionLit | RuleLit .
+BasicLit = int_lit | float_lit | string_lit .
+OperandName = identifier | QualifiedIdent.
+```
+
+### Primary Expressions
+
+Primary expressions are the operands for unary and binary expressions.
+
+```ebnf
+PrimaryExpr =
+ Operand |
+ PrimaryExpr Selector |
+ PrimaryExpr Index |
+ PrimaryExpr Slice |
+ PrimaryExpr Arguments .
+
+Selector = "." identifier .
+Index = "[" Expression "]" .
+Slice = "[" [ Expression ] ":" [ Expression ] "]" |
+ "[" [ Expression ] ":" Expression ":" Expression "]" .
+Arguments = "(" [ ( ExpressionList | Type [ "," ExpressionList ] ) [ "..." ] [ "," ] ] ")" .
+```
+
+```sentinel
+x
+2
+(s + ".txt")
+f(3.1415, true)
+m["foo"]
+s[i : j + 1]
+obj.color
+f.p[i].x()
+```
+
+### Null
+
+The reserved word `null` denote the singleton value `null`. Null represents the explicit absense of a value. Behavior of `null` within expressions is specified explicitly for each expression.
+
+### Booleans
+
+### Boolean Literals
+
+The reserved words `true` and `false` denote objects that represent the boolean values `true` and `false`, respectively. These are boolean literals.
+
+```ebnf
+BoolLit = "true" | "false" .
+```
+
+#### Boolean Expressions
+
+Boolean expressions are expressions that must result in a boolean value. Any other value type becomes the `undefined` value.
+
+```ebnf
+BoolExpr = Expression .
+```
+
+### List Literals
+
+A list literal denotes a list, which is an integer indexed collection of values.
+
+```ebnf
+ListLit = "[" [ ElementList [ "," ] ] "]" .
+ElementList = Element { "," Element } .
+Element = Expression | LiteralValue .
+```
+
+A list may contain zero or more values. The number of values in a list is its length.
+
+A list has a set of indices. An empty list has an empty set of indices. A non-empty list has the index set `{0...n - 1}` where `n` is the length of the list. Attempting to access a list using an index that is not a member of its set of indices results in the `undefined` value.
+
+### Map Literals
+
+A map literal denotes a map, which is an unordered group of elements indexed by a set of unique keys.
+
+```ebnf
+MapLit = "{" [ KeyedElementList [ "," ] ] "}" .
+KeyedElementList = KeyedElement { "," KeyedElement } .
+KeyedElement = Element ":" Element .
+```
+
+Keys can only be a boolean, numeric, or string type.
+
+The value of a non-existent key is the `undefined` value.
+
+### Function Literals
+
+A function literal represents a function.
+
+```ebnf
+FunctionLit = "func" Function .
+Function = Parameters FunctionBody .
+FunctionBody = Block .
+Parameters = "(" [ IdentifierList [ "," ] ] ")" .
+IdentifierList = identifier { "," identifier } .
+```
+
+```sentinel
+func(a, b) { return b }
+```
+
+Function literals are only allowed in the file scope. They may refer to variables defined in surrounding blocks.
+
+A function must terminate with a return statement. If a function has no meaningful return value, it should return `undefined`.
+
+### Rule Expressions
+
+A rule is a boolean expression that is evaluated lazily and the result is memoized.
+
+If the optional "when" predicate is present, the rule is evaluated only when
+the "when" boolean expression results in true. If the predicate is false,
+the rule is not evaluated and returns true. The predicate is evaluated when
+the rule would be evaluated; it is also lazy and memoized in the same way.
+
+```ebnf
+RuleExpr = "rule" [ "when" BoolExpr ] "{" BoolExpr "}" .
+```
+
+```sentinel
+rule { x is y }
+
+rule {
+ x == "value" or
+ y == "other"
+}
+
+rule when x is y { y > 42 }
+```
+
+### Index Expressions
+
+A primary expression of the form `a[x]` denotes the element of a list, map, or string indexed by x. The value x is called the index or key.
+
+For `a` of type map:
+
+* If `a` contains a key `x`, `a[x]` is the map value with key `x`.
+* If `a` does not contain key `x`, `a[x]` is the `undefined` value.
+
+For `a` of type list:
+
+* `x` must be an integer
+* `x` must be in the range `[-1 * length(a), length(a)-1]`
+* If `x` is contained in the set of indices of `a`, `a[x]` is the list element at index `x`. If `x` is negative, `a[x]` is equivalent to `a[length(a)+x]`.
+* If `x` is not contained in the set of indices of `a`, `a[x]` is the `undefined` value.
+
+For `a` of type string:
+
+* `x` must be an integer
+* `x` must be in the range `[-1 * length(a), length(a)-1]`
+* If `x` is in range, `a[x]` is the byte value at index `x` as a string. If `x` is negative, `a[x]` is equivalent to `a[length(a)+x]`.
+* If `x` is out of range, `a[x]` is the `undefined` value.
+
+For `a` of value `null`:
+
+* `a[x]` is the `undefined` value.
+
+Otherwise `a[x]` is an error.
+
+### Selectors
+
+For a primary expression x, the selector expression `x.f` denotes the field `f` of the value `x`. The identifier `f` is called the selector. The type of the selector expression is the type of the selector.
+
+As a special case, selectors can be [reserved words](#keywords) and keyword [operators](#operators-and-delimiters), but cannot be any other non-identifier element.
+
+Selectors are used to access data from [imports](#imports). The first primary expression `x` in `x.f` denotes the import name. The field `f` is the selector to access data from the import.
+
+Selectors may also be used to access map data with static keys. They are syntactic sugar over index expressions. `math.pi` is equivalent to `math["pi"]` and exhibit the same limitations and behavior as an index expression. Selectors cannot, however, be used to assign values to map data.
+
+Selectors on undefined result in undefined.
+
+```sentinel
+math.pi
+time.pst.hour
+```
+
+### Slice Expressions
+
+Slice expressions construct a substring or list from a string or list.
+
+The primary expression
+
+```sentinel
+a[low : high]
+```
+
+constructs a substring or list. The indices `low` and `high` select which elements of operand `a` appear in the result. The result has indices starting at 0 and length equal to `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+```
+
+For convenience, any of the indices may be omitted. A missing `low` index defaults to zero; a missing `high` index defaults to the length of the sliced operand:
+
+```sentinel
+a[2:] // same as a[2 : length(a)]
+a[:3] // same as a[0 : 3]
+a[:] // same as a[0 : length(a)]
+```
+
+The indices are in range if `0 <= low <= high <= length(a)`, otherwise they are out of range. If the indices are out of range at run time, the result is the `undefined` value.
+
+If `a` is the value `null`, the result is the `undefined` value.
+
+If `a` is any other value type, it is an error.
+
+### Calls
+
+Given an expression `f` where `f` is a function value:
+
+```sentinel
+f(a1, a2, … an)
+```
+
+calls `f` with arguments `a1, a2, ... an`. The type of the expression is the result of `f`. The arguments are evaluated left to right. Arguments are passed by value.
+
+### Operators
+
+```ebnf
+Expression = UnaryExpr | Expression binary_op Expression .
+UnaryExpr = PrimaryExpr | unary_op UnaryExpr .
+
+binary_op = logical_op | set_op | rel_op | add_op | mul_op | else_op .
+logical_op = "and" | "or" | "xor" .
+set_op = ["not"] ( "contains" | "in" ).
+rel_op = "==" | "!=" | "<" | "<=" | ">" | ">=" |
+ "is" | "is not" | "matches" | "not matches" .
+add_op = "+" | "-" .
+mul_op = "*" | "/" | "%" .
+else_op = "else" .
+unary_op = "+" | "-" | "!" | "not" .
+```
+
+#### Operator Precedence
+
+Unary operators have the highest precedence.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Binary operators of the same precedence associate from left to right. For instance, x / y _ z is the same as (x / y) _ z.
+
+### Arithmetic Operators
+
+Arithmetic operators apply to numeric values and yield a result of the same type when both operands are the same.
+
+Arithmetic operations between integer and floating-point types result in a floating-point value with the integer operand treated as a floating-point value for purposes of calculation.
+
+Arithmetic operations between numeric values (integer, floating-point) and non-numeric values (strings, lists) are not permitted.
+
+All five supported arithmetic operators (+, -, \*, /, %) apply to both integer and floating-point types; + also applies to strings.
+
+```plaintext
++ sum integers, floats, strings, lists
+* difference integers, floats
+* product integers, floats
+/ quotient integers, floats
+% remainder integers, floats
+```
+
+#### Integer operators
+
+For two integer values x and y, the integer quotient `q = x / y` and remainder `r = x % y` satisfy the following relationships:
+
+```sentinel
+x = q*y + r and |r| < |y|
+```
+
+with x / y truncated towards zero.
+
+```plaintext
+ x y x / y x % y
+ 5 3 1 2
+-5 3 -1 -2
+ 5 -3 -1 2
+-5 -3 1 -2
+```
+
+As an exception to this rule, if the dividend x is the most negative value for the int type of x (-9223372036854775808), the quotient `q = x / -1` is equal to x (and r = 0).
+
+If the divisor is a constant, it must not be zero. If the divisor is zero at run time, an error occurs.
+
+#### Integer overflow
+
+For signed integers, the operations `+`, `-`, and `*` may legally overflow and the resulting value exists and is deterministically defined by the signed integer representation, the operation, and its operands. No exception is raised as a result of overflow.
+
+#### Floating-point operators
+
+For floating-point numbers, +x is the same as x, while -x is the negation of x. The result of a floating-point division by zero is not specified beyond the IEEE-754 standard.
+
+#### String Concatenation
+
+Strings can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = "hi"
+y = "hello"
+x = x + ", " + y // "hi, hello"
+x += " and good bye" // "hi, hello and good bye"
+```
+
+String addition creates a new string by concatenating the operands.
+
+#### List Concatenation
+
+Lists can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = [1, 2]
+x = x + [2, 3] // [1, 2, 2, 3]
+x += [4] // [1, 2, 2, 3, 4]
+```
+
+List addition creates a new list by concatenating the operands.
+
+### Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+In any comparison, the two operands must be equivalent types. The only exception are integers and floats, which are considered numeric and may be compared. Any comparison between other non-matching types results in the `undefined` value.
+
+The equality operators `==`, `!=`, `is`, and `is not` apply to operands that are comparable. The ordering operators `<`, `<=`, `>`, and `>=` apply to operands that are ordered. The behavior of `is` with `==` and `is not` with `!=` is identical. These terms and the result of the comparisons are defined as follows:
+
+* Boolean values are comparable. Two boolean values are equal if they are either both true or both false.
+* Integer values are comparable and ordered, in the usual way.
+* Floating point values are comparable and ordered, as defined by the IEEE-754 standard.
+* An integer compared with floating point value treats the integer as the converted floating point value.
+* String values are comparable and ordered, lexically byte-wise.
+* Lists are comparable. Lists are equal if they are of equal length and their
+ corresponding elements are comparable and equal.
+
+Maps are not comparable. As a special case, maps may be compared to the `null` value for equality or inequality.
+
+If either operand is the `undefined` value, the result of the expression is the `undefined` value.
+
+### Logical Operators
+
+Logical operators apply to boolean values and yield a boolean result.
+
+Logical operators are evaluated left-to-right and perform short-circuit logic. The right-hand side is not guaranteed to be evaluated if a short-circuit can be performed.
+
+```plaintext
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+### Set Operators
+
+The set operators `contains` and `in` test for set inclusion for lists
+and maps, and substring inclusion for strings.
+
+Set operators may be negated by prefixing the operator with `not`: `not contains` and `not in`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+`contains` tests if the left-hand collection contains the right-hand value. For lists, this tests equality of any value. For maps, this tests equality of any key. For strings, this tests for substring existence.
+
+`in` tests if the right-hand collection contains the left-hand value. The behavior is equivalent to `contains`.
+
+The collection must be a list, map, or string. If it is the `undefined` value, the result is the `undefined` value. For any other value, the result is an error.
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+
+"test" contains "est" // true
+"test" contains "best" // false
+"test" in "testing" // true
+"best" in "testing" // false
+```
+
+### Matches Operator
+
+The matches operator tests if a string matches a regular expression.
+
+The matches operators may be negated by prefixing the operator with `not`: `not matches`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+The left-hand value is the string to test. The right-hand value is a string value representing a regular expression.
+
+The syntax of the regular expression is the same general syntax used by Python, Ruby, and other languages. More precisely, it is the syntax accepted by RE2. The regular expression is not anchored by default; any anchoring must be explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+If either operand is `undefined`, the result is the `undefined` value. For any other non-string value, the result is an error.
+
+### Else Operator
+
+Else operators can be used to provide a default value for an expression that may evaluate to the `undefined` value. Else operators return their left-hand value unless it is `undefined`, otherwise they return their right-hand value.
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else null
+```
+
+### Any, All Expressions
+
+`any` and `all` expressions are existential and universal quantifiers, respectively. `any` expressions are equivalent to a chain of `or` and `all` expressions are equivalent to a chain of `and`. Both expressions implement short-circuiting equivalent to logical operators.
+
+The body of `any` and `all` expressions is a boolean expression.
+
+`any` returns the boolean value `true` if any value in the collection expression results in the body expression evaluating to `true`. If the body expression evalutes to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression result in the body expression evaluating to `true`. If any value in the collection expression result in the body expression evaluating to `false`, the all expression returns `false`.
+
+For empty collections, `any` returns `false` and `all` returns `true`.
+
+When the collection is a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When the collection is a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+QuantExpr = QuantOp Expression "as" [ identifier "," ] identifier "{" BoolExpr "}" .
+QuantOp = "all" | "any" .
+```
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" }
+any group.tasks as t { t.driver is "vmware" }
+```
+
+## Statements
+
+### Expression Statements
+
+Function call expressions may exist in the statement context.
+
+```ebnf
+ExpressionStmt = Expression .
+```
+
+### Assignments
+
+Assignments set the value of an expression to the value of another expression.
+
+```ebnf
+Assignment = AssignExpr assign_op Expression .
+AssignExpr = identifier | IndexExpr .
+assign_op = [ add_op | mul_op ] "=" .
+```
+
+The assignment `x op= y` is equivalent to `x = x op (y)` for supported values of `op`.
+
+```sentinel
+a = 1
+b[12] = 42
+c["key"] = "value"
+
+a *= 12
+b[12] += 8
+```
+
+Assignments to lists and maps can be carried out using [index expressions](#index-expressions), with the operands of the index expression being evaluated alongside the right hand side expression in a right-to-left (RHS, LHS) order.
+
+In addition to the rules detailed in the section describing their use, the following rules apply when assigning to maps or lists using index expressions:
+
+* The [variable](#variables) must exist and be a list or map. Attempts to use an index assignment on an unknown variable, or a variable that not a list or map, results in a runtime error.
+* Assigning to a list or map index that already exists overwrites that index's data with evaluated right hand side's value.
+* Assigning to an unknown key in a map creates a key for that map with the evaluated right hand side's value assigned to that key.
+* Attempting to assign a value to a list index that is out of range results in a runtime error.
+
+### If Statements
+
+If statements specify the conditional execution of two branches according to the value of a boolean expression. If the expression evaluates to true, the "if" branch is executed, otherwise, if present, the "else" branch is executed.
+
+```ebnf
+IfStmt = "if" BoolExpr Block [ "else" ( IfStmt | Block ) ] .
+```
+
+```sentinel
+if x < y {
+ return x
+} else if x > z {
+ return z
+} else {
+ return y
+}
+```
+
+### Case Statements
+
+Case statements specify a selection control mechanism to conditionally execute a branch based on expression equality.
+
+Each clause that wishes to provide an expression must use the "when" keyword. Multiple expressions can be provided, separated with a comma (",").
+
+There can be one, optional, "else" clause within a `case` statement. The "else" clause is executed if all other clauses failed to run.
+
+The clauses are evaluated in the order they are listed, with the first successful evaluation being executed.
+
+A `case` statement can optionally provide an expression. If no expression is provided, it is the equivalent of writing `case true {`.
+
+```ebnf
+CaseStmt = "case" [ Expression ] "{" { CaseWhenClause } "}" .
+CaseWhenClause = CaseWhenCase ":" StatementList .
+CaseWhenCase = "when" Expression { "," Expression } | "else" .
+```
+
+```sentinel
+case x {
+when y, z:
+ return true
+else:
+ return false
+}
+
+case {
+when x > 42:
+ return true
+else:
+ return false
+}
+```
+
+### For Statements
+
+A `for` statement specifies repeated execution of a block.
+
+The expression must evaluate to a list or a map.
+
+When iterating over a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When iterating over a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+ForStmt = "for" Expressions "as" [ identifier "," ] identifier Block .
+```
+
+```sentinel
+for [1, 2, 3] as v { count += v } // basic sum
+
+for [1, 2, 3] as idx, v {
+ if idx > 1 { count += v }
+}
+
+data = { "a": 12, "b": 32 }
+for data as k { count += data[k] } // sum map values
+for data as k, v { count += v }
+```
+
+### Break Statements
+
+A `break` statement terminates execution of the innermost `for` statement
+within the same function.
+
+```ebnf
+BreakStmt = "break" .
+```
+
+```sentinel
+// Will only print "1"
+for [1, 2, 3] as v {
+ print(v)
+ break
+}
+```
+
+### Continue Statements
+
+A `continue` statement begins the next iteration of the innermost `for` loop.
+The `for` loop must be within the same function.
+
+```ebnf
+ContinueStmt = "continue" .
+```
+
+```sentinel
+// Will print 1, 3
+for [1, 2, 3] as v {
+ if v == 2 {
+ continue
+ }
+
+ print(v)
+ break
+}
+```
+
+### Return Statements
+
+A return statement in a function F terminates the execution of F and provides the result value.
+
+```ebnf
+ReturnStmt = "return" Expression .
+```
+
+A function must terminate with a return statement.
+
+The return statement can be invoked at any time during a function to terminate execution.
+
+## Imports
+
+An import adds an assignment to the global scope to external definitions.
+
+The import declarations must only appear at the top of the source file, before any other statements. Comments may appear before imports.
+
+```ebnf
+ImportDecl = "import" Name ["as" identifier] .
+Name = string_lit .
+```
+
+An import name and identifier must be distinct. Two names may not repeated even if they're declared with different identifiers.
+
+If an identifier is not specified, the identifier is the name of the import itself.
+
+An import is not a valid value. The identifier representing the import may not be used as an expression, such as in assignment statements or call expressions.
+
+Values in imports are not assignable directly via their selectors. Function calls may be used to alter the state of the external data represented by the import. Whether or not this is supported is specific to the import implementation. The exception to this assignment restriction is the memoized data returned by a call to an import (see below).
+
+Data returned by imports can be memoized, meaning that data is returned by the import in the form of a map which is then used as much as possible without having to return to the import for more data. For example, `t = time.now` returns a map so that `t.hour` will be able to be looked up without having to go back to the import for that data.
+
+Data returned by imports may be callable, meaning that methods may be called on the memoized data returned by an import. For example, given `t = time.now`, `t.after(some_previous_time)` is valid, with the receiver data being set to the local memoized data stored in `t`. It is a valid case to accept modifications to the receiver data (example: assignment to the memoized data via index expressions), as long as all data in in the receiver continues to be string-keyed. It is an invalid case to accept receiver data with non-string-typed keys. Methods may also alter the contents of receiver data so that it is different after the call is finished.
+
+As with methods, imports may also have callable keys where the data may not be memoized. Example: in the event of `v = foo.bar`, if `v.baz` is not included in the memoized data, a call will be made to the import to fetch it. The same rules and restrictions to receiver data apply to callable keys as do to method calls.
+
+The support for callable methods and non-memoized keys on data returned by imports is specific to the import implementation.
+
+The handling of import loading is specific to the runtime implementation.
+
+Implicit imports may be added by the runtime, for example for standard library definitions. An explicit import of the same name should override a matching implicit import. For example, if "time" is an implicit import and the program specifies `import "time" as cal`, then only `cal` is defined.
+
+```sentinel
+import "time"
+import "math" as m
+```
+
+## Parameters
+
+```ebnf
+ParamDecl = "param" identifier [ "default" Expression ]
+```
+
+A parameter is a way for a policy to describe variables that are expected to be supplied by the calling application, in addition to any default value.
+
+Parameter declarations must appear at the top of the source file, following imports.
+
+Like imports, comments may appear before parameters; this is mainly pertinent when the policy is not importing anything, which would make parameters the first declarations that appear in a policy.
+
+A parameter declaration describes a valid variable that is added to the scope of a policy at runtime. This variable has the same properties and rules as other variables assigned during the course of policy evaluation as defined by the specification. This includes having a dynamic type and being able to be re-assigned during execution.
+
+Parameters may only be strings, integers, floating point numbers, booleans, lists, or maps. More complex types such as rules or functions are not allowed.
+
+A parameter can specify a default value by adding one in the declaration via use of the "default" keyword, and supplying a literal for the default value. The literal for a default is a very limited expression, comprising of:
+
+* For strings, a simple string literal;
+* For integers and floating-point numbers, either a literal denoting the value, or a unary expression with the literal, and the operators - or +;
+* For booleans, the pre-declared identifiers true or false;
+* For lists and maps, their respective literals composed of values and keys (for maps) of the above values only.
+
+Any other type of expression or identifier is not allowed.
+
+A parameter that does not have a default value defined is required by the policy. Evaluating a policy with a required parameter that has not been supplied at execution results in a runtime error.
+
+Parameters must not conflict with imports or any other identifiers currently defined in the global scope, including any reserved or pre-declared identifiers. Attempting to assign a parameter to an existing value results in a runtime error.
+
+```sentinel
+import "time"
+
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+param time default "11:00" // error, conflicts with "time" import
+param undefined // error, reserved identifier
+```
+
+## Built-in Functions
+
+Built-in functions are predeclared. They are called like any other function.
+
+### Length
+
+The built-in function `length` returns the length of a collection of string.
+
+The length of a string is the number of bytes in the string. It is not necessarilly the number of characters.
+
+The length of a collection is the number of elements in that collection.
+
+The length of `undefined` is `undefined`.
+
+### Collections
+
+#### List Append
+
+The built-in function `append` appends a value to the end of a list in-place.
+
+Appending to a non-list value results in an immediate `fail`.
+
+The return value of `append` is always the `undefined` value.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+#### Map Delete
+
+The built-in function `delete` deletes elements from a map by key. The map is modified in-place.
+
+Deleting a key that does not exist does nothing.
+
+Calling delete for a non-map value results in an immediate `fail`.
+
+The return value of `delete` is always the `undefined` value.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
+
+#### Keys and Values
+
+The built-in function `keys` and `values` return the keys and values of a map, respectively. The return value is a list. Both functions return values in an unspecified order.
+
+The `keys` or `values` of `undefined` is `undefined`.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+### Range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it defaults to `1`.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4]
+range(1, 5, 2) // [1,3]
+range(0, -3, -1) // [0,-1,-2]
+```
+
+### Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a value of that type according to the rules below.
+
+For `int`:
+
+* Integer values are unchanged
+* String values are converted according to the syntax of integer literals
+* Float values are rounded down to their nearest integer value
+* Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+* Float values are unchanged
+* Integer values are converted to the nearest equivalent floating point value
+* String values are converted according to the syntax of float literals
+* Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+* String values are unchanged
+* Integer values are converted to the base 10 string representation
+* Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+* Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+* The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+* The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+* Any non-zero integer or float value converts to `true`
+* Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
+
+### Printing
+
+The built-in function `print` can be used to output formatted debug information. The runtime may omit print function calls depending on its execution mode. The runtime may choose where the output of a print function goes.
+
+`print` is a variadic function, accepting one or more values to be printed; values are separated by a single whitespace character (`" "`).
+
+The return value of `print` is always `true` to allow it to be used in boolean expressions.
+
+```sentinel
+print("hello") // "hello"
+print("hello", "world") // "hello world"
+print("The", "number", "is", 42) // "The number is 42"
+print([1, 2, 3]) // "[1, 2, 3]"
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // "false"
+```
+
+### Errors
+
+The built-in function `error` is equivalent to `print` but immediately causes execution to halt and the main rule to evaluate to `false`. The program execution is considered to have failed.
+
+## Program Execution
+
+Program execution begins by evaluating the source top to bottom. If evaluation is successful, the `main` rule is evaluated and its result is returned. Execution can be interrupted at any time by an error, which can be called explicitly or implicitly through illegal or undefined behavior.
+
+---
+
+> The content of this page is licensed under the [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
+>
+> Based on [The Go Programming Language Specification](https://golang.org/ref/spec) licensed under [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/language/undefined.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/language/undefined.mdx
new file mode 100644
index 0000000000..fa8dd50cc4
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/language/undefined.mdx
@@ -0,0 +1,98 @@
+---
+page_title: Sentinel Language - Undefined
+sidebar_title: Undefined
+sidebar_current: docs-language-undefined
+description: >-
+ The value `undefined` is a special value representing undefined behavior or an
+ unknown value. Any operation on an `undefined` value results in `undefined`.
+layout: docs
+---
+
+# Language: Undefined
+
+The value `undefined` is a special value representing undefined behavior
+or an unknown value.
+
+Undefined is not an error because there are situations where the undefined
+value can be safely ignored and the language also provides construts for
+recovering from an undefined value.
+
+The undefined value can be created manually with `undefined`. In most cases,
+undefined is created by accessing a non-existent list element or value.
+The undefine value is created in a number of other circumstances. The documentation
+will note when an undefined value may be created.
+
+Almost any operation with `undefined` results in `undefined`. For example,
+performing math on undefined, attempting to call a function that is undefined,
+etc.
+
+## Main and Undefined
+
+If the value `undefined` is returned from the top-level `main` rule, then
+the policy is considered `false`. However, the runtime provides mechanisms
+for the host application to determine the policy failure was caused by
+an undefined value and report that to the user.
+
+The `main` rule returning the undefined value should be considered a logical
+error in most cases and should probably be corrected by the policy writer.
+
+## Debugging Undefined
+
+When an `undefined` value is first created (either explicitly or implicitly),
+the runtime will record the position where the undefined was created. This
+location can be used to find logic that could be erroneous.
+
+## Boolean Logic and Undefined
+
+Boolean logic is one way to safely recover from `undefined`. If boolean
+logic can safely determine a result despite an undefined value, that result
+will be returned.
+
+For example: `undefined or true` will result in `true`, because no matter
+what actual value `undefined` could've been if the policy behaved properly,
+the boolean expression would still be true.
+
+Another scenario where `undefined` can be safely ignored is short-circuit
+logic with `and`. `false and undefined` will result in `false`.
+
+The full table of logical operations on `undefined` is below:
+
+```sentinel
+undefined or true = true
+undefined or false = undefined
+undefined or undefined = undefined
+undefined and true = undefined
+undefined and false = undefined
+undefined and undefined = undefined
+undefined xor true = undefined
+undefined xor false = undefined
+undefined xor undefined = undefined
+
+// Short-circuit examples
+false or true or undefined = true
+false or undefined or true = true
+true and false and undefined = false
+true and undefined and false = undefined
+```
+
+## Else Expressions
+
+The `else` expression allows explicit recovery from undefined and is useful
+for setting default values.
+
+`else` is an operator where the left and right hand side are values. If the
+left-hand value is `undefined`, the right-hand value is returned. Otherwise,
+the left-hand value is returned.
+
+Examples:
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else "default"
+
+// In more complex scenarios
+
+foo() else 42 == 12
+a = config.value else "default"
+```
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/language/values.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/language/values.mdx
new file mode 100644
index 0000000000..a131ef30dc
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/language/values.mdx
@@ -0,0 +1,195 @@
+---
+page_title: Sentinel Language - Values
+sidebar_title: Values
+sidebar_current: docs-language-values
+description: Values are the data that Sentinel policies operate on.
+layout: docs
+---
+
+# Language: Values
+
+Values are the _data_ that Sentinel policies operate on. You can create this
+data yourself, for example by just typing the number `42`, or you may access
+this data from an external source to make policy decisions.
+
+Values have a _type_, which determines what kind of operatins can be performed
+on the data. Example types are booleans (true and false), numbers,
+and strings (text).
+
+This page documents all the available value types. You can also
+[convert between types](#type-conversion).
+
+## Boolean
+
+A boolean is a value that is either true or false.
+
+A boolean value is created literally with `true` or `false`.
+
+As a policy language, booleans are central to the behavior of Sentinel.
+Booleans are used as conditions in [if statements](/sentinel/language/if),
+are the result of [rules](/sentinel/language/rules), and more. The ultimate
+result of a Sentinel policy is true or false.
+
+## Integer
+
+An integer is a whole number.
+
+An integer is a 64-bit value. This means it can represent numbers from
+-9,223,372,036,854,775,808 to 9,223,372,036,854,775,808.
+
+Integers are created by typing them out literally with no
+separators (such as a comma). An optional prefix can set a non-decimal base:
+`0` for octal, and `0x` for hexadecimal. In hexadecimal literals, letters
+`a-f` and `A-F` represent values 10 to 15.
+
+Example integers are shown below:
+
+```sentinel
+42
+0600
+0xBadFace
+170141183460469
+```
+
+Integers are used for math and numerical comparison.
+
+## Float
+
+A float is a number with a decimal point. It has an integer part, a decimal
+point, a fractional part, and optionally an exponent part. Example
+floats are shown below:
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+Floats are IEEE-754 64-bit floating point numbers.
+
+## String
+
+Strings are text values.
+
+Strings are created by wrapping text in double quotes, such as `"hello"`.
+Within the quotes, any character may appear except newline and an unescaped
+double quote. The text between the quotes is the value.
+
+Because Sentinel policies must be UTF-8 encoded text, strings themselves are
+UTF-8 encoded text. This means you can put any value UTF-8 character into
+a string, such as `"日本語"`.
+
+You can have a string with a literal double-quote by _escaping_ it with
+a backslash. For example: `"they said \"hello\""` turns into the value
+`they said "hello"`.
+
+Backslash escapes can be used for much more than only escaping a double quote.
+Newlines are `\n`, a literal backslash is `\\`, and many more. The full list
+of available backslash escapes can be found
+[in the language specification](/sentinel/language/spec#string-literals).
+
+Example strings:
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+Strings support indexing. Indexing a string will access that _byte_
+in the string as if the string were a byte array. This is an important
+distinction: it _will not_ access the character at that position if you're
+using multi-byte characters.
+
+Strings support [slicing](/sentinel/language/slices) to efficiently create
+substrings.
+
+## List
+
+See [Lists](/sentinel/language/lists).
+
+## Map
+
+See [Maps](/sentinel/language/maps).
+
+## Rule
+
+See [Rules](/sentinel/language/rules).
+
+## Function
+
+See [Functions](/sentinel/language/functions).
+
+## Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a
+value of that type. Some examples are shown below followed by a list of exact
+rules for type conversion.
+
+```sentinel
+int(42) // 42
+int("42") // 42
+int(42.8) // 42
+int(true) // 1
+
+float(1.2) // 1.2
+float(1) // 1.0
+float("4.2") // 4.2
+float(true) // 1.0
+
+string("foo") // "foo"
+string(88) // "88"
+string(0xF) // "15"
+string(true) // "true"
+
+bool("true") // true
+bool(1) // true
+bool(-1) // true
+bool(0.1) // true
+bool("false") // false
+bool(0) // false
+```
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/language/variables.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/language/variables.mdx
new file mode 100644
index 0000000000..69655b0c63
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/language/variables.mdx
@@ -0,0 +1,100 @@
+---
+page_title: Sentinel Language - Variables
+sidebar_title: Variables
+sidebar_current: docs-language-vars
+description: Variables store values that can be used later.
+layout: docs
+---
+
+# Language: Variables
+
+Variables store values that can be used later.
+
+Most notably, a variable assignment of `main` is required for all
+Sentinel policies. This is the main [rule](/sentinel/language/rules) that
+is executed to determine the result of a policy. The `main` rule may
+use other variables that are assigned in the policy.
+
+Example:
+
+```sentinel
+a = 1
+b = a + 1
+```
+
+In this example, two variables are assigned: `a` and `b`. `a` is given
+the value of "1" and `b` uses the `a` to calculate its own value.
+
+## Syntax
+
+The syntax for assigning a variable is:
+
+```sentinel
+IDENTIFIER = VALUE
+```
+
+On the left of the equal sign is an _identifier_. This is a name for your
+variable and will be how you reference it later. An identifier is any
+combination of letters and digits, but must start with a letter. An
+underscore `_` is also a valid letter.
+
+On the right is any valid Sentinel value or expression. A value is a
+literal value such as a number or string. An expression is some computed
+value such as doing math, calling a function, etc.
+
+## Assignment and Reassignment
+
+A variable is _assigned_ when it is given a value. You can also _reassign_
+variables at any time by setting it to a new value. The new value takes
+effect for any subsequent use of that variable. A variable can be reassigned
+to a different type.
+
+For example:
+
+```sentinel
+a = 1 // a = 1
+b = a // b = 1
+a = "value" // a = "value", b = 1
+c = a // c = "value", b = 1
+```
+
+In the above example you can see that the variables are set and reassigned.
+Notice that the value of a variable is the _current_ value, and that reassigning
+a variable only affects _future_ uses of that variable. You can see this with
+`c` and `b` being different values.
+
+## Unassigned Variables
+
+Using a variable that is _unassigned_ is an error.
+
+In the example below, the first line would result in the policy erroring.
+Sentinel is executed top-down and the value of `c` is not available yet
+on the first line.
+
+```sentinel
+a = c // Error!
+c = 1
+```
+
+## Type Conversion
+
+While a topic more related to [values][lang-values], variables can be assigned
+(or-reassigned) a different value type via type conversion.
+
+[lang-values]: /sentinel/language/values
+
+```sentinel
+s = "1.1"
+a = int(s) // a = 1
+a = float(s) // a = 1.1
+
+a = 1
+s = string(a) // s = "1"
+```
+
+For more details, see the type conversion sections in the
+[values][lang-values-type-conversion] page, or the [Sentinel Language
+Specification][lang-spec-type-conversion].
+
+[lang-values-type-conversion]: /sentinel/language/values#type-conversion
+[lang-spec-type-conversion]: /sentinel/language/spec#type-conversion
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/nomad/index.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/nomad/index.mdx
new file mode 100644
index 0000000000..b519c1bf29
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/nomad/index.mdx
@@ -0,0 +1,50 @@
+---
+page_title: Nomad and Sentinel
+sidebar_title: Nomad
+sidebar_current: docs-app-nomad
+description: >-
+ Nomad Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies can currently execute on job
+ submission (creation, update).
+layout: docs
+---
+
+# Nomad
+
+[Nomad Enterprise](https://www.hashicorp.com/products/nomad/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies can currently execute on job submission (creation, update).
+
+Sentinel policies have full access to the job structure. This allows the
+Sentinel policy to control behavior based on any attribute within a job,
+such as the driver, resource requests, network configuration, volume
+configuration, and more. The information that Sentinel policies have access
+to will expand over time.
+
+Nomad fully supports all [enforcement levels](/sentinel/concepts/enforcement-levels).
+For soft mandatory policies, the `sentinel-override` capability must be
+available on the user's ACL policy to allow override. Overrides are always
+logged.
+
+The Nomad integration with Sentinel is documented in depth in the
+[Nomad Enterprise documentation](https://www.nomadproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Only allow Docker-based jobs.**
+
+```sentinel
+# Test policy only allows Docker based tasks
+main = rule { all_drivers_docker }
+
+# all_drivers_docker checks that all the drivers in use are Docker
+all_drivers_docker = rule {
+ all job.task_groups as tg {
+ all tg.tasks as task {
+ task.driver is "docker"
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/terraform/index.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/terraform/index.mdx
new file mode 100644
index 0000000000..4825ad24fe
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/terraform/index.mdx
@@ -0,0 +1,60 @@
+---
+page_title: Terraform and Sentinel
+sidebar_title: Terraform
+sidebar_current: docs-app-terraform
+description: >-
+ Sentinel can be used with Terraform and Terraform Enterprise to enforce policy
+ on Terraform configurations, states, and plans.
+layout: docs
+---
+
+# Terraform
+
+[Terraform Enterprise](https://www.hashicorp.com/products/terraform/) uses Sentinel to enforce
+policy on [Terraform](https://www.terraform.io) configurations, states, and plans.
+
+The Sentinel integration with Terraform runs within
+[Terraform Enterprise](https://www.hashicorp.com/products/terraform/)
+after a `terraform plan` and before a `terraform apply`. The policies
+have access to the created plan, the state at the time of the plan,
+and the configuration at the time of the plan.
+
+The Terraform integration with Sentinel is documented in depth in the [Terraform Enterprise documentation](https://www.terraform.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show basic examples.
+
+### Examples
+
+**Example: All AWS instances must have a tag**
+
+```sentinel
+import "tfplan"
+
+main = rule {
+ all tfplan.resources.aws_instance as _, instances {
+ all instances as _, r {
+ (length(r.applied.tags) else 0) > 0
+ }
+ }
+}
+```
+
+**Example: Only allow GCP instance sizes smaller than `n1-standard-16`**
+
+```sentinel
+import "tfplan"
+
+allowed_machine_types = [
+ "n1-standard-1",
+ "n1-standard-2",
+ "n1-standard-4",
+ "n1-standard-8",
+]
+
+main = rule {
+ all tfplan.resources.google_compute_instance as _, instances {
+ all instances as _, r {
+ r.applied.machine_type in allowed_machine_types
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/vault/index.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/vault/index.mdx
new file mode 100644
index 0000000000..9c37e0eedb
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/vault/index.mdx
@@ -0,0 +1,65 @@
+---
+page_title: Vault and Sentinel
+sidebar_title: Vault
+sidebar_current: docs-app-vault
+description: >-
+ Vault Enterprise uses Sentinel to augment the built-in policy system to
+ provide Role Governing Policies (RGPs) and Endpoint Governing Policies (EGPs)
+ to enable complex, flexible policies across identities and endpoints.
+layout: docs
+---
+
+# Vault
+
+[Vault Enterprise](https://www.hashicorp.com/products/vault/) uses Sentinel
+to augment the built-in policy system to provide Role Governing Policies (RGPs)
+and Endpoint Governing Policies (EGPs) to enable complex, flexible policies
+across identities and endpoints.
+
+**Role Governing Policies (RGPs)** are Sentinel policies that are tied to
+particular tokens, Identity entities, or Identity groups. They have access to
+a rich set of controls across various aspects of Vault. These are evaluated
+whenever a token they're attached to is used.
+
+**Endpoint Governing Policies (EGPs)** are Sentinel policies that are tied to
+particular paths instead of tokens. They have access to as much request
+information as possible, but they can take effect even on unauthenticated
+paths, such as login paths.
+
+The Vault integration with Sentinel is documented in depth in the
+[Vault Enterprise documentation](https://www.vaultproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Endpoint policy that requires MFA authentication from a corporate network.**
+
+```sentinel
+import "sockaddr"
+
+# We expect logins to come only from our private IP range
+cidrcheck = rule {
+ sockaddr.is_contained(request.connection.remote_addr, "10.20.0.0/16")
+}
+
+# Require Ping MFA validation to succeed
+ping_valid = rule {
+ mfa.methods.ping.valid
+}
+
+main = rule when request.path is "auth/ldap/login" {
+ ping_valid and cidrcheck
+}
+```
+
+**Example: Endpoint policy that disallows tokens created before a certain time.**
+
+```sentinel
+import "time"
+import "strings"
+
+main = rule when not strings.has_prefix(request.path, "auth/ldap/login") {
+ time.load(token.creation_time).unix > time.load("2017-09-17T13:25:29Z").unix
+}
+```
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/writing/basic.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/writing/basic.mdx
new file mode 100644
index 0000000000..1a881476a4
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/writing/basic.mdx
@@ -0,0 +1,107 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Basics
+sidebar_current: docs-writing-basic
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Policy Basics
+
+Sentinel policies are easy to write while still supporting advanced constructs
+for creating complex policies. This page will explain the basics of writing
+Sentinel policies to get started. You don't need any prior Sentinel knowledge,
+but we do recommend reading the
+[getting started guide](/sentinel/intro/getting-started/first-policy) and
+[language guide](/sentinel/language) after this.
+
+Sentinel policies are text files written using the [Sentinel language](/sentinel/language).
+The policies are evaluated top-to-bottom. The value of `main` after execution
+determines whether a policy passes or fails.
+
+## The Simplest Policy
+
+Sentinel only requires that a policy have a `main` variable that evaluates to
+a boolean value.
+
+A valid example is shown below:
+
+```sentinel
+main = 10 > 5
+```
+
+This type of minimal policy is not purely academic. In practice, simple
+policies can often be reduced to a single line logical statement resulting
+in `true` or `false`. However, the expression is usually wrapped in a
+[rule](/sentinel/language/rules) for testing reasons.
+
+You can verify Sentinel will execute this minimal policy using the CLI:
+
+```
+$ sentinel apply minimal.sentinel
+Pass
+```
+
+## Logical Expressions
+
+Policy is at its core a set of logic: you can or can not perform some action
+under a certain set of circumstances. Those circumstances are logical
+expressions. Therefore, Sentinel policies primarily translate into logical
+expressions.
+
+Detailed documentation on boolean expressions is
+[available in the language guide](/sentinel/language/boolexpr).
+
+A simple numerical comparison was seen in the first example on this page.
+Sentinel also provides inclusion operators such as `contains`, `any`, `all`, and
+more. Sentinel allows some operators to have aliases to promote readability
+while remaining programmer-familiar, such as `==` which can equivalently be `is`.
+
+The example below verifies that all numbers in a list are even:
+
+```sentinel
+numbers = [6, 22, 8, 4, 12]
+main = all numbers as n { n % 2 is 0 }
+```
+
+## Variables
+
+A policy will very often use variables. Applications such as
+[Nomad](https://www.nomadproject.io) inject variables into the global
+scope of a policy for making policy decisions. For example, Nomad injects
+the job that is being run into the policy scope. Knowing how to use
+variables is critical to effectively using Sentinel.
+
+Detailed documentation on how to define and access variables is
+[available in the language guide](/sentinel/language/variables).
+
+Variables can be defined and used explicitly. For example:
+
+```sentinel
+value = 10
+main = value > 5
+```
+
+But they may also be introduced implictly by the host system. Nomad
+injects `job` into policies to describe the job that is being run. The
+policy below is a valid policy that requires a job have two task groups.
+Notice that `job` is not defined anywhere. It is implicitly inserted by
+the host application (in this case Nomad). Refer to the application you're
+writing policy for to determine if it implicitly inserts values.
+
+```sentinel
+main = length(job.task_groups) is 2
+```
+
+## And more!
+
+The Sentinel language supports many more features such as functions,
+loops, and more. You can learn about all of this in the
+[complete language guide](/sentinel/language).
+
+The other pages in the [writing policy](/sentinel/writing)
+will cover other information you need to know about writing Sentinel
+policies that isn't simply a language reference.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/writing/debugging.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/writing/debugging.mdx
new file mode 100644
index 0000000000..eb7b6ea2a9
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/writing/debugging.mdx
@@ -0,0 +1,46 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Debugging
+sidebar_current: docs-writing-debugging
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Debugging
+
+As policies become more complex, it becomes harder to understand exactly why
+a policy may be behaving differently than you expect.
+
+Prior to actively debugging, there are multiple best practices we recommend
+following. We recommend breaking your policy down into a set of
+[rules](/sentinel/writing/rules). This will make it easier to understand
+[traces](/sentinel/writing/traces) and make it easier to
+[test](/sentinel/writing/testing) your policies.
+This should help to debug policies up to a significant complexity.
+
+## Print Logging
+
+The built-in [print function](/sentinel/language/logging-errors#print)
+can be used to log data. The print output is only shown during failures
+or when tracing is explicitly enabled.
+
+The print function is [variadic](https://en.wikipedia.org/wiki/Variadic_function).
+Each argument specifies a value to print together, concatenated with a space
+in between each. You can put any type as an argument and Sentinel will
+convert that to a human-friendly string format.
+
+Example:
+
+```sentinel
+value = 42
+print("the value is", value) // the value is 42
+
+map = { "foo": false }
+print(map) // { "foo": false }
+```
+
+The `print` function returns the value `true` so that it can also be
+used within rule expressions. Note that if short-circuiting is occuring
+within the boolean logic, the `print` function may never be reached.
+The runtime contains a special case where `print` will not short-circuit
+its own logic, so `print() or expr` will always evaluate `expr`.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/writing/imports.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/writing/imports.mdx
new file mode 100644
index 0000000000..3b1e6732e0
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/writing/imports.mdx
@@ -0,0 +1,124 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Imports
+sidebar_current: docs-writing-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom import](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only
+local context for making policy decisions.
+
+Sentinel also comes with a set of [standard imports](/sentinel/imports).
+Standard imports are available to every Sentinel policy to help policy writers
+with common tasks such as working with the time, network addresses, and more.
+
+-> **This page is about writing policies that _use_ imports.**
+If you're interested in
+_creating_ a new import, please see the section on
+[writing import plugins](/sentinel/extending). Anyone
+can write a custom import to access new data.
+
+## Using Imports
+
+To use an import, you use the `import` keyword at the top of your policy.
+This specifies the name of the import you want to use. The application
+you're writing the policy for must already be
+[configured](/sentinel/extending/install) to provide that import.
+
+Details on imports can be found in the
+[import section in the language reference](/sentinel/language/imports).
+
+In the example below, we use the [time](/sentinel/imports/time) import:
+
+```sentinel
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+There are two options to develop a policy locally when using imports:
+configure Sentinel to launch the import on `apply`, or mock the import
+using `mock`. The former requires access to the import plugin while the
+latter is faster (doesn't have to launch a process) and doesn't require
+the plugin.
+
+## Mocking Imports
+
+The first option to developing policies locally is to mock the import values.
+When mocking an import, you don't need the import plugin available. This can be
+useful since some imports may not be available as a plugin and may only be
+available to the application the policy runs in.
+
+Mocks are specified via the [configuration
+file](/sentinel/commands/config). Mocks can also be used for
+[testing](/sentinel/writing/testing).
+
+You can supply mock configuration one of two ways, depending on your use case:
+
+* **[Using static data](/sentinel/commands/config#mocking-static-data):**
+ Use this method when you can accurately represent your mock data in JSON and
+ do not need to mock complex Sentinel features such as functions.
+* **[Using Sentinel code](/sentinel/commands/config#mocking-with-sentinel-code):**
+ Use this method when using a static JSON object is insufficient, such as when
+ you need to mock functions or other complex Sentinel features.
+
+Our example does not require complex data to be mocked, so a static JSON object
+is sufficient:
+
+```json
+{
+ "mock": {
+ "time": {
+ "now": {
+ "hour": 12,
+ "weekday_name": "Tuesday"
+ }
+ }
+ }
+}
+```
+
+This can be used via the CLI:
+
+```
+$ sentinel apply -config=config.json policy.sentinel
+Pass
+```
+
+## Launching Imports
+
+If you have access to the plugin binary, you can launch the import. The
+benefit of this is that it is really using the import to test your
+policy. If the import changes, your policies may start failing. If you
+only use mock data and the import changes, your policies will still
+appear to work.
+
+Imports are configured in the configuration file:
+
+```json
+{
+ "imports": {
+ "time": {
+ "path": "/path/to/sentinel-time-import"
+ }
+ }
+}
+```
+
+This would require the `sentinel-time-import` binary. For this example
+this doesn't currently exist. We plan on writing one to provide for this
+section of the documentation.
+
+## Custom Imports
+
+You can also [create your own imports](/sentinel/extending).
+
+If your policy decisions could benefit from accessing external information,
+then you can use custom imports as a way to do this.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/writing/index.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/writing/index.mdx
new file mode 100644
index 0000000000..e7743c0011
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/writing/index.mdx
@@ -0,0 +1,36 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Writing Policy
+sidebar_current: docs-writing
+description: >-
+ Sentinel provides a language and workflow for building policy across any
+ system that embeds Sentinel. By learning Sentinel once, you are able to
+ effectively control access to many systems using Sentinel's powerful features.
+ Basic concepts that are important to understand for Vault usage.
+layout: docs
+---
+
+# Writing Sentinel Policy
+
+This section covers how to write Sentinel policies.
+
+It is recommended that you complete the
+[getting started guide](/sentinel/intro/getting-started/first-policy) prior to
+reading this section of the documentation. The getting started guide will
+explain all the basics of writing and testing policies. This section of the
+documentation will serve as more of a reference guide to all available
+features of Sentinel.
+
+Sentinel provides a language and workflow for building policy across any system
+that embeds Sentinel. By learning Sentinel once, you are able to effectively
+control access to many systems using Sentinel's powerful features.
+
+Sentinel also provides a [local CLI](/sentinel/commands) for developing and testing
+Sentinel policies. This CLI can be integrated into a daily developer's workflow
+along with CI to treat [policy as code](/sentinel/concepts/policy-as-code).
+
+Sentinel uses its own [language](/sentinel/language) for writing
+policies. You can view a [language reference](/sentinel/language)
+as well as the [specification](/sentinel/language/spec) for details. You
+don't have to read those documents immediately, since the language should
+be easy enough to pick up throughout this section.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/writing/rules.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/writing/rules.mdx
new file mode 100644
index 0000000000..a99b58a04e
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/writing/rules.mdx
@@ -0,0 +1,49 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Rules
+sidebar_current: docs-writing-rules
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Rules
+
+Rules form the basis of a policy by representing behavior that is either
+passing or failing (true or false). Rules are a first class language construct
+in Sentinel. A policy can and should be broken down into rules to aid with
+readability, testability, and performance.
+
+Rules provide:
+
+* **Readability:** A policy that is broken down into rules can be read
+ more easily. The logic becomes clearer to see for policy writers when
+ they come back to it.
+
+* **Debuggability:** When [tracing](/sentinel/writing/tracing) is enabled,
+ the trace is formatted by rule names. A policy that is broken down into
+ more rules is more easily debugging by noticing unexpected rule values.
+
+* **Testability:** [Policy testing](/sentinel/writing/testing) is
+ built on asserting the values of rules. By breaking logic down into
+ rules, you're able to more effectively test your policies.
+
+* **Performance:** As explained in the next section, rules are only
+ evaluated once on demand. This means that a rule referenced multiple
+ time only has a one-time performance cost. For complex logic, this
+ could result in improved performance.
+
+An example usage of rules is shown below:
+
+```sentinel
+is_sunny = rule { weather is "sunny" }
+is_wednesday = rule { day is "wednesday" }
+main = rule { is_sunny and is_wednesday }
+```
+
+Details about rules and their behavior can be found in
+[the language reference](/sentinel/language/rules). Rules have important
+behavior so we recommend reading the language reference on rules.
+Rules will be used throughout the remainder of the documentation.
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/writing/testing.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/writing/testing.mdx
new file mode 100644
index 0000000000..b812ccfe73
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/writing/testing.mdx
@@ -0,0 +1,340 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Testing
+sidebar_current: docs-writing-testing
+description: >-
+ Sentinel has a built-in test framework to validate a policy behaves as
+ expected.
+layout: docs
+---
+
+# Testing
+
+Sentinel has a built-in test framework to validate a policy behaves as expected.
+
+With an ever-increasing amount of automation surrounding technology,
+the guardrails provided by policies are a critical piece towards ensuring
+expected behavior. As a reliance on correct policy increases, it is important
+to test and verify policies.
+
+Testing is a necessary step to fully realize
+[policy as code](/sentinel/concepts/policy-as-code). Just as good software
+is well tested, a good set of policies should be equally well tested.
+
+## Test Folder Structure
+
+Policies are tested by asserting that [rules](/sentinel/writing/rules)
+are the expected values given a pre-configured input. Tests are run
+by executing the [test command](/sentinel/commands/test).
+
+Sentinel is opinionated about the folder structure required for tests.
+This opinionated structure allows testing to be as simple as running
+`sentinel test` with no arguments. Additionally, it becomes simple to test
+in a CI or add new policies.
+
+The structure Sentinel expects is `test//*.json` where ``
+is the name of your policy file without the file extension. Within
+that folder is a list of JSON files. Each JSON file represents a single
+test case. Therefore, each policy can have multiple tests associated with
+it.
+
+## Test Case Format
+
+Each JSON file within the test folder for a policy is a single test case.
+
+The JSON file is the same configuration format as the
+[apply configuration file](/sentinel/commands/apply#configuration-file).
+The format lets you define mock data, imports to use, and more.
+This mock data is the key piece in being able to test policies: you
+craft a specific scenario and assert your policy behaves as you expect.
+
+Test cases also use the special key `test` within the configuration file
+to assert the boolean value of [rules](/sentinel/writing/rules). If
+the `test` key is omitted, the policy is expected to pass (the same
+as asserting that the `main` rule is true). If the test key is specified,
+only the rules specified in the map will be asserted. This means if you
+omit `main`, then the final policy result is not asserted.
+
+Example with assertions:
+
+```json
+{
+ "param": {
+ "day": "monday",
+ "hour": 7
+ },
+
+ "test": {
+ "main": false,
+ "is_open_hours": false,
+ "is_weekday": true
+ }
+}
+```
+
+The configuration above specifies some parameter data, and asserts the result of
+some rules. This is the same configuration used in the example section below.
+
+## Example
+
+Lets use the following file as an example. Save this file to a directory
+and name it `policy.sentinel`. It can be named anything with the `sentinel`
+extension, but by naming it `policy.sentinel` your output should match
+the example output on this page.
+
+```sentinel
+// The day of the week.
+param day
+
+// The hour of the day.
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+### A Passing Test
+
+Next, let's define a single test case. Relative to where you saved
+the policy, create a file at the path `test/policy/good.json`.
+
+```json
+{
+ "param": {
+ "day": "monday",
+ "hour": 14
+ }
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/good.json
+```
+
+This passed because the policy passed. We didn't assert any specific
+rules. By not specifying any assertions, `test` expects the policy itself
+to fully pass.
+
+### A Failing Test
+
+Define another test case to fail. We want to verify our policy fails when expected, too.
+
+Save the following as `test/policy/7-am.json`:
+
+```json
+{
+ "param": {
+ "day": "monday",
+ "hour": 7
+ }
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+FAIL - policy.sentinel
+ FAIL - test/policy/7-am.json
+ expected "main" to be true, got: false
+
+ trace:
+ FALSE - policy.sentinel:9:1 - Rule "main"
+ FALSE - policy.sentinel:9:15 - is_open_hours
+ FALSE - policy.sentinel:8:24 - hour > 8 and hour < 17
+ FALSE - policy.sentinel:8:24 - hour > 8
+
+ FALSE - policy.sentinel:8:1 - Rule "is_open_hours"
+ FALSE - policy.sentinel:8:24 - hour > 8
+ PASS - test/policy/good.json
+```
+
+As you can see, the test fails because "main" is false. This is good
+because the policy _should_ have failed since we specified an invalid
+hour. But, we expect main to be false and don't want our test to fail!
+Update `7-am.json` to add test assertions:
+
+```json
+{
+ "param": {
+ "day": "monday",
+ "hour": 7
+ },
+
+ "test": {
+ "main": false,
+ "is_open_hours": false,
+ "is_weekday": true
+ }
+}
+```
+
+And when we run the tests:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/7-am.json
+ PASS - test/policy/good.json
+```
+
+The test passes. We asserted that we expect the `main` rule to be false,
+the `is_open_hours` rule to be false, and the `is_weekday` rule to be
+true. By asserting some rules are true, we can verify that our policy
+is failing for reasons we expect.
+
+## Mocking
+
+The above example demonstrates how to test by supplying different
+[parameters](/sentinel/language/parameters). Parameters in a policy can be
+specifically useful when you want to control user-defined input values to a
+policy.
+
+However, generally, when testing, you will need mimic the conditions you will
+see in production. Production implementations of Sentinel will supply data using
+one of two methods:
+
+* **Global data:** Data is injected directly into the policy's scope and is
+ accessible using normal identifiers, similar to variables.
+* **Imports:** Data is stored behind an [import](/sentinel/language/imports)
+ and loaded on demand as needed by the policy author.
+
+Proper testing of a policy requires that these values be able to be _mocked_ -
+or, in other words, simulated in a way that allows the accurate testing of the
+scenarios that a policy could reasonably pass or fail under.
+
+Mocking both globals and imports can be done by setting various parts of the
+configuration file.
+
+### Mocking Globals
+
+Demonstrating the mocking of globals can be seen by making a few modifications to
+our example policy, removing the `param` declarations:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Then, change the `param` section in the configuration file to
+[`global`](/sentinel/commands/config#global-1).
+
+```json
+{
+ "global": {
+ "day": "monday",
+ "hour": 14
+ }
+}
+```
+
+This test should still pass, as if nothing had happened, although what we've
+done is shifted our parameters to globals, simulating an environment where `day`
+and `hour` are already defined for us.
+
+### Mocking Imports
+
+To mock imports, we need to use the
+[`mock`](/sentinel/commands/config#mock-imports) section of the
+configuration file.
+
+Let's say the above example is behind an import named `time`.
+
+-> **NOTE:** [`time`](/sentinel/imports/time) is a valid standard import.
+This example may not be accurate to the
+import's syntax.
+
+The code now looks like this:
+
+```sentinel
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To mock this import, we can [mock it as static
+data](/sentinel/commands/config#mocking-static-data). The configuration
+file now looks like, without assertions:
+
+```json
+{
+ "mock": {
+ "time": {
+ "now": {
+ "weekday_name": "Monday",
+ "hour": 14
+ }
+ }
+ }
+}
+```
+
+The policy will now pass, with the `time` import mocked.
+
+Data can also be [mocked as Sentinel
+code](/sentinel/commands/config#mocking-with-sentinel-code). In this case,
+the above configuration file would look like:
+
+```json
+{
+ "mock": {
+ "time": "mock-time.sentinel"
+ }
+}
+```
+
+And a file named `mock-time.sentinel` would now hold your mock values:
+
+```sentinel
+day = "monday"
+hour = 14
+```
+
+Mocking as Sentinel code allows more complex details to be mocked as well, such
+as functions. Say we wanted to mock the `time.load()` function. To mock this,
+just add it to the `mock-time.sentinel` file:
+
+```sentinel
+load = func(_) {
+ return {
+ "weekday_name": "Monday",
+ "hour": 14,
+ }
+}
+```
+
+Your code can now be written as:
+
+```sentinel
+import "time"
+
+t = time.load("a_mock_timestamp")
+
+is_weekday = rule { t.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { t.hour > 8 and t.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To see more details, see the [Mock
+Imports](/sentinel/commands/config#mock-imports) section in the
+configuration file.
+
+## Test Automation
+
+The `sentinel test` command was designed to be automation friendly.
+We encourage you to enable a CI such as [Travis CI](https://travis-ci.com/)
+on your policy repositories to continuously run tests. An example
+`.travis.yml` configuration file is shown below:
+
+```yaml
+language: bash
+script: sentinel test
+```
diff --git a/content/sentinel/v0.13.x/content/sentinel/docs/writing/tracing.mdx b/content/sentinel/v0.13.x/content/sentinel/docs/writing/tracing.mdx
new file mode 100644
index 0000000000..691dbbe8f4
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/docs/writing/tracing.mdx
@@ -0,0 +1,98 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Traces
+sidebar_current: docs-writing-tracing
+description: >-
+ Sentinel provides execution traces to better understand the execution of a
+ policy.
+layout: docs
+---
+
+# Traces
+
+Sentinel provides execution traces to better understand the execution of a policy.
+
+When using the Sentinel [CLI](/sentinel/commands), traces are shown
+on policy failure during `apply` or `test`, or when the `-trace` flag is
+explicitly specified. For Sentinel-enabled applications, traces are optional
+and may require special configuration to enable. Most Sentinel-enabled
+applications log traces on failure.
+
+-> **Note:** We're actively improving Sentinel tracing in each release
+to provide better data and improve readability. The exact format of a
+trace may not match the Sentinel version embedded in the application you're
+using, but the data should be similar.
+
+## Analyzing a Trace
+
+To show traces, let's use the example policy below with the CLI:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Let's cause the policy to fail so the trace is more interesting.
+If you're on a terminal that supports it, the trace output will be colored
+so you can more clearly see passes, failures, and rules.
+
+```
+$ sentinel apply -global 'day=sunday' -global 'hour=14' policy.sentinel
+Fail
+
+FALSE - policy.sentinel:6:1 - Rule "main"
+ TRUE - policy.sentinel:6:15 - is_open_hours
+ TRUE - policy.sentinel:5:24 - hour > 8 and hour < 17
+ TRUE - policy.sentinel:5:24 - hour > 8
+ TRUE - policy.sentinel:5:37 - hour < 17
+ FALSE - policy.sentinel:6:33 - is_weekday
+ FALSE - policy.sentinel:4:21 - day not in ["saturday", "sunday"]
+
+TRUE - policy.sentinel:5:1 - Rule "is_open_hours"
+ TRUE - policy.sentinel:5:24 - hour > 8
+ TRUE - policy.sentinel:5:37 - hour < 17
+
+FALSE - policy.sentinel:4:1 - Rule "is_weekday"
+```
+
+The least-nested values are all rules. It begins with the
+value of that rule (`TRUE` or `FALSE`) followed by the location and name
+of the rule. Notice that the `main` rule is false, the `is_open_hours`
+rule is true, and the `is_weekday` rule is false.
+
+We know that main requires _both_ `is_open_hours` and `is_weekday` to be
+true, so based on the trace, we can tell that main failed because
+`is_weekday` was false. We can then go to the definition of `is_weekday`
+in the source and determine the logical failure.
+
+The trace also breaks down more complex logical expressions. Notice that
+`is_open_hours` is split into the two sides of the `and`, but `is_weekday`
+has no values under it. Because `is_weekday` is itself a single boolean
+expression, the failure isn't repeated. But for `is_open_hours`, you can also
+see the result of each side of the boolean expression.
+
+## Missing Rules or Expressions
+
+The Sentinel runtime sometimes optimizes away executions completely. This
+can result in a trace being incomplete. If you don't see a rule or boolean
+expression within the trace, it means the runtime didn't execute it at all.
+
+Consider the example policy:
+
+```sentinel
+main = rule { 42 > 40 or 10 < 5 }
+```
+
+If we run it with trace:
+
+```
+$ sentinel apply -trace policy.sentinel
+TRUE - policy.sentinel:1:1 - Rule "main"
+ TRUE - policy.sentinel:1:15 - 42 > 40
+```
+
+Notice that the expression `10 < 5` is not present in the trace.
+Since `42 > 40` is true, the entire `or` can be short-circuited and
+the result is known to be true. Remaining parts of the expression are not
+executed and are therefore not present in the trace.
diff --git a/content/sentinel/v0.13.x/content/sentinel/intro/getting-started/first-policy.mdx b/content/sentinel/v0.13.x/content/sentinel/intro/getting-started/first-policy.mdx
new file mode 100644
index 0000000000..e0cc4cef50
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/intro/getting-started/first-policy.mdx
@@ -0,0 +1,54 @@
+---
+page_title: Your First Sentinel Policy
+sidebar_title: Your First Policy
+sidebar_current: intro-getting-started-first
+description: Let's begin by writing a working Sentinel policy.
+layout: intro
+---
+
+# Your First Sentinel Policy
+
+Sentinel is a system to enforce complex policies on an integrated application.
+
+Writing Sentinel policy requires minimal programming experience. The Sentinel
+language is designed to be approachable and learned quickly and easily. Whether
+you're a professional programmer or someone who uses SQL and Excel, you can
+learn to write Sentinel policies.
+
+Let's begin by writing a simple, working Sentinel policy:
+
+```sentinel
+hour = 4
+main = rule { hour >= 0 and hour < 12 }
+```
+
+This is a valid Sentinel policy. It will pass since we hardcoded the
+`hour` to be 4. In a real system, `hour` may be something that is provided
+to us and actually set to the current hour. We'll learn more about that later.
+
+For now, try running this policy locally. Save the above example to a file
+named `policy.sentinel` and execute it. Then, modify the policy to make it
+fail. Play around more if you'd like.
+
+```
+$ sentinel apply policy.sentinel
+Pass
+```
+
+## Main
+
+Every Sentinel policy must have a `main` rule. This is the rule that
+is evaluated to determine the result of a policy. A rule describes a
+condition that must be true for the rule to be true. Within the rule,
+a single boolean expression describes the condition. In the example above,
+it is a straightforward check that the time is greater than zero (midnight)
+and less than 12.
+
+It is easy to imagine that such a rule might be used in a system such
+as [Nomad](https://www.nomadproject.io) to restrict the times when a
+deploy can occur. The power of arbitrary logical statements within Sentinel
+allows Sentinel policies to restrict almost any behavior.
+
+Next, we'll
+[introduce and explain rules](/sentinel/intro/getting-started/rules)
+so we can use them in our policies.
diff --git a/content/sentinel/v0.13.x/content/sentinel/intro/getting-started/imports.mdx b/content/sentinel/v0.13.x/content/sentinel/intro/getting-started/imports.mdx
new file mode 100644
index 0000000000..b2b3eb94e7
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/intro/getting-started/imports.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Imports
+sidebar_title: Imports
+sidebar_current: intro-getting-started-imports
+description: Imports enable Sentinel to access external data and functions.
+layout: intro
+---
+
+# Imports
+
+Imports enable Sentinel to access external data and functions.
+
+Sentinel ships with a set of [standard imports](/sentinel/imports).
+Standard imports are available by default to every Sentinel policy. Note that
+the application embedding Sentinel may whitelist or blacklist the available
+set of imports.
+
+To use an import, use the `import` statement. Then, access data and
+functions on the import using the import name followed by a period and
+the field you want to access. The example below checks whether the request
+path starts with a prefix. Assume that `request_path` is available.
+
+```sentinel
+import "strings"
+
+main = rule { strings.has_prefix(request_path, "/admin") }
+```
+
+## External Data
+
+The true power in imports is their ability to reference external data.
+Imports can be added to a Sentinel-enabled application through
+[plugins](/sentinel/extending). This enables any Sentinel-enabled
+application to make policy decision based on any source of data.
+
+This ability allows the policy system to enforce almost any necessary
+organizational policy, since the ability of a policy isn't restricted
+purely to the embedding application's data model.
+
+For example, policies in [Nomad](https://www.nomadproject.io) can
+access data in [Consul](https://www.consul.io) to determine attributes
+of a policy. In the example below, we use a hypothetical Consul import:
+
+```sentinel
+import "consul"
+
+main = rule {
+ job.tasks[0].resources.memory <= int(consul.get("policy/nomad/max-memory"))
+}
+```
+
+In this example, a Nomad job's memory usage is limited to the value of
+a Consul key/value item. By simply changing a Consul KV entry, policy
+can be changed. Imports can do anything, you can [write your own import plugins](/sentinel/extending)
+to extend Sentinel.
+
+Next, we'll learn how to
+[test policies](/sentinel/intro/getting-started/testing)
+to ensure our policy logic is correct.
diff --git a/content/sentinel/v0.13.x/content/sentinel/intro/getting-started/index.mdx b/content/sentinel/v0.13.x/content/sentinel/intro/getting-started/index.mdx
new file mode 100644
index 0000000000..0942cdb637
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/intro/getting-started/index.mdx
@@ -0,0 +1,17 @@
+---
+page_title: Install Sentinel CLI - Getting Started
+sidebar_title: Getting Started
+sidebar_current: intro-getting-started-overview
+description: The first step to using Sentinel is to get the CLI installed.
+layout: intro
+---
+
+# Getting Started With Sentinel
+
+This section covers getting started with Sentinel. Here, we will take you
+through the first steps that you will need to do to get started with installing
+the Sentinel CLI, writing your first policy, and getting familiar with rules,
+boolean logic, imports, and testing.
+
+You can select a topic on the menu to get started. The first step is to [install
+the Sentinel CLI](/sentinel/intro/getting-started/install).
diff --git a/content/sentinel/v0.13.x/content/sentinel/intro/getting-started/install.mdx b/content/sentinel/v0.13.x/content/sentinel/intro/getting-started/install.mdx
new file mode 100644
index 0000000000..4c3959d617
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/intro/getting-started/install.mdx
@@ -0,0 +1,57 @@
+---
+page_title: Install Sentinel CLI - Getting Started
+sidebar_title: Installing the CLI
+sidebar_current: intro-getting-started-install
+description: The first step to using Sentinel is to get the CLI installed.
+layout: intro
+---
+
+# Install Sentinel CLI
+
+Sentinel is a policy framework that is embedded in the enterprise versions of
+HashiCorp tools. The policies you write are deployed to these applications and
+enforced there.
+
+The Sentinel CLI is a command-line interface for local development and testing.
+For the getting started guide, we'll use the CLI to learn how to write policies
+for Sentinel-enabled applications. The Sentinel CLI is distributed as a [binary
+package](/sentinel/downloads) for all supported platforms and architectures.
+
+## Installation Instructions
+
+To install the Sentinel CLI, find the [appropriate package](/sentinel/downloads) for your
+system and download it. The CLI is packaged as a zip archive.
+
+After downloading Sentinel, unzip the package. The CLI runs as a single binary
+named `sentinel`. Any other files in the package can be safely removed and
+Sentinel will still function.
+
+The final step is to make sure that the `sentinel` binary is available on the `PATH`.
+See [this page](https://stackoverflow.com/questions/14637979/how-to-permanently-set-path-on-linux)
+for instructions on setting the PATH on Linux and Mac.
+[This page](https://stackoverflow.com/questions/1618280/where-can-i-set-path-to-make-exe-on-windows)
+contains instructions for setting the PATH on Windows.
+
+## Verifying the Installation
+
+After installing the Sentinel CLI, verify the installation worked by opening a
+new terminal session and checking that the `sentinel` binary is available. By
+executing `sentinel`, you should see help output similar to the following:
+
+```
+$ sentinel
+Usage: sentinel [--version] [--help] []
+
+Available commands are:
+ apply Execute a policy and output the result
+ fmt Format Sentinel policy to a canonical format
+ test Test policies
+ version Prints the Sentinel version
+```
+
+If you get an error that the binary could not be found, then your `PATH`
+environment variable was not setup properly. Please go back and ensure that your
+`PATH` variable contains the directory where Sentinel was installed.
+
+Otherwise, the CLI is installed and ready to go. Let's celebrate by [writing our
+first policy!](/sentinel/intro/getting-started/first-policy)
diff --git a/content/sentinel/v0.13.x/content/sentinel/intro/getting-started/logic.mdx b/content/sentinel/v0.13.x/content/sentinel/intro/getting-started/logic.mdx
new file mode 100644
index 0000000000..893d63c851
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/intro/getting-started/logic.mdx
@@ -0,0 +1,55 @@
+---
+page_title: Logic
+sidebar_title: Logic
+sidebar_current: intro-getting-started-logic
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Logic
+
+The core of a policy is a set of logical expressions to determine whether
+a behavior is allowed or not.
+Sentinel makes it easy to write readable logical expressions to express
+the intended policy.
+
+The logical expression syntax will be familiar to anyone who has programmed
+before. Sentinel also supports a couple more unique constructs to assist
+with common policy requirements. Detailed documentation on how to write
+logical expressions and the supported operators is available in
+[the boolean expression reference](/sentinel/language/boolexpr).
+
+Example logic:
+
+```sentinel
+a is b // Equality, you can also use ==
+a is not b // Inequality, you can also use !=
+
+a > b // Relational operators, comparison
+a < b
+a >= b
+a <= b
+
+a contains b // Inclusion check, substrings, etc.
+a not contains b // Negated version of above
+```
+
+The full list of available operators can be found in
+[the boolean expression reference](/sentinel/language/boolexpr).
+
+Logic can be combined with `and` or `or`. When combined with `and`, both
+sides must result in `true`. When combined with `or`, only one side needs
+to be true to result in `true`.
+
+With these building blocks, basic rules can be built up:
+
+```sentinel
+main = rule {
+ hour >= 8 and
+ hour < 17
+}
+```
+
+Next, we'll introduce
+[imports](/sentinel/intro/getting-started/imports)
+so that we can write rules that interact with external data.
diff --git a/content/sentinel/v0.13.x/content/sentinel/intro/getting-started/next-steps.mdx b/content/sentinel/v0.13.x/content/sentinel/intro/getting-started/next-steps.mdx
new file mode 100644
index 0000000000..8c01c3e6f3
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/intro/getting-started/next-steps.mdx
@@ -0,0 +1,21 @@
+---
+page_title: Next Steps
+sidebar_current: intro-getting-started-nextsteps
+description: That concludes the getting started guide for Sentinel.
+layout: intro
+---
+
+# Next Steps
+
+That concludes the getting started guide for Sentinel. Hopefully you're excited
+about the possibilities of Sentinel and ready to put this knowledge to use to
+improve the safety of your environments.
+
+We've covered the basics of the core features of Sentinel in this guide.
+We recommend the following as next steps:
+
+* [Documentation](/sentinel) - The documentation is an in-depth
+ reference guide of all the features of Sentinel.
+
+* [Language Reference](/sentinel/language) - The language reference
+ is a complete reference to the features of the Sentine policy language.
diff --git a/content/sentinel/v0.13.x/content/sentinel/intro/getting-started/rules.mdx b/content/sentinel/v0.13.x/content/sentinel/intro/getting-started/rules.mdx
new file mode 100644
index 0000000000..6a72af190e
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/intro/getting-started/rules.mdx
@@ -0,0 +1,73 @@
+---
+page_title: Rules
+sidebar_title: Rules
+sidebar_current: intro-getting-started-rules
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Rules
+
+A rule describes a set of conditions that result in either true or false.
+
+Rules in Sentinel are a first-class concept. They are used for making complex
+logic more understandable by breaking it down into a set of rules, for
+testing policies by asserting certain rules are passed, and more.
+
+## Writing Rules
+
+Let's look at the Sentinel policy we wrote in the previous section:
+
+```sentinel
+hour = 4
+main = rule { hour >= 0 and hour < 12 }
+```
+
+The logic in this policy is straightforward and easy to understand. However,
+it is common for policy logic to become much more complicated. Rules are the
+key abstraction for making complex logic understandable. We can turn the
+policy above into a set of rules:
+
+```sentinel
+hour = 4
+
+past_midnight = rule { hour >= 0 }
+before_noon = rule { hour < 12 }
+
+main = rule {
+ past_midnight and
+ before_noon
+}
+```
+
+This policy is easier to read. Our original policy was already quite
+simple, but it is easy to imagine a more complex policy becoming much
+more readable by extracting logical expressions into a set of rules.
+
+Rules don't just exist to make a policy more readable. They're also a
+testing and debugging point. For testing, you can assert that certain rules
+are certain values given a specific input to verify that your policy works
+as you expect. Testing and debugging are covered in later sections.
+
+## Conditional Rules
+
+In many cases, a rule is only valid when something else is also true.
+
+Consider the human language statement "before you eat, you must wash your hands."
+The rule "you must wash your hands" is only valid "before you eat". In any
+other scenario, the rule is meaningless.
+This is also true in a technical context. Imagine the rule "if the driver
+is Docker, you must not expose any ports." For this example, assume
+rules `is_docker` and `has_no_exposed_ports` exist. You can write this rule
+like this:
+
+```sentinel
+main = rule when is_docker { has_no_exposed_ports }
+```
+
+When `is_docker` is true, the rule is evaluated as normal. However,
+when `is_docker` is false, the rule always returns `true`, because the
+logic of the rule is meaningless.
+
+Let's learn how to create more complex rules with
+[logical expressions](/sentinel/intro/getting-started/logic).
diff --git a/content/sentinel/v0.13.x/content/sentinel/intro/getting-started/testing.mdx b/content/sentinel/v0.13.x/content/sentinel/intro/getting-started/testing.mdx
new file mode 100644
index 0000000000..ee245f1c0e
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/intro/getting-started/testing.mdx
@@ -0,0 +1,70 @@
+---
+page_title: Testing
+sidebar_current: intro-getting-started-testing
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Testing
+
+It is important to ensure the policies you write are correct. Sentinel
+includes a built-in test framework that can be run locally and in CI
+environments to test that policies behave as expected.
+
+A common pitfall with many simple ACL systems is that they provide no easy
+way to verify their correctness. You basically have to set the ACL and try
+the behaviors against a real system to verify it is working as expected.
+This requires a lot of setup and is unique to each system.
+
+[Sentinel's built-in test framework](/sentinel/writing/testing) has
+zero dependencies. It is a single binary that can mock the data that real
+systems are exposing to the policy. It is designed to be CI-friendly and
+enables continuous testing of your policies. This is necessary for [policy
+as code](/sentinel/concepts/policy-as-code).
+
+Detailed documentation on testing policies is available in [the Sentinel Testing reference](/sentinel/writing/testing).
+
+## Writing Tests
+
+For this example, save the following policy as `policy.sentinel`:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Next, let's write a passing test case. This will test that the policy tests
+when we expect it to pass. Save the following in `test/policy/good.json`:
+
+```json
+{
+ "global": {
+ "day": "monday",
+ "hour": 14
+ }
+}
+```
+
+And run `sentinel test`:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/good.json
+```
+
+The `sentinel test` command will automatically find all Sentinel policies
+and their associated test cases and run them all. The test framework will
+run all JSON files as individual test cases, allowing you to test a variety
+of scenarios for your policies.
+
+Try adding another test case to force the policy to fail. This test case can
+be saved at `test/policy/fail.json`. For test cases with intentional
+failures, you'll need to use the [test assertions described in the testing
+reference](/sentinel/writing/testing#a-failing-test).
+
+Now that you have a good grasp of what Sentinel is and how to use it, please feel
+free to look through the
+[next steps](/sentinel/intro/getting-started/next-steps)
+you can take to further your Sentinel knowledge.
diff --git a/content/sentinel/v0.13.x/content/sentinel/intro/index.mdx b/content/sentinel/v0.13.x/content/sentinel/intro/index.mdx
new file mode 100644
index 0000000000..25aa1179ba
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/intro/index.mdx
@@ -0,0 +1,24 @@
+---
+layout: intro
+page_title: Documentation
+sidebar_current: docs-home
+description: >-
+ Sentinel is a language and framework for policy built to be embedded in
+ existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+[introduction](/sentinel/intro) to understand what Sentinel is, how it
+compares to other software, and more.
diff --git a/content/sentinel/v0.13.x/content/sentinel/intro/what/index.mdx b/content/sentinel/v0.13.x/content/sentinel/intro/what/index.mdx
new file mode 100644
index 0000000000..fba1f3a0e8
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/intro/what/index.mdx
@@ -0,0 +1,65 @@
+---
+page_title: Introduction
+sidebar_title: What is Sentinel?
+sidebar_current: intro-what
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: intro
+---
+
+# Introduction to Sentinel
+
+Welcome to the intro guide to Sentinel! This guide is the best
+place to start with Sentinel.
+
+If you are already familiar with the basics of Sentinel, the
+[documentation](/sentinel) provides a better reference
+guide for all available features as well as internals.
+
+## What is Sentinel?
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+Sentinel provides a language for writing policy and a workflow for developing
+and testing policies independent of the software they'll be written to. We
+also provide a framework for developers to build plugins that allow a
+Sentinel-enabled system to access external information to make policy
+decisions.
+
+Most systems today have some degree of access control. You are able to define
+identities and what they have access to. These ACL systems solve an immediate
+and necessary problem of locking down a system in very broad strokes. Sentinel
+is a reusable system for more advanced software policy decisions. Sentinel
+enables:
+
+* **Fine-Grained Policy**: Most ACL systems only enable coarse-grained
+ behaviors: "read", "write", etc. Sentinel enables fine-grained behavior such
+ as disallowing a certain API call when specific parameters are present.
+
+* **Logic-Based Policy**: You can write policy using full
+ conditional logic. For example, you may only allow a certain application behavior
+ on Monday to Thursday unless there is a manager override.
+
+* **Accessing External Information**: Sentinel can source external information
+ to be used in policy decisions. For example, a policy that restricts the size
+ of a payload may read data from [Consul](https://www.consul.io) to determine
+ the payload size limit.
+
+* **Enforcement levels**: Sentinel allows policies to be defined along
+ with an "enforcement level" that dictates the pass/fail behavior of a policy.
+ Advisory policies warn if they fail, soft mandatory policies can have their
+ failures overridden, and hard mandatory policies must pass under all
+ circumstances. Having this as a built-in concept enables you to model
+ policy more accurately and completely for your organization.
+
+## Next Steps
+
+See the page on [Why Sentinel?](/sentinel/intro/why) to learn more
+about the origins of the product. Then, continue onwards with
+the [getting started guide](/sentinel/intro/getting-started/install) to write
+your first policies and see how Sentinel works in practice.
diff --git a/content/sentinel/v0.13.x/content/sentinel/intro/why/index.mdx b/content/sentinel/v0.13.x/content/sentinel/intro/why/index.mdx
new file mode 100644
index 0000000000..37e01c6fc3
--- /dev/null
+++ b/content/sentinel/v0.13.x/content/sentinel/intro/why/index.mdx
@@ -0,0 +1,50 @@
+---
+page_title: Why Sentinel?
+sidebar_title: Why Sentinel?
+sidebar_current: intro-why
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: intro
+---
+
+# Why Sentinel?
+
+The growth of infrastructure and applications has been enabled in part
+by an increasing trend towards automation everywhere.
+Configuration as code (such as Chef or Puppet with [Packer](https://www.packer.io))
+has enabled automated machine configuration. Infrastructure
+as code (such as [Terraform](https://www.terraform.io)) has enabled
+automatic infrastructure creation. And schedulers (such as
+[Nomad](https://www.nomadproject.io) and Kubernetes) have enabled
+automatic application deployment.
+Sentinel enables guardrails to be put in place
+on automation while allowing the codification and automatic enforcement
+of business requirements in critical areas of your infrastructure.
+
+Meanwhile, businesses have business requirements and sometimes legal
+requirements which must be expressed in policies. Traditionally, these
+policies are enforced by humans. But in a highly automated world, the
+automation is only as fast as its slowest component. In many cases, this
+is the human verification step.
+
+As an example: before infrastructure as code and autoscaling, if an order
+came through for 5,000 new machines, a human would likely respond to the
+ticket verifying that the user really intended to order 5,000 new machines.
+Today, automation can almost always freely order 5,000 new compute instances
+without any hesitation, which can result in unintended expense or system
+instability.
+
+Sentinel introduces [policy as code](/sentinel/concepts/policy-as-code)
+and a powerful framework built-in to HashiCorp tooling to allow automation
+guardrails, business requirements, legal compliance, and more to be
+actively enforced by the running systems in realtime.
+
+With Sentinel, you can require an override to create certain numbers of
+infrastructure resources. You can disallow unsafe deployment configurations
+with Nomad. You can enforce certain key/value formats in Consul. You
+can restrict secret access by time in Vault. And more.
+
+Sentinel is available today in HashiCorp enterprise products. You can
+try Sentinel immediately with the [getting started guide](/sentinel/intro/getting-started/install) or learn more about the integrations in the
+[documentation](/sentinel).
diff --git a/content/sentinel/v0.13.x/data/docs-nav-data.json b/content/sentinel/v0.13.x/data/docs-nav-data.json
new file mode 100644
index 0000000000..cd31371633
--- /dev/null
+++ b/content/sentinel/v0.13.x/data/docs-nav-data.json
@@ -0,0 +1,332 @@
+[
+ {
+ "title": "Release Notes",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "changelog"
+ }
+ ]
+ },
+ {
+ "title": "Basic Concepts",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "concepts"
+ },
+ {
+ "title": "Policy as Code",
+ "path": "concepts/policy-as-code"
+ },
+ {
+ "title": "Policy Language",
+ "path": "concepts/language"
+ },
+ {
+ "title": "Imports",
+ "path": "concepts/imports"
+ },
+ {
+ "title": "Enforcement Levels",
+ "path": "concepts/enforcement-levels"
+ }
+ ]
+ },
+ {
+ "title": "Commands (CLI)",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "commands"
+ },
+ {
+ "title": "Configuration File Syntax",
+ "path": "commands/config"
+ },
+ {
+ "title": "apply",
+ "path": "commands/apply"
+ },
+ {
+ "title": "fmt",
+ "path": "commands/fmt"
+ },
+ {
+ "title": "test",
+ "path": "commands/test"
+ }
+ ]
+ },
+ {
+ "title": "Writing Policy",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "writing"
+ },
+ {
+ "title": "Basics",
+ "path": "writing/basic"
+ },
+ {
+ "title": "Rules",
+ "path": "writing/rules"
+ },
+ {
+ "title": "Traces",
+ "path": "writing/tracing"
+ },
+ {
+ "title": "Testing",
+ "path": "writing/testing"
+ },
+ {
+ "title": "Imports",
+ "path": "writing/imports"
+ },
+ {
+ "title": "Debugging",
+ "path": "writing/debugging"
+ }
+ ]
+ },
+ {
+ "title": "Import Plugins",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "extending"
+ },
+ {
+ "title": "Install",
+ "path": "extending/install"
+ },
+ {
+ "title": "Develop",
+ "path": "extending/dev"
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Language",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "language"
+ },
+ {
+ "title": "Variables",
+ "path": "language/variables"
+ },
+ {
+ "title": "Values",
+ "path": "language/values"
+ },
+ {
+ "title": "Lists",
+ "path": "language/lists"
+ },
+ {
+ "title": "Maps",
+ "path": "language/maps"
+ },
+ {
+ "title": "Rules",
+ "path": "language/rules"
+ },
+ {
+ "title": "Imports",
+ "path": "language/imports"
+ },
+ {
+ "title": "Parameters",
+ "path": "language/parameters"
+ },
+ {
+ "title": "Boolean Expressions",
+ "path": "language/boolexpr"
+ },
+ {
+ "title": "Arithmetic",
+ "path": "language/arithmetic"
+ },
+ {
+ "title": "Slices",
+ "path": "language/slices"
+ },
+ {
+ "title": "Conditionals",
+ "path": "language/conditionals"
+ },
+ {
+ "title": "Loops",
+ "path": "language/loops"
+ },
+ {
+ "title": "Functions",
+ "path": "language/functions"
+ },
+ {
+ "title": "Scope",
+ "path": "language/scope"
+ },
+ {
+ "title": "Undefined",
+ "path": "language/undefined"
+ },
+ {
+ "title": "Logging and Errors",
+ "path": "language/logging-errors"
+ },
+ {
+ "title": "Specification",
+ "path": "language/spec"
+ }
+ ]
+ },
+ {
+ "title": "Builtin Functions",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "functions"
+ },
+ {
+ "title": "append",
+ "path": "functions/append"
+ },
+ {
+ "title": "delete",
+ "path": "functions/delete"
+ },
+ {
+ "title": "error",
+ "path": "functions/error"
+ },
+ {
+ "title": "keys",
+ "path": "functions/keys"
+ },
+ {
+ "title": "length",
+ "path": "functions/length"
+ },
+ {
+ "title": "print",
+ "path": "functions/print"
+ },
+ {
+ "title": "range",
+ "path": "functions/range"
+ },
+ {
+ "title": "values",
+ "path": "functions/values"
+ }
+ ]
+ },
+ {
+ "title": "Standard Imports",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "imports"
+ },
+ {
+ "title": "decimal",
+ "path": "imports/decimal"
+ },
+ {
+ "title": "http",
+ "path": "imports/http"
+ },
+ {
+ "title": "json",
+ "path": "imports/json"
+ },
+ {
+ "title": "runtime",
+ "path": "imports/runtime"
+ },
+ {
+ "title": "sockaddr",
+ "path": "imports/sockaddr"
+ },
+ {
+ "title": "strings",
+ "path": "imports/strings"
+ },
+ {
+ "title": "time",
+ "path": "imports/time"
+ },
+ {
+ "title": "types",
+ "path": "imports/types"
+ },
+ {
+ "title": "units",
+ "path": "imports/units"
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Consul",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "consul"
+ }
+ ]
+ },
+ {
+ "title": "Nomad",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "nomad"
+ }
+ ]
+ },
+ {
+ "title": "Terraform",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "terraform"
+ }
+ ]
+ },
+ {
+ "title": "Vault",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "vault"
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Internals",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "internals"
+ },
+ {
+ "title": "Plugins",
+ "path": "internals/plugins"
+ }
+ ]
+ }
+]
diff --git a/content/sentinel/v0.13.x/data/intro-nav-data.json b/content/sentinel/v0.13.x/data/intro-nav-data.json
new file mode 100644
index 0000000000..c95181af70
--- /dev/null
+++ b/content/sentinel/v0.13.x/data/intro-nav-data.json
@@ -0,0 +1,47 @@
+[
+ {
+ "title": "What is Sentinel?",
+ "path": "what"
+ },
+ {
+ "title": "Why Sentinel?",
+ "path": "why"
+ },
+ {
+ "title": "Getting Started",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "getting-started"
+ },
+ {
+ "title": "Installing the CLI",
+ "path": "getting-started/install"
+ },
+ {
+ "title": "Your First Policy",
+ "path": "getting-started/first-policy"
+ },
+ {
+ "title": "Rules",
+ "path": "getting-started/rules"
+ },
+ {
+ "title": "Logic",
+ "path": "getting-started/logic"
+ },
+ {
+ "title": "Imports",
+ "path": "getting-started/imports"
+ },
+ {
+ "title": "Testing",
+ "path": "getting-started/testing"
+ },
+ {
+ "title": "Next Steps",
+ "path": "getting-started/next-steps"
+ }
+ ]
+ }
+]
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/changelog/index.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/changelog/index.mdx
new file mode 100644
index 0000000000..a0259d49a7
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/changelog/index.mdx
@@ -0,0 +1,421 @@
+---
+page_title: Sentinel Runtime Release Notes
+sidebar_title: Release Notes
+sidebar_current: docs-changelog
+description: >-
+ This are the public release notes for the Sentinel runtime. See this document
+ for the latest updates.
+layout: docs
+---
+
+# Sentinel Runtime Release Notes
+
+These are the release notes for the Sentinel runtime.
+
+Each version of the runtime is released with a corresponding version of
+[Sentinel CLI](/sentinel/commands). To download the CLI, see the [downloads
+page](/sentinel/downloads).
+
+Sentinel integrations and embedded runtimes may not always have the latest
+version installed, depending on the product's individual release cycle. For more
+information, contact the support team for your specific integration.
+
+
+
+## 0.14.4 (February 6, 2020)
+
+IMPROVEMENTS:
+
+- `imports/time`: Changed the `add` timespace function in the `time` import to
+ allow it to take float types as durations. These values will be truncated to
+ the appropriate integer-value duration.
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where out-of-place right-hand braces were being
+ parsed as empty statements, instead of raising syntax errors.
+
+## 0.14.3 (January 22, 2020)
+
+IMPROVEMENTS:
+
+- `cmd/test`: Fail when an assertion is not found in a trace.
+- `cmd/test`: Added a message to notify when no rules were evaluated in a test.
+
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue where import aliases (the `as baz` in `import
+ "foo/bar" as baz`) were being removed when formatting with `sentinel fmt`.
+
+- `imports/http`: Fixed an issue with the http import where the client library's
+ debug logs were being printed to standard error.
+
+## 0.14.2 (January 15, 2020)
+
+NOTES:
+
+With this release, we are discontinuing support for MacOS 32-bit. 64-bit builds
+are now the only builds available for MacOS.
+
+IMPROVEMENTS:
+
+- `lang/printer`: A newline is now added to files formatted via `sentinel fmt`.
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue in printing where multi-line expressions would
+ indent infinitely. They will now only indent once at the most. This is mostly
+ seen when using `sentinel fmt`.
+- `runtime/encoding`: Fixed an issue where if a plugin returned a deeply
+ embedded undefined value, the value was instead decoded into a map.
+
+## 0.14.1 (January 6, 2020)
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where reserved words could not be used as
+ selectors when the selector expression was the last lexeme on a line.
+
+## 0.14.0 (December 18, 2019)
+
+LANGUAGE CHANGES:
+
+- **Filter Expression** `filter`. A new quantifier expression, `filter` has been added.
+ `filter` returns a subset of the provided collection based on a boolean condition that
+ is asserted for each element.
+
+NOTES:
+
+- **Apple Notarization**. Our darwin releases for this version and up will be signed
+ and notarized according to Apple's requirements.
+
+## 0.13.1 (November 25, 2019)
+
+BUG FIXES:
+
+- `runtime/eval`: Parameters are no longer allowed in mock files. Adding one to
+ a mock will result in a runtime error.
+- `runtime/eval`: Fixed an issue where import calls from within mocks were
+ failing under certain circumstances.
+- `imports/decimal`: `int` should now correctly provide the truncated integer
+ representation of a decimal number, not the rounded one.
+
+## 0.13.0 (November 15, 2019)
+
+LANGUAGE CHANGES:
+
+- **Policy Parameters**. The new [policy
+ parameters](https://docs.hashicorp.com/sentinel/language/parameters) feature
+ allows authors to use a `param` declaration at the top of a policy to supply
+ values that are expected to come from outside of a policy. See the linked
+ documentation for more details.
+
+FEATURES:
+
+- `imports/http`: The [`http`
+ import](https://docs.hashicorp.com/sentinel/imports/http) is a new addition
+ to the standard library enabling the use of HTTP-accessible data from outside
+ the runtime in policy rules.
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where loading other imports before a mocked
+ import would cause those imports to no longer be visible from within the
+ policy.
+
+## 0.12.0 (October 7, 2019)
+
+LANGUAGE CHANGES:
+
+- **New `case` statement**. This statement is a selection control mechanism to
+ conditionally execute a branch based on expression equality, allowing
+ simplification of complex conditional chains that may otherwise need to be
+ written with `else if`. See the [Case
+ Statements](https://docs.hashicorp.com/sentinel/language/spec#case-statements)
+ section of the Sentinel language specification for more details.
+
+IMPROVEMENTS:
+
+- `lang/semantic`: Added a semantic check to ensure usage of append is not using
+ a return value.
+
+BUG FIXES:
+
+- `runtime/eval`: Corrected an issue where calling a method on an import object
+ value that was the result of a method call on another import object value
+ would have erroneously tried to call an import of the name of the "parent"
+ import object value. Example: in `a = subject.new(); b = a.call(); c =
+ b.call()`, `b.call()` would attempt to call a method named `call` on the root
+ namespace for an import named `a`. This has now been corrected so that
+ `b.call()` will now correctly call the `call` method for the respective object
+ namespace residing in the import named `subject`.
+- `imports`: Some standard imports may have been returning null for some unknown
+ keys in objects when they should have been returning undefined. This was due
+ to an SDK issue which was corrected in SDK version 0.3.2, which has now been
+ corrected.
+- `cmd/test`: `sentinel test` will now correctly fail a policy if it encounters
+ an error.
+- `cmd/test`: `sentinel test` will now correctly display errors and other output
+ that were missing due to a formatting issue.
+- `runtime/eval`: The `append` builtin now correctly returns undefined for all
+ calls, as called for by the Specification. Note that in most cases, the
+ semantic check outlined above will trigger an error if the return value is
+ used.
+
+## 0.11.0 (September 5, 2019)
+
+LANGUAGE CHANGES:
+
+- **New builtin function:** `range()`. This function existed in the spec in
+ earlier versions but was removed as it lacked an implementation. This has now
+ been implemented and re-added to the spec. See the
+ [Range](https://docs.hashicorp.com/sentinel/language/spec#range) section of
+ the Sentinel Specification for more details.
+- Lists are now comparable. Lists are equal if their corresponding elements are
+ comparable and equal.
+- Method calls on values returned by imports are now supported. See the
+ [Imports](https://docs.hashicorp.com/sentinel/language/spec#imports) section
+ of the Sentinel Specification for more details.
+
+FEATURES:
+
+- `imports/decimal`: This is a new import designed to do exact precision
+ mathematical calculations.
+- `runtime/eval`: Compound call expressions that refer to imports (example:
+ foo.bar().baz() when `foo` is a loaded import) will now function as expected.
+ Previously, this was only supported up to the first call (example:
+ `foo.bar()`).
+- `imports/time`: Timespaces returned by calls such as `time.now` are now
+ callable. Example: `t = time.now; t.after(some_previous_time)` will now
+ function.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: The implementation of comparison of non-comparable types has
+ now changed. Rather than triggering a runtime error, non-comparable types will
+ now return false when attempting to compare.
+
+## 0.10.4 (August 15, 2019)
+
+BUG FIXES:
+
+- `runtime/encoding`: Fixed an issue with conversion of null values that could
+ lead to crashes in imports.
+
+## 0.10.3 (July 15, 2019)
+
+BUG FIXES:
+
+- `command/config`: Parsing a configuration file where malformed data is
+ encountered after apparently properly-formed data will now report an error. An
+ example would be a situation where misplaced braces would cause a JSON object
+ to only parse part of the configuration file.
+
+## 0.10.2 (June 25, 2019)
+
+BUG FIXES:
+
+- `lang/parser`: Corrected an issue where parsing certain compound binary
+ expressions where the negation predicate (`not`) was in use would cause the
+ negation to have no effect. Example: `foo else "bar" not in "baz"` would have
+ been parsed and evaluated as `foo else "bar" in "baz"`, effectively producing
+ the opposite result.
+
+## 0.10.1 (May 9, 2019)
+
+BUG FIXES:
+
+- `command/test`: `sentinel test` now displays policies without
+tests as an unknown result with [no test files], instead of the somewhat
+erroneous behavior of displaying it as a PASS. A full test run with a mixture
+of passing tests and no tests still results in an overall successful result.
+
+## 0.10.0 (April 18, 2019)
+
+LANGUAGE CHANGES:
+
+- Mixed-number arithmetic operations are now allowed. Addition (`+`),
+ subtraction (`-`), multiplication (`*`), division (`/`), and remainder
+ operations (`%`) are no longer restricted to number values of the same
+ type, and can mix integer and floating point. The result of these operations
+ is always a floating-point number.
+- Remainder (modulo, `%`) operations are now allowed on floating-point numbers.
+
+
+BUG FIXES:
+
+- `lang/parser`: Fixed a bug that affected the use of `not` with `contains`,
+ `matches`, or `in` in a compound expression.
+- `runtime/eval`: Fixed error messages on evaluation errors with `contains` and
+ `in` to indicate that strings are an allowed type along with lists and maps.
+
+
+## 0.9.2 (March 15, 2019)
+
+This is a dependency update related to the changes mentioned in 0.9.1. No other
+changes have been made.
+
+## 0.9.1 (March 14, 2019)
+
+This is a patch release that is required to integrate with the latest versions
+of the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). No other
+changes have been made.
+
+## 0.9.0 (January 28, 2019)
+
+FEATURES:
+
+- `imports/strings`: Added a `join` function to the `strings` import. This can
+ be used to join a list into a string with a specific separator.
+ Multi-dimensional lists and all Sentinel primitives are supported.
+
+## 0.8.1 (January 17, 2019)
+
+BUG FIXES:
+
+- `lang/eval`: Fixed a bug that prevents the effective use of `return`
+ statements in `for` loops.
+
+## 0.8.0 (January 14, 2019)
+
+FEATURES:
+
+- Mocks can now be represented by Sentinel code. This allows for the mocking of
+ functions and other complex data structures that cannot be represented in
+ JSON. For more information on using this feature via mocks, click
+ [here](https://docs.hashicorp.com/sentinel/commands/config#mocking-with-sentinel-code).
+
+
+IMPROVEMENTS:
+
+- Import validation has now been moved to the semantic checking phase. This
+ should result in better reporting of validation errors. In addition, import
+ validation will now enforce the use of an `as` identifier when an import path
+ is not a valid identifier on its own (example: `import "foo/bar"`).
+
+## 0.7.0 (December 12, 2018)
+
+BUG FIXES:
+
+- There have been changes to the runtime in how scope is handled over multiple
+ policy executions. Scope is now correctly unique per single policy execution,
+ and values set or builtins that are overridden in one policy will no longer
+ affect those values within another.
+
+## 0.6.0 (November 30, 2018)
+
+FEATURES:
+
+- `imports/runtime`: This new import allows for one to check various aspects of
+ the Sentinel runtime as it may be embedded in the simulator or a specific
+ implementation. For now, it allows the version to be checked.
+
+## 0.5.1 (November 28, 2018)
+
+IMPROVEMENTS:
+
+- `imports/time`: Added the `zone` and `zone_string` attributes to assist with
+ validation of a timespace's zone.
+- `command/fmt`: Added a new -check flag. This option does not commit changes,
+ but instead checks to see what files need formatting and outputs them on
+ stdout.
+
+BUG FIXES:
+
+- `command/test`: Ensure that passing test results are correctly output one per
+ line. Tests are also now run in a deterministic fashion based on
+ lexicographical (alphabetical) order.
+- `imports/time`: `month_name` and `weekday_name` will now show up correctly in
+ a returned timespace result.
+
+
+## 0.5.0 (November 5, 2018)
+
+IMPROVEMENTS:
+
+- `spec`: Selectors can now contain any reserved word (example: `rule`) or
+ keyword operator (example: `any`, `all`, `is`, `not`). This only works for the
+ _selector_ part of the expression (after the first period) - the first primary
+ expression (before the first period) still needs to be an identifier that does
+ not conflict with reserved words.
+
+BUG FIXES:
+
+- The simulator should now display import function call names correctly in
+ import errors.
+
+## 0.4.0 (October 1, 2018)
+
+FEATURES:
+
+- `builtin`: Added the `bool` built-in type conversion function. Booleans will
+ also now accepted as conversion into other values as well, with the full list
+ of behaviors available in the spec.
+
+## 0.3.2 (September 27, 2018)
+
+FEATURES:
+
+- `command/apply`: `sentinel apply` now prints out messages output by the
+ `print()` function when a trace is output on policy failure, or when a trace
+ is forced with `-trace`.
+- `imports/time`: Added the `month_name` and `weekday_name` keys to the
+ timespace, which return full-English names for the month and day of the week.
+
+
+BUG FIXES:
+
+- `command/fmt`: `sentinel fmt -` Will no longer print out the filter status
+ message on the output stream when `-write=false` Is not explicitly stated.
+ This brings the behavior of the command in line with the help text.
+- `runtime`: Index operations on the right-hand-side that have negative indexes
+ that go out of range (example: `length(list) * -1 - 1`) now correctly return
+ `undefined`. left-hand-side index assignments with a out-of-range negative
+ index still return runtime errors.
+
+## 0.3.1 (August 3, 2018)
+
+BUG FIXES:
+
+- `runtime`: Basic index assignment has been implemented as per the spec.
+- `runtime`: Index expressions for lists with negative indexes will no longer panic if the
+ list index is less than `length(list) * -1`.
+
+## 0.3.0 (July 20, 2018)
+
+FEATURES:
+
+- **New standard import: `types`.** This can be used to dynamicaly detect the
+ type of some value.
+
+## 0.2.0 (April 11, 2018)
+
+FEATURES:
+
+- **New standard import: `json`.** Marshal and unmarshal JSON documents and
+ access their contents as native Sentinel values.
+- **`break` and `continue`.** These are now both specified and implemented.
+ `break` allows loop exiting and `continue` allows immediate execution of the
+ next iteration.
+
+IMPROVEMENTS:
+
+- runtime: `print()` map values are now ordered alphabetically by keys.
+
+BUG FIXES:
+
+- command/test: If no `test` block exists, test behaves like it is asserting
+ `main: true`.
+- runtime: default maximum stack depth to 500
+- runtime: `print()` map values now appear like more typical maps.
+- runtime: division by zero is an error, not a crash
+- runtime: plugins that send map values with `null` values now decode properly
+ into native Sentinel values.
+
+## 0.1.0 (September 19, 2017)
+
+Initial release.
+
+
+
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/commands/apply.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/commands/apply.mdx
new file mode 100644
index 0000000000..b715c5c77f
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/commands/apply.mdx
@@ -0,0 +1,47 @@
+---
+page_title: 'Command: apply'
+sidebar_title: 'apply'
+sidebar_current: docs-commands-apply
+description: >-
+ The `sentinel apply` command is used to execute a policy locally for
+ development purposes.
+layout: docs
+---
+
+# Command: `apply`
+
+The `sentinel apply` command is used to execute a policy locally for development
+purposes.
+
+## Usage
+
+Usage: `sentinel apply [options] POLICY`
+
+This command executes the policy file at the path specified by POLICY.
+
+Use the exit code of this command to determine the exact status of the policy
+evaluation. `0` is pass, `1` is fail, `2` is undefined (fail, but because the
+result was undefined), and `3` is a runtime error. Errors unrelated to the
+policy status itself are returned with an exit status of `9`.
+
+To control the behavior of the `apply` command, create a [configuration
+file](/sentinel/commands/config). With this, you can define available
+[import plugins](/sentinel/concepts/imports), mock data, and global values.
+This can help you simulate a policy embedded within an application.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-config=path` - Path to a configuration file specifying available imports,
+ mock data, globals, etc. The default is `sentinel.json`.
+
+- `-global key=value` - Set global values. This is the same as setting `global`
+ in the configuration file, and will override any of these respective values
+ set in the configuration. The value is either a string, or a JSON number,
+ array, or object. To force strings, use quotes.
+
+- `-param key=value` - Set parameters, the same as setting `param` in the
+ configuration file. Values are handled in the same way they are with the
+ `-global` flag.
+
+- `-trace` - Always show the execution trace. This shows intermediate
+ boolean expression values. This always shows for failed policies.
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/commands/config.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/commands/config.mdx
new file mode 100644
index 0000000000..26c6fca28d
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/commands/config.mdx
@@ -0,0 +1,248 @@
+---
+page_title: Sentinel CLI Configuration File Syntax
+sidebar_title: Configuration File Syntax
+sidebar_current: docs-commands-config
+description: >-
+ The Sentinel CLI's configuration file can be used to control the
+ behavior of the simulator during apply and test operations.
+layout: docs
+---
+
+# Sentinel CLI Configuration File Syntax
+
+The Sentinel CLI's configuration file can be used to control the behavior
+of the simulator during [`apply`](/sentinel/commands/apply) and
+[`test`](/sentinel/commands/test) operations.
+
+## Usage
+
+The configuration file is used in different ways depending on what operation you
+are trying to execute.
+
+### Apply
+
+When using `sentinel apply`, supply the configuration file by using the
+`-config=FILE` flag, where `FILE` is the path to the configuration file. The
+default is `sentinel.json`.
+
+See the [apply command](/sentinel/commands/apply) reference for more
+details.
+
+### Test
+
+When using sentinel test, each file matching `test//*.json` is a
+configuration file representing a single test case, where `` is the name
+of the policy being tested. Configure assertions for each [test
+case](#test-cases) using the test section.
+
+See the [test command](/sentinel/commands/test) reference for more details.
+
+## Configuration File Reference
+
+The format of the configuration file is JSON. The available keys are:
+
+- `mock` - A map of mock import data.
+- `imports` - A map of available imports via [import plugins](/sentinel/extending).
+- `global` - Data that is inserted into the global scope.
+- `param` - Values for parameters defined in the policy.
+- `test` - Test cases for the [test command](/sentinel/command/test).
+
+### Mock Imports
+
+Mock imports allow running a policy with an
+[import](/sentinel/concept/import) that you may not have access to or is
+too difficult to run. For example, it can be easier to mock data for [Terraform
+Enterprise](https://www.terraform.io/docs/enterprise/sentinel/index.html)
+locally instead of having to run a workspace through a plan/policy check cycle.
+
+Mock imports are specified in the `mock` key, as a map to mock data, keyed by
+the import name that you want to mock. For example, if you wanted to mock the
+`time` import, you could create an entry with the `time` key pointing to the
+data you want to mock.
+
+The data can take one of two forms:
+
+#### Mocking static data
+
+Static data can be mocked directly by supplying a JSON object as the mock data.
+
+Example:
+
+```json
+{
+ "mock": {
+ "time": {
+ "now": {
+ "hour": 9,
+ "minute": 42
+ }
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```sentinel
+import "time"
+
+main = time.now.hour == 9
+```
+
+#### Mocking with Sentinel code
+
+There are some Sentinel types that static JSON data cannot mock, such as
+functions, and maps that don't have string keys. To mock this data, you can use
+a Sentinel file itself. In this case, the parameter to your import key is the
+file with the Sentinel code in it, relative to the current working directory in
+the event of `apply`, or relative to the configuration file location in the
+event of `test`.
+
+Note that `main` is not required in a mock data file.
+
+Example:
+
+```json
+{
+ "mock": {
+ "foo": "mock-foo.sentinel"
+ }
+}
+```
+
+`mock-foo.sentinel` would contain:
+
+```sentinel
+bar = func() {
+ return "baz"
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```sentinel
+import "foo"
+
+main = foo.bar() == "baz"
+```
+
+### Imports
+
+Imports allow you to run a real [import plugin](/sentinel/extending)
+with a policy. The `sentinel` binary will launch the plugin, connect to it,
+configure it, and execute it as needed by the policy.
+
+Note the difference between this and mock imports above is that this
+configuration specifies real import plugins to execute. The mock data
+fakes an import with static data.
+
+Imports are specified by the `import` key. The value of this is a map
+where the key is the name of the import and the value is another map with
+configuration about that import. The configuration map has the following keys:
+
+- `path` (string, required) - Path to the import plugin executable.
+- `args` (list of string, optional) - A list of arguments to pass to the
+ executable when starting it.
+- `env` (map of string to string, optional) - A set of environmental variables
+ to set when launching the plugin.
+- `config` (map, optional) - Configuration for the plugin itself. This is
+ specific to each individual plugin. Please reference the documentation for
+ the plugin for more information.
+
+Example:
+
+```json
+{
+ "imports": {
+ "time": {
+ "path": "/path/to/sentinel-import-time",
+ "config": { "fixed_time": 1504155600 }
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass assuming
+the import configuration is valid:
+
+```sentinel
+import "time"
+
+main = time.now.day == 31
+```
+
+### Global
+
+Global data is injected directly into the global scope of the policy.
+This can be used to simulate global data that may be injected by a
+Sentinel-enabled application.
+
+Global data is specified by the `global` key. The value is a map of
+variables to inject directly into the running policy.
+
+Example:
+
+```json
+{
+ "global": {
+ "time": {
+ "now": {
+ "day": 31
+ }
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass. Notice
+that we don't have to do any `import`. The values of `global` are
+injected directly into the global scope.
+
+```sentinel
+main = time.now.day == 31
+```
+
+### Parameters
+
+The `param` section allows you to supply values for the
+[parameters](/sentinel/language/parameters) found in a policy. Values
+entered here will satisfy required parameters in a policy, in addition to
+override any defaults. Note that any of the manual parameter value methods
+supported by `sentinel apply` will override the values set here.
+
+```json
+{
+ "param": {
+ "foo": "bar"
+ }
+}
+```
+
+### Test Cases
+
+Test cases specify the test cases for the [test command](/sentinel/commands/test).
+
+Tests are specified by the `test` key. The value of this is a map of
+string to boolean. The key is the name of a rule and the value is the
+expected value of that rule. If a rule is not specified, than any value is
+allowed.
+
+Example:
+
+```json
+{
+ "test": {
+ "main": true,
+ "valid_day": false
+ }
+}
+```
+
+For the policy:
+
+```sentinel
+valid_day = rule { 2 < 1 } # This is just an example!
+main = rule { not valid_day }
+```
+
+For more information, read about the [test command](/sentinel/commands/test).
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/commands/fmt.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/commands/fmt.mdx
new file mode 100644
index 0000000000..6e9660fb43
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/commands/fmt.mdx
@@ -0,0 +1,30 @@
+---
+page_title: 'Command: fmt'
+sidebar_title: 'fmt'
+sidebar_current: docs-commands-fmt
+description: The `sentinel fmt` command formats a policy source to a canonical format.
+layout: docs
+---
+
+# Command: `fmt`
+
+The `sentinel fmt` command formats a policy source to a canonical format.
+
+## Usage
+
+Usage: `sentinel fmt [options] FILE ...`
+
+This command formats all the specified policy files to a canonical format.
+
+By default, policy files are overwritten in place. This behavior can be
+changed with the `-write` flag. If a specified FILE is `-` then stdin is
+read and the output is always written to stdout.
+
+The command-line flags are all optional. The list of available flags are:
+
+* `-write=true` - Write formatted policy to the named source file. If false,
+ output will go to stdout. If multiple files are specified, the output will
+ be concatenated directly.
+* `-check=false` - Don't format, only check if formatting is necessary. Files
+ that require formatting are printed, and a non-zero exit code is returned if
+ changes are required.
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/commands/index.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/commands/index.mdx
new file mode 100644
index 0000000000..7177093cec
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/commands/index.mdx
@@ -0,0 +1,23 @@
+---
+page_title: Commands (CLI)
+sidebar_current: docs-commands
+description: >-
+ Sentinel provides a `sentinel` command-line interface (CLI) for developing and
+ testing policies.
+layout: docs
+---
+
+# Sentinel CLI Commands
+
+The Sentinel command-line interface (CLI) allows for the developing and testing
+of policies outside of a particular Sentinel implementation. Having a standard
+workflow to develop policies is critical for our mission of [policy as
+code](/sentinel/concepts/policy-as-code).
+
+The CLI takes a subcommand to execute. The complete list of subcommands is in
+the navigation to the left.
+
+The Sentinel CLI is a well-behaved command line application. In erroneous cases,
+a non-zero exit status will be returned. It also responds to -h and --help as
+you'd expect. To view a list of the available commands at any time, just run
+`sentinel` with no arguments.
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/commands/test.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/commands/test.mdx
new file mode 100644
index 0000000000..ec96d3a72e
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/commands/test.mdx
@@ -0,0 +1,40 @@
+---
+page_title: 'Command: test'
+sidebar_title: 'test'
+sidebar_current: docs-commands-test
+description: The `sentinel test` command runs policies against the Sentinel test framework.
+layout: docs
+---
+
+# Command: `test`
+
+The `sentinel test` command runs policies against the Sentinel test framework.
+
+To learn more about testing, see the [testing
+policies](/sentinel/writing/testing) page.
+
+## Usage
+
+Usage: `sentinel test [options] [PATH] ...`
+
+This command runs the defined test cases against a set of policies.
+
+The test cases are expected to live at the path `test//*.json` relative
+to the policy being tested. `` should be the name of the policy
+excluding the file extension. The JSON files should be in the [configuration
+file structure](/sentinel/commands/config). The `test` key is used for
+assertions.
+
+The PATH arguments specified may be a list of zero or more files or directories.
+When no arguments are given, it defaults to the current directory. When a
+directory is given, all policies ending with the extension `.sentinel` are
+tested.
+
+The command-line flags are all optional. The list of available flags are:
+
+* `-run=regexp` - Run only tests matching the regular expression pattern.
+ A `/` splits policy name and test case name.
+
+* `-verbose` - Show tracing and log output for tests that succeed in addition
+ to tests that fail. By default, traces and logs are only shown for tests that
+ fail.
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/concepts/enforcement-levels.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/concepts/enforcement-levels.mdx
new file mode 100644
index 0000000000..7c7fbcf996
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/concepts/enforcement-levels.mdx
@@ -0,0 +1,45 @@
+---
+page_title: Enforcement Levels
+sidebar_title: Enforcement Levels
+sidebar_current: docs-concepts-levels
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Enforcement Levels
+
+Enforcement levels are a first class concept in Sentinel allowing pass/fail
+behavior to be associated separately from the policy logic. This enables
+any policy to be a warning, allow overrides, or be absolutely mandatory.
+Because this level is not part of the policy body itself, different uses of
+the same policy can have different enforcement levels.
+
+Sentinel has three enforcement levels:
+
+* **Advisory:** The policy is allowed to fail. However, a warning should be
+ shown to the user or logged.
+
+* **Soft Mandatory:** The policy must pass unless an override is specified.
+ The semantics of "override" are specific to each Sentinel-enabled application.
+ The purpose of this level is to provide a level of privilege separation
+ for a behavior. Additionally, the override provides non-repudiation since
+ at least the primary actor was explicitly overriding a failed policy.
+
+* **Hard Mandatory:** The policy must pass no matter what. The only way to
+ override a hard mandatory policy is to explicitly remove the policy.
+ Hard mandatory is the default enforcement level. It should be used in
+ situations where an override is not possible.
+
+## Configuring Enforcement Levels
+
+Enforcement levels are configured when a policy is deployed to a
+Sentinel-enabled application. The exact mechanism that the level is specified
+is determined by each application. Please reference the documentation for
+your Sentinel-enabled application for more information.
+
+Enforcement levels are not configured and are not known by the policy body
+itself. All policies should be written to describe exactly the behavior
+they're attempting to control. For example, a policy that restricts deploys
+to business hours should be written exactly like so. When that policy is
+configured on an application, the operator may specify that it is advisory,
+soft, or hard mandatory.
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/concepts/imports.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/concepts/imports.mdx
new file mode 100644
index 0000000000..844a06ed59
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/concepts/imports.mdx
@@ -0,0 +1,34 @@
+---
+page_title: Imports
+sidebar_title: Imports
+sidebar_current: docs-concepts-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom import](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only
+local context for making policy decisions.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that the
+import calendar allows access to engineer calendars. This import only exists
+for the purpose of this example and at the time of writing is not a real
+available import.
+
+```sentinel
+import "calendar"
+
+// Get the calendar for Bob for today
+bob_calendar = calendar.for("bob").today
+
+// Allow this policy to pass if Bob is not on vacation.
+main = rule { not bob_calendar.has_event("vacation") }
+```
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/concepts/index.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/concepts/index.mdx
new file mode 100644
index 0000000000..ce7ac51454
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/concepts/index.mdx
@@ -0,0 +1,15 @@
+---
+page_title: Basic Concepts
+sidebar_current: docs-concepts
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Basic Concepts
+
+This section covers some high level basic concepts that are important
+to understand for day to day Sentinel usage. Every page in
+this section is recommended reading for anyone consuming
+or extending Sentinel.
+
+Please use the navigation to the left to learn more about a topic.
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/concepts/language.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/concepts/language.mdx
new file mode 100644
index 0000000000..af287f1da6
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/concepts/language.mdx
@@ -0,0 +1,92 @@
+---
+page_title: Policy Language
+sidebar_title: Policy Language
+sidebar_current: docs-concepts-language
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Policy Language
+
+Sentinel defines and uses its own [policy language](/sentinel/language).
+
+The language was designed to be approachable by non-programmers, since
+there are many use cases where the individual defining policy may not
+be a developer. However, the language includes constructs that
+are familiar to developers to enable powerful policies.
+
+To learn more about the language, please see the
+[writing policy section](/sentinel/writing)
+or the [language reference](/sentinel/language).
+
+An example of the policy language is shown below:
+
+```sentinel
+import "time"
+
+# Validate time is between 8 AM and 4 PM
+valid_time = rule { time.now.hour >= 8 and time.now.hour < 16 }
+
+# Validate day is M - Th
+valid_day = rule {
+ time.now.weekday_name in ["Monday", "Tuesday", "Wednesday", "Thursday"]
+}
+
+main = rule { valid_time and valid_day }
+```
+
+## Why?
+
+To explain why Sentinel defines and uses its own language, the question
+can be more easily split into roughly two types of languages: _configuration_ languages
+and _programming_ languages.
+
+### Configuration Languages
+
+Configuration languages are formats such as JSON, YAML, XML, etc. Many applications
+use configuration languages as their ACL format. Configuration languages
+are good for static, declarative information. ACL systems are a good fit
+for this. ACL systems are focused, providing the limiting options necessary
+to restrict access to a system.
+
+Configuration languages are not good for dynamic or logical rules. They
+typically only contain limited conditions, loops, or functions. Further
+configuration is often declarative, which can introduce complexities to
+logical statements that depend on ordering.
+
+The use cases that Sentinel was built for require the ability to perform
+complex behavior that didn't model well into a configuration format or a
+declarative form.
+
+### Programming Languages
+
+The Sentinel language was designed with the following goals:
+
+* **Non-programmer friendly.** Sentinel was built to be used by non-programmers.
+ For the use cases we discovered, non-programmers needed the ability to
+ enforce certain rules within a system. For example, a person responsible for
+ compliance may need to insert rules into a system.
+
+* **Programmer friendly.** At the same time, the language needed to support
+ programmer-friendly constructs such as conditionals, loops, and functions
+ for complex policies that a programmer may be writing.
+
+* **Embeddable.** Sentinel is embedded in existing software. The language
+ itself needs to be easily embeddedable. Further, Sentinel was designed
+ to be embedded in [HashiCorp](https://www.hashicorp.com) software
+ written in Go, so it needed to be easily embeddable in Go.
+
+* **Safe.** The language is used in highly security-sensitive environments.
+ It must not be able to crash its host system or access system resources
+ without explicit approval.
+
+Prior to building our own language, Sentinel evaluated other languages.
+Some languages worked quite well. As we continued to develop Sentinel, we
+found that the flexibility and control over designing our own language
+outweighed the costs.
+
+Because the language itself doesn't need to be general purpose, building
+a language focused on policy proved to be the best route for Sentinel.
+It allows us to build powerful tracing capabilities for debugging, make
+interesting performance choices, and extend the language as needed in the
+future.
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/concepts/policy-as-code.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/concepts/policy-as-code.mdx
new file mode 100644
index 0000000000..d9b51f04ad
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/concepts/policy-as-code.mdx
@@ -0,0 +1,73 @@
+---
+page_title: Policy as Code
+sidebar_title: Policy as Code
+sidebar_current: docs-concepts-pac
+description: >-
+ Policy as code is the idea of writing code in a high-level language to manage
+ and automate policies. By representing policies as code in text files, proven
+ software development best practices can be adopted such as version control,
+ automated testing, and automated deployment.
+layout: docs
+---
+
+# Policy as Code
+
+Policy as code is the idea of writing code in a high-level language to
+manage and automate policies. By representing policies as code in text files,
+proven software development best practices can be adopted such as version
+control, automated testing, and automated deployment.
+
+Many existing policy or ACL systems do not practice policy as code. Many
+policies are set by clicking in a GUI, which isn't easily repeatable nor
+versionable. They usually don't provide any system for testing policies
+other than testing an action that would violate the policy. This makes it
+difficult for automated testing. And the policy language itself varies by
+product.
+
+Sentinel is built around the idea and provides all the benefits of policy as code.
+
+## Benefits
+
+Policy as code provides a number of benefits:
+
+* **Sandboxing.** Policies provide the guardrails for other automated systems.
+ As the number of automated systems grow, there is also a growing need to
+ protect those automated systems from performing dangerous actions. Manual
+ verification is too slow; policies need to be represented as code to
+ keep up with other automated systems.
+
+* **Codification.** By representing policy logic as code, the information
+ and logic about a policy is directly represented in code and can be augmented
+ with comments rather than relying on oral tradition to learn about the
+ reason for policies.
+
+* **Version Control.** Policies are encouraged to be stored as simple text
+ files managed by a version control system. This lets you gain all the
+ benefits of a modern VCS such as history, diffs, pull requests, and more.
+
+* **Testing.** Policies are just code. Their syntax and behavior can be
+ [easily validated with Sentinel](/sentinel/commands/test). This also encourages
+ automated testing such as through a CI. Paired with a VCS system, this
+ allows a pull request workflow to verify that a policy keeps the system
+ behavior as expected before merging.
+
+* **Automation.** With all policies as code in simple text files, various
+ automation tools can be used. For example, it is trivial to create tools
+ to automatically deploy the policies into a system.
+
+## Sentinel and Policy as Code
+
+Sentinel fully embraces policy as code in a number of ways:
+
+* **Language.** All Sentinel policies are written using the
+ [Sentinel language](/sentinel/concepts/language). This language is
+ made to inputted directly to text files. As an additional benefit,
+ all Sentinel-enabled applications share the same policy language.
+
+* **Development.** Sentinel provides a [CLI](/sentinel/commands)
+ for development and testing. This local CLI can be used to verify policies
+ before deploying them to a system.
+
+* **Testing.** Sentinel provides a [test framework](/sentinel/commands/test)
+ designed specifically for automation. This allows developers and CI systems
+ to further verify policies.
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/consul/index.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/consul/index.mdx
new file mode 100644
index 0000000000..16b3f502a6
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/consul/index.mdx
@@ -0,0 +1,49 @@
+---
+page_title: Consul and Sentinel
+sidebar_title: Consul
+sidebar_current: docs-app-consul
+description: >-
+ Consul Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies are applied during writes to
+ the KV Store.
+layout: docs
+---
+
+# Consul
+
+[Consul Enterprise](https://www.hashicorp.com/products/consul/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies are applied during writes to the KV Store.
+
+Sentinel policies have access to the key/value being written. They can be used to
+allow or deny the modification. The information that Sentinel policies have access
+to will expand over time.
+
+The Consul integration with Sentinel is documented in depth in the
+[Consul Enterprise documentation](https://www.consul.io/docs/guides/sentinel.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Input validation depending on the name of the key.**
+
+```sentinel
+
+main = rule { valid_key() }
+
+required = [
+ ["port", "\\d+"], # ports must be integers
+ ["name", "\\w+"], # name must be a word
+]
+
+valid_key = func() {
+ for required as v {
+ if key is v[0] {
+ return value matches v[1]
+ }
+ }
+
+ return false
+}
+```
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/extending/dev.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/extending/dev.mdx
new file mode 100644
index 0000000000..1146690d17
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/extending/dev.mdx
@@ -0,0 +1,456 @@
+---
+page_title: Import Plugins
+sidebar_title: Develop
+sidebar_current: docs-extending-dev
+description: >-
+ Import plugins are installed by configuring the Sentinel-enabled application
+ with the path to the plugin, the name of the import, and any arguments needed
+ to launch the plugin.
+layout: docs
+---
+
+# Developing an Import Plugin
+
+Anyone can develop a Sentinel Import plugin using the [Sentinel
+SDK](https://github.com/hashicorp/sentinel-sdk). The SDK contains a high-level
+framework for writing plugins in [Go](https://golang.org) including a test
+framework. Additionally, the SDK contains the low-level [gRPC protocol buffers
+definition](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/import.proto)
+for writing plugins in other languages.
+
+The primary reasons to write an import plugin are:
+
+* You want to expose or access new information within your Sentinel policies.
+ For example, you may want to query information from an external service.
+
+* You want to expose new functions to Sentinel policies.
+
+This page will document how to write a new Sentinel import in Go using the
+Sentinel SDK and the high-level framework provided. You are expected to already
+know the basics of Go and have a properly configured Go installation.
+
+Throughout this page, we'll be developing a simple import to access time values.
+
+-> **NOTE:** The example here is not reflective of the actual implementation of
+the [`time`](/sentinel/imports/time) standard import, so make sure to not
+get the two confused.
+
+## Import Framework
+
+The Sentinel SDK provides a high-level
+[framework](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework)
+for writing imports. We recommend using this framework.
+
+### Basic Concepts
+
+This framework works by implementing the correct Go interfaces.
+
+As a general overview:
+[`framework.Import`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Import)
+implements the
+[`sdk.Import`](https://godoc.org/github.com/hashicorp/sentinel-sdk#Import)
+interface and therefore is a valid import plugin. You must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface to configure the import. The root may then delegate nested access to
+various
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+implementations. Other interfaces are implemented to provide functionality past
+what is provided by the basic `framework.Namespace` implementation - these are
+documented below.
+
+This all may sound like a lot of interfaces, but each interface typically only
+requires a single function implementation and you're only required to implement
+a single namespace (`framework.Namespace`).
+
+As we move on, we'll use real examples to make this clear.
+
+### Implementing framework.Root
+
+To begin, you must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface. This is the interface representing the root of your import. The root
+itself must be implement
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace),
+which allows values to be accessed. Note that the root can optionally implement
+other interfaces, but they're more advanced and omitted here for simplicity.
+
+For our time example, our root may look like this:
+
+```sentinel
+type root struct {
+ time time.Time
+}
+
+// framework.Root impl.
+func (m *root) Configure(raw map[string]interface{}) error {
+ if _, ok := raw["timestamp"]; !ok {
+ raw["timestamp"] = time.Now().Unix()
+ }
+
+ v := raw["timestamp"]
+ timestamp, ok := v.(int)
+ if !ok {
+ return fmt.Errorf("invalid timestamp type %T", v)
+ }
+
+ m.time = time.Unix(timestamp, 0).UTC()
+ return nil
+}
+
+// framework.Namespace impl.
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "minute":
+ return m.time.Minute(), nil
+ }
+
+ return nil, nil
+}
+```
+
+This example implements `framework.Root` and `framework.Namespace` and would
+enable the following within a Sentinel policy once the completed import is
+installed.
+
+```sentinel
+import "time"
+
+print(time.minute)
+main = true
+```
+
+### framework.Namespace
+
+The
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+interface implements a namespace of values. You saw above that
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+must itself be a namespace. However, a namespace `Get` implementation may
+further return namespaces to access nested values.
+
+This enables behavior such as `time.month.string` vs. `time.month.index`.
+Notice each of these examples accesses a nested value within `month`. This can
+be modeled as namespaces within the framework.
+
+In the example below, we return a new namespace for `month` to do just this:
+
+```sentinel
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "month":
+ return &namespaceMonth{Month: m.time.Month()}, nil
+ }
+
+ // ...
+}
+
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil nil
+}
+```
+
+### Primitive Types and Structs
+
+A namespace may also return any primitive Go type as well as structs. The
+framework automatically exposes primitive values as you would expect. For
+structs, exported fields are lowercased and exposed to the policies. The
+`sentinel` struct tag can be used to control how Sentinel can access the field.
+
+In the example below, we expose a struct directly from the root namespace:
+
+```sentinel
+type Location struct {
+ Name string
+ TimeZone string `sentinel:"time_zone"`
+}
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "location":
+ return &Location{Name: "somewhere", TimeZone: "some zone"}, nil
+ }
+
+ // ...
+}
+```
+
+This makes the following values work within a Sentinel policy:
+`time.location.name`, `time.location.time_zone`.
+
+If a field has an empty `sentinel` struct tag (example: `sentinel:""`),
+then that field will not be accessible from a policy.
+
+### Optional Interfaces
+
+The following are optional interfaces that can be implemented on top of
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace).
+All of these interfaces can be implemented at any level, except for
+[`framework.New`](#framework-new), which only works at the root level.
+
+#### framework.Call
+
+The
+[`framework.Call`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Call)
+interface can be implemented to support function calls.
+
+Implementing this interface is very similar to attribute access, except instead
+of returning the attribute value, you return a function. The framework uses Go
+reflection to determine the argument and result types and calls it. If the
+argument types do not match or the signature is otherwise invalid, an error is
+returned.
+
+For example, let's implement a function to add months to the current month:
+
+```sentinel
+func (m *root) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *root) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.time.AddDate(0, n, 0).Month()}
+}
+```
+
+You can now call the function. If today was in the month of September,
+the policy below would pass:
+
+```sentinel
+import "time"
+
+main = time.add_month(4).string == "January"
+```
+
+#### framework.Map
+
+The optional
+[`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+interface allows an alternative method to to present a namespace back to a
+Sentinel policy as a map.
+
+`framework.Map` is best paired with
+[`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys),
+which allows you to quickly fetch the necessary data via `Get` calls on the
+namespace:
+
+```
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil, nil
+}
+
+func (m *namespaceMonth) Map() (map[string]interface{}, error) {
+ return framework.MapFromKeys(m, []string{"string", "index"})
+}
+```
+
+#### framework.New
+
+The optional
+[`framework.New`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#New)
+interface can be implemented on a root namespace to add _methods_ to your
+namespaces.
+
+Data returned from a namespace is memoized and returned as a map, and is
+normally not callable - to call a function to operate on the data, you would
+need to create a new namespace from the top-level of the import, and then make a
+function call on the result within the same expression.
+
+Let's consider the [root example](#implementing-framework-root). Let's say,
+instead of hard-coding the time value at configuration, we wanted to load it
+on-demand using a `time.now` key, and allow `add_month` to be callable on that
+value. Our root and de-coupled time namespace would now look like:
+
+```
+type root struct{}
+
+func (m *root) Configure(raw map[string]interface{}) error { return nil }
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "now":
+ return &namespaceTime{Time: time.Now()}
+ }
+
+ return nil
+}
+
+func (m *root) New(data map[string]interface{}) (framework.Namespace, error) {
+ if v, ok := data["unix"]; ok {
+ if t, ok := v.(int64); !ok {
+ return &namespaceTime{Time: time.Unix(t, 0)}
+ }
+
+ return nil, fmt.Errorf("expected timestamp to be int64, got %T", v)
+ }
+
+ return nil, nil
+}
+
+type namespaceTime struct {
+ Time time.Time
+}
+
+func (m *namespaceTime) Get(key string) interface{} {
+ switch key {
+ case "unix":
+ return m.Time.Unix()
+
+ // case ...
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) Get(key string) (interface{}, error) {
+ return framework.MapFromKeys(m, []string{"unix", "..."})
+}
+
+func (m *namespaceTime) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.Time.AddDate(0, n, 0).Month()}
+}
+```
+
+Via this example, we can now create and assign a `namespaceTime` using `t = time.now`, and then call `t.add_month(months_to_add)` to return a
+`namespaceMonth` for the corresponding month.
+
+Methods on a namespace can also _modfiy_ the receiver data. So you can, for
+example, add a method named `increment_month` that takes no arguments, but
+increments the time stored in `namespaceTime.Time` by a month. Subsequent
+statements using the receiver would see the new value.
+
+Note that there are some restrictions and guidelines that you should take into
+account when using `framework.New`:
+
+* Only data that makes it back to a policy can be used as receiver data to
+ instantiate new namespaces. As such it's recommended to make use of
+ [`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+ and
+ [`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys)
+ to return all of the attribute data you need to construct new objects.
+* `framework.New` is only supported on the root namespace and has no effect on
+ namespaces below the root. Check all possible cases of receiver data within
+ the top-level `New` function and return the appropriate namespace based on the
+ input data.
+* Do not return general errors from your `New` method. When an unknown lookup is
+ made off of memoized data, it will hit your `New` method for possible
+ instantiation and key calls. This will ensure `undefined` is correctly
+ returned for these cases.
+* `framework.New` is designed to add utility to imports where calling methods on
+ a value is more intuitive than just making a function call, or just returning
+ all of a data set as a memoized map. Use it sparingly and avoid using it on
+ recursively complex data sets.
+
+## Testing
+
+The SDK exposes a testing framework to verify your plugin works as
+expected. This test framework integrates directly into `go test` so it
+is part of a familiar workflow.
+
+The test framework works by dynamically building your import and
+running it against the `sentinel` CLI to verify it behaves as expected.
+This ensures that your plugin builds, your plugin communicates via RPC
+correctly, and that the behavior within a policy is also correct.
+
+The example below shows a complete ready table-driven test for our time plugin.
+You can run this with a normal `go test`.
+
+```sentinel
+package main
+
+import (
+ "os"
+ "testing"
+
+ "github.com/hashicorp/sentinel-sdk"
+ plugintesting "github.com/hashicorp/sentinel-sdk/testing"
+)
+
+func TestMain(m *testing.M) {
+ exitCode := m.Run()
+ plugintesting.Clean()
+ os.Exit(exitCode)
+}
+
+func TestImport(t *testing.T) {
+ cases := []struct {
+ Name string
+ Data map[string]interface{}
+ Source string
+ }{
+ {
+ "month",
+ map[string]interface{}{"timestamp": 1495483674},
+ `main = subject.month.string == "September"`,
+ },
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.Name, func(t *testing.T) {
+ plugintesting.TestImport(t, plugintesting.TestImportCase{
+ Config: tc.Data,
+ Source: tc.Source,
+ })
+ })
+ }
+}
+```
+
+## Building Your Import
+
+To build your import, you must first implement the `main` function.
+The main function should just use the `rpc.Serve` method to serve the
+plugin over the Sentinel RPC layer:
+
+```sentinel
+package main
+
+import (
+ "github.com/hashicorp/sentinel-sdk"
+ "github.com/hashicorp/sentinel-sdk/rpc"
+)
+
+func main() {
+ rpc.Serve(&rpc.ServeOpts{
+ ImportFunc: func() sdk.Import {
+ return &framework.Import{Root: &root{}}
+ },
+ })
+}
+```
+
+You can then build this using `go build`. The output can be named
+anything. Once your plugin is built, [install it](/sentinel/extending/install)
+and try it out!
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/extending/index.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/extending/index.mdx
new file mode 100644
index 0000000000..8b99ad5e63
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/extending/index.mdx
@@ -0,0 +1,14 @@
+---
+page_title: Import Plugins
+sidebar_current: docs-extending
+description: Imports allow a Sentinel policy to access external data and add new functions.
+layout: docs
+---
+
+# Import Plugins
+
+Sentinel is built on a plugin-based architecture. [Imports](/sentinel/language/imports)
+can be implemented as plugins. Users of Sentinel are able to write new
+imports in order to access external data and add new functions.
+This section of the documentation explains how to use plugins, how to
+write plugins, how Sentinel internals work with plugins, and more.
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/extending/install.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/extending/install.mdx
new file mode 100644
index 0000000000..bc36d0585a
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/extending/install.mdx
@@ -0,0 +1,65 @@
+---
+page_title: Import Plugins
+sidebar_title: Install
+sidebar_current: docs-extending-install
+description: >-
+ Import plugins are installed by configuring the Sentinel-enabled application
+ with the path to the plugin, the name of the import, and any arguments needed
+ to launch the plugin.
+layout: docs
+---
+
+# Installing Import Plugins
+
+Import plugins are installed by configuring the Sentinel-enabled application
+with the path to the plugin, the name of the import, and any arguments needed
+to launch the plugin.
+
+-> **Not all Sentinel-enabled applications support plugins.** Please refer
+to the documentation of the specific Sentinel-enabled application. Some
+applications disable plugins for security reasons. For those that do
+enable plugins, the method to configure plugins may vary.
+
+## Installing Plugins
+
+Sentinel plugins are standalone executables. The Sentinel-enabled application
+launches these plugins and communicates with them via
+[RPC](https://en.wikipedia.org/wiki/Remote_procedure_call). To install
+a plugin, the first step is to download the plugin executable for the
+platform that the Sentinel-enabled application is running on.
+
+After downloading the plugin, you must configure the Sentinel-enabled
+application. This may be via a configuration file, an API call, or some
+other method. Please refer to the application-specific documentation for
+more details.
+
+Sentinel imports are configured with the following settings:
+
+* `name` (required) - The name of the import within Sentinel policies.
+ For example, `import "foo"`, where "foo" is the import name.
+ This is often specified as the key to the configuration.
+
+* `path` (required) - This is the path to the plugin executable.
+
+* `args` (optional) - This is a list of arguments to pass to the executable when
+ launched. By default, no arguments are given.
+
+* `env` (optional) - This is a set of environment variables to expose to
+ the plugin. By default, the same environment variables given to the
+ application are inherited by the plugin. This key can be used to add
+ additional variables.
+
+* `config` (optional) - This is a map of key/value pairs for configuring
+ the plugin. Please refer to the plugin documentation for available
+ configuration options. Some plugins may not require any configuration.
+
+After configuring the plugin, you may have to restart the application
+for it to become available.
+
+## Debugging Plugins
+
+Sentinel logs when it launches, configures, closes a plugin and more.
+
+To debug issues with a plugin, please refer to the logs of the
+Sentinel-enabled application. You may have to configure the application
+to show Sentinel logs. Please refer to the application-specific documentation.
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/functions/append.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/functions/append.mdx
new file mode 100644
index 0000000000..1b3e54b8e5
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/functions/append.mdx
@@ -0,0 +1,47 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Append'
+sidebar_title: 'append'
+sidebar_current: docs-funcs-append
+description: The built-in function `append` appends a value to the end of a list.
+layout: docs
+---
+
+# Builtin Function: append
+
+The built-in function `append` appends a value to the end of a list. The list
+is modified in-place. The return value will always be [undefined](/sentinel/language/undefined).
+
+`append` called on any value other than a list or undefined will result
+in an immediate error.
+
+Examples:
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+### Why does this function return undefined?
+
+This function returns `undefined` to avoid unintended side effects of the function in the context
+of a [rule](/sentinel/language/rules).
+
+For example, if `append` returned the list value as a result, the following policy would depend
+on the order in which rules are referenced:
+
+```sentinel
+list = []
+a = rule { append(list, 1) contains 1 }
+b = rule { append(list, 2) contains 2 }
+
+// Scenario 1: list becomes [1, 2]
+main = rule { a and b }
+
+// Scenario 2: list becomes [2, 1]
+main = rule { b and a }
+```
+
+This sort of side effect behavior introduces complex and difficult to track logical errors in programs.
+As a result, functions like this return `undefined` and modify values in-place.
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/functions/delete.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/functions/delete.mdx
new file mode 100644
index 0000000000..0a6340876d
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/functions/delete.mdx
@@ -0,0 +1,26 @@
+---
+page_title: 'Sentinel Language - Builtin Function: delete'
+sidebar_title: 'delete'
+sidebar_current: docs-funcs-delete
+description: The built-in function `delete` deletes an element from a map.
+layout: docs
+---
+
+# Builtin Function: delete
+
+The built-in function `delete` deletes an element from a map.
+
+If the element to delete does not exist, the map is left unchanged.
+Calling `delete` on a value that is not a map or
+[undefined](/sentinel/language/undefined)
+is an immediate error. The result of `delete` is always `undefined`.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/functions/error.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/functions/error.mdx
new file mode 100644
index 0000000000..1d06dfc432
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/functions/error.mdx
@@ -0,0 +1,16 @@
+---
+page_title: 'Sentinel Language - Builtin Function: error'
+sidebar_title: 'error'
+sidebar_current: docs-funcs-error
+description: >-
+ The built-in function `error` is used to exit immediately with an error
+ message.
+layout: docs
+---
+
+# Builtin Function: error
+
+The built-in function `error` is used to exit immediately with an error message.
+
+Please see the [page on errors](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/functions/index.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/functions/index.mdx
new file mode 100644
index 0000000000..0d900b33d0
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/functions/index.mdx
@@ -0,0 +1,14 @@
+---
+page_title: Sentinel Language - Builtin Functions
+sidebar_title: Builtin Functions
+sidebar_current: docs-funcs
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Language: Builtin Functions
+
+Builtin functions are
+[functions](/sentinel/language/functions)
+that are available in all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in function.
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/functions/keys.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/functions/keys.mdx
new file mode 100644
index 0000000000..36d99bc2b7
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/functions/keys.mdx
@@ -0,0 +1,23 @@
+---
+page_title: 'Sentinel Language - Builtin Function: keys'
+sidebar_title: 'keys'
+sidebar_current: docs-funcs-keys
+description: The built-in function `keys` returns the keys of a map.
+layout: docs
+---
+
+# Builtin Function: keys
+
+The built-in function `keys` returns the keys of a map.
+
+`keys` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned keys are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+```
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/functions/length.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/functions/length.mdx
new file mode 100644
index 0000000000..efe184c0f1
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/functions/length.mdx
@@ -0,0 +1,24 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Length'
+sidebar_title: 'length'
+sidebar_current: docs-funcs-length
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Builtin Function: length
+
+The built-in function `length` returns the length of a collection or string.
+
+The length of a string is the number of bytes in the string. It is not
+necessarilly the number of characters. The length of a collection is the
+number of elements in that collection. The length of
+[undefined](/sentinel/language/undefined) is `undefined`.
+
+Examples:
+
+```sentinel
+length("foo") // 3
+length([1, 2, 3, 4]) // 4
+length({ "key": "value" }) // 1
+```
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/functions/print.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/functions/print.mdx
new file mode 100644
index 0000000000..9d714195db
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/functions/print.mdx
@@ -0,0 +1,14 @@
+---
+page_title: 'Sentinel Language - Builtin Function: print'
+sidebar_title: 'print'
+sidebar_current: docs-funcs-print
+description: The built-in function `print` is used for logging and debugging.
+layout: docs
+---
+
+# Builtin Function: print
+
+The built-in function `print` is used for logging and debugging.
+
+Please see the [page on logging](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/functions/range.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/functions/range.mdx
new file mode 100644
index 0000000000..0a9a07aa6f
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/functions/range.mdx
@@ -0,0 +1,50 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Range'
+sidebar_title: 'range'
+sidebar_current: docs-funcs-range
+description: The built-in function `range` returns a list of numbers in a range.
+layout: docs
+---
+
+# Builtin Function: range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it
+defaults to `1`.
+
+Negative steps are permitted and count down from `start` towards `end`, instead
+of up.
+
+The range ends when the next value given by the sum of the last value and `step`
+would exceed `end`, regardless of if it has been reached.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4] - starts at 1 instead of 0
+range(1, 5, 2) // [1,3] - 3+2 does not satisfy r[-1] < end
+range(0, -3, -1) // [0,-1,-2] - example of negative range
+```
+
+Impossible ranges, or ranges where `start` will never reach `end` given `step`,
+yield an empty list.
+
+```
+range(1, 1) // [] - already starting at 1
+range(0, 1, -1) // [] - negative step will never reach 1
+```
+
+Supplying an undefined value to any argument of `range` yields an undefined
+value back.
+
+A range with a zero step is a runtime error.
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/functions/values.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/functions/values.mdx
new file mode 100644
index 0000000000..95b448f6c7
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/functions/values.mdx
@@ -0,0 +1,23 @@
+---
+page_title: 'Sentinel Language - Builtin Function: values'
+sidebar_title: 'values'
+sidebar_current: docs-funcs-values
+description: The built-in function `values` returns the values of a map.
+layout: docs
+---
+
+# Builtin Function: values
+
+The built-in function `values` returns the values of a map.
+
+`values` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned values are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+values(data) // [3, 2]
+```
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/guides/index.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/guides/index.mdx
new file mode 100644
index 0000000000..8577828033
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/guides/index.mdx
@@ -0,0 +1,12 @@
+---
+page_title: Documentation
+sidebar_current: guides-index
+description: TODO Guides
+layout: docs
+---
+
+# Sentinel Guides
+
+Welcome to the Sentinel Guides!
+
+TODO
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/imports/decimal.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/imports/decimal.mdx
new file mode 100644
index 0000000000..ae9b33011d
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/imports/decimal.mdx
@@ -0,0 +1,316 @@
+---
+page_title: 'Import: decimal'
+sidebar_title: 'decimal'
+sidebar_current: docs-imports-decimal
+description: |-
+ The decimal import provides functions for operating on numbers represented
+ as decimals.
+layout: docs
+---
+
+# Import: decimal
+
+The decimal import provides functions for operating on numbers represented
+as decimals.
+
+Binary floating-point numbers provided by the `float` type are unable to
+fully represent numbers like `1.1` and `2.2`. The decimal import can
+represent these numbers exactly, and so should be used when exactness is
+required.
+
+Decimals are created through the `new` function, which takes any type that
+can represent a number. Arguments to the decimal operators can also take
+a value of any of these types. The full list is:
+
+* float
+* int
+* string
+* existing decimal value
+
+### decimal.infinity(sign)
+
+Creates an infinite-value decimal. Infinite-value decimals are always larger
+or smaller, depending on sign, than any non-infinite decimals.
+
+```sentinel
+decimal.infinity(1) // +Infinity
+decimal.infinity(-1) // -Infinity
+```
+
+Also available as `decimal.inf`.
+
+### decimal.nan
+
+A decimal representing a "not-a-number" value. NaNs are not equal to any
+other number, even themselves.
+
+```sentinel
+decimal.new(-1).sqrt() // NaN
+decimal.new(0).mul(decimal.inf(1)) // NaN
+decimal.nan.is(decimal.nan) // false
+```
+
+### decimal.isinfinite(d)
+
+Evaluates to true if the decimal is infinite.
+
+```sentinel
+decimal.isinfinite(100) // false
+decimal.isinfinite("Infinity") // true
+```
+
+Also available as `decimal.isinf`.
+
+### decimal.isnan(d)
+
+Evaluates to true if the decimal is not-a-number.
+
+```sentinel
+decimal.isnan("NaN") // true
+```
+
+### decimal.new(v)
+
+Constructs a decimal from another value.
+
+```sentinel
+decimal.new("1.1") // Constructs a decimal from a string.
+decimal.new(2.2) // Constructs a decimal from a float.
+decimal.new(3) // Constructs a decimal from an integer.
+decimal.new("1.1234E+400") // Constructs a decimal from a string using E-notation.
+```
+
+## Type: number
+Numbers are represented internally by a sign, coefficient, and exponent.
+The value is calculated with the formula `sign * coefficient * 10^exponent`.
+Exponent is limited to 32 bits, and the coefficient is limited by the total
+precision.
+
+The maximum precision a number can represent is 100 digits. This includes
+both before and after the decimal place.
+
+### number.string
+
+A string representation of the decimal.
+
+```sentinel
+decimal.new(1.5).string // 1.5
+```
+
+### number.sign
+
+A number between `-1` and `+1` representing the sign of the decimal.
+
+```sentinel
+decimal.new(1.5).sign // 1
+```
+
+### number.coefficient
+
+The coefficient component of the decimal.
+
+```sentinel
+decimal.new(1.5).coefficient // 15
+```
+
+### number.exponent
+
+The exponent component of the decimal.
+
+```sentinel
+decimal.new(1.5).exponent // -1
+```
+
+### number.float
+
+A floating-point representation of the decimal.
+
+```sentinel
+decimal.new(1.5).float // 1.500000
+```
+
+### number.int
+
+An integer representation of the decimal. This always rounds down to the nearest integer.
+
+```sentinel
+decimal.new(1.5).int // 1
+```
+
+### number.is(v)
+
+Test for equality with another value.
+
+```sentinel
+decimal.new(1.5).is(1) // false
+```
+
+### number.is_not(v)
+
+Test for inequality with another value.
+
+```sentinel
+decimal.new(1.5).is_not(1) // true
+```
+
+### number.less_than(v)
+
+Test that the decimal is less than another value.
+Can also be called as `number.lt(v)`
+
+```sentinel
+decimal.new(1.5).less_than(1) // false
+```
+
+### number.less_than_or_equals(v)
+
+Test that the decimal is less than or equals to another value.
+Can also be called as `number.lte(v)`
+
+```sentinel
+decimal.new(1.5).less_than_or_equals(1) // false
+```
+
+### number.greater_than(v)
+
+Test that the decimal is greater than another value.
+Can also be called as `number.gt(v)`
+
+```sentinel
+decimal.new(1.5).greater_than(1) // true
+```
+
+### number.greater_than_or_equals(v)
+
+Test that the decimal is greater than or equals to another value.
+Can also be called as `number.gte(v)`
+
+```sentinel
+decimal.new(1.5).greater_than_or_equals(1) // true
+```
+
+### number.add(v)
+
+Add another number to the decimal.
+
+```sentinel
+decimal.new(1.5).add(1) // 2.5
+```
+
+### number.subtract(v)
+
+Subtract another number from the decimal.
+Can also be called as `number.sub(v)`
+
+```sentinel
+decimal.new(1.5).subtract(1) // 0.5
+```
+
+### number.multiply(v)
+
+Multiply the decimal by a number.
+Can also be called as `number.mul(v)`
+
+```sentinel
+decimal.new(1.5).multiply(2) // 3.0
+```
+
+### number.divide(v)
+
+Divide the decimal by a number.
+Can also be called as `number.div(v)`
+
+```sentinel
+decimal.new(3.0).divide(1.5) // 2
+```
+
+### number.modulo(v)
+
+Find the remainder after dividing the decimal by a number.
+Can also be called as `mod`, `remainder`, or `rem`.
+
+```sentinel
+decimal.new(1.5).modulo(1) // 0.5
+```
+
+### number.power(v)
+
+Raise the decimal to a power.
+Can also be called as `number.pow(v)`
+
+```sentinel
+decimal.new(1.5).power(2) // 2.25
+```
+
+### number.exp()
+
+The natural exponent of the decimal. This calculates `e^number`.
+
+```sentinel
+decimal.new(1.5).exp() // 4.4816...
+```
+
+### number.loge()
+
+The natural logarithm of the decimal.
+Can also be called as `number.ln()`
+
+```sentinel
+decimal.new(1.5).loge() // 0.4054...
+```
+
+### number.log10()
+
+The base-10 logarithm of the decimal.
+Can also be called as `number.log()`
+
+```sentinel
+decimal.new(1.5).log10() // 0.1760...
+```
+
+### number.square_root()
+
+The square root of the decimal.
+Can also be called as `number.sqrt()`
+
+```sentinel
+decimal.new(1.5).square_root() // 1.2247...
+```
+
+### number.ceiling()
+
+Round the decimal to the smallest integer greater or equal to itself.
+Can also be called as `number.ceil()`
+
+```sentinel
+decimal.new(1.5).ceiling() // 2
+```
+
+### number.floor()
+
+Round the decimal to the largest integer less than or equal to itself.
+
+```sentinel
+decimal.new(1.5).floor() // 1
+```
+
+### number.absolute()
+
+The absolute value of the decimal.
+Can also be called as `number.abs()`
+
+```sentinel
+decimal.new(1.5).absolute() // 1.5
+decimal.new(-1.5).absolute() // 1.5
+```
+
+### number.negate()
+
+The negated value of the decimal. A positive decimal will become negative,
+and a negative decimal will become positive.
+Can also be called as `number.neg()`
+
+```sentinel
+decimal.new(1.5).negate() // -1.5
+decimal.new(-1.5).negate() // 1.5
+```
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/imports/http.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/imports/http.mdx
new file mode 100644
index 0000000000..c00c62eeeb
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/imports/http.mdx
@@ -0,0 +1,305 @@
+---
+page_title: 'Import: http'
+sidebar_title: 'http'
+sidebar_current: docs-imports-http
+description: The http import enables the use of HTTP-accessible data from outside the runtime in Sentinel policy rules.
+layout: docs
+---
+
+# Import: http
+
+The http import enables the use of HTTP-accessible data from outside
+the runtime in Sentinel policy rules.
+
+At the center of the import is the [`get()`](#clientgeturl_or_request) function, which issues a GET
+request and returns a `response` type:
+
+```sentinel
+import "http"
+
+resp = http.get("https://example.hashicorp.com")
+main = rule { resp.body contains "something" }
+```
+
+By default, any request response that is not a successful "200 OK" HTTP
+response causes a policy error. See
+[`accept_status_codes`](#clientaccept_status_codeslist) for more information and
+how to customize this behavior.
+
+For more advanced requests which require setting request headers, use a
+[`request` type](#type-request):
+
+```sentinel
+import "http"
+import "json"
+
+param token
+
+req = http.request("https://example.hashicorp.com").with_header("Authorization", "token "+token)
+resp = json.unmarshal(http.get(req).body)
+main = rule { resp["some_key"] is true }
+```
+
+The `with_header` function above returns the `request` object with the
+`Authorization` HTTP header set to value of the variable `some_value`. Many
+of the methods within the http import API are designed around this concept
+of method chaining, allowing for complex building of HTTP requests
+encapsulated in functions. The following is an idiomatic example further
+demonstrating this pattern:
+
+```sentinel
+import "http"
+import "json"
+
+param github_user
+param github_token
+
+client = func(req) {
+ return http.with_timeout(5).with_retries(3)
+}
+
+github_request = func(path) {
+ full_url = "https://api.github.com" + path
+ return http.request(full_url).
+ with_basic_auth(github_user, github_token).
+ with_header("Accept", "application/vnd.github.v3+json").
+ with_header("Custom", "foo"),
+}
+
+default_response = client.get(github_request("/example"))
+
+# Override the Custom header for this single request
+custom_header_response = json.unmarshal(
+ client.get(
+ github_request("/example").with_header("Custom", "bar"),
+ ),
+)
+
+# Override the timeout value for a known slower request
+slow_response = json.unmarshal(
+ client.with_timeout(15).get(github_request("/slow-endpoint")),
+)
+
+some_key_is_true = rule { json.unmarshal(default_response.body)["some_key"] is true }
+body_contains_text = rule { custom_header_response.body contains "something" }
+response_is_ok = rule { slow_response.status_code is 200 }
+
+main = rule { some_key_is_true and body_contains_text and response_is_ok }
+```
+
+### http.client
+
+Retrieve the base HTTP client with default settings. The returned client may be
+configured by calling configuration functions on it; all of these configuration
+functions on are also aliased as top-level functions in this namespace. See
+[`client`](#type-client) below.
+
+### http.request(url)
+
+Create a new [`request`](#type-request) to the specified `url` (string). A
+`request` type enables customization of headers and other concerns before then
+providing the constructed `request` to [`get()`](#clientgeturl_or_request).
+
+```sentinel
+req = http.request("example.hashicorp.com/foo").with_header("Foo", "bar")
+resp = http.get(req)
+```
+
+## Type: client
+
+All of the following configuration functions on the default client are also
+aliased as top-level functions in the import. This allows a short-hand (and
+idiomatic) way of configuring a new client without having to directly access
+`http.client` each time:
+
+```sentinel
+# The following two lines are semantically the same:
+resp = http.client.with_timeout(5).get(url)
+resp = http.with_timeout(5).get(url)
+```
+
+### client.get(url_or_request)
+
+Issue a GET request with a URL string or a `request` type. Returns a `response`
+type.
+
+When `url_or_request` is a string, a GET request is made with the URL
+specified by the string. To customize HTTP headers and other settings, pass
+a request. By default, a request has a timeout value of 10 seconds and 1
+retry. These settings can be adjusted with [`with_timeout()`](#clientwith_timeoutinteger) and
+[`with_retries()`](#clientwith_retriesinteger), respectively.
+
+If the response is one of the following redirect codes, `get()` follows the
+redirect, up to a maximum of 10 redirects:
+
+301 (Moved Permanently)
+302 (Found)
+303 (See Other)
+307 (Temporary Redirect)
+308 (Permanent Redirect)
+
+If the redirect limit is exceeded, the result is a policy error.
+
+By default, after any redirects are followed (up to the maximum), any request
+response that is not a successful "200 OK" response causes a policy error. See
+[`accept_status_codes`](#clientaccept_status_codeslist) for more information
+and how to customize this behavior.
+
+### client.accept_status_codes(list)
+
+Configures a client to not automatically cause a policy failure if
+the resulting response's status code is in `list`. Any status code received
+that is not present in this list will trigger a runtime error.
+
+By default, any request response that is not a successful "200 OK" response
+causes a policy error. This is for convenience, as the most common scenario
+by far is to receive a response and immediately signal a policy error if the
+status code is not 200. Use this scoping to specify a list of non-redirect
+HTTP codes that you wish to accept without error and evaluate the response
+yourself.
+
+Redirects are not considered final status codes in this context. That is,
+redirects are always followed up to the maximum allowed value (see [`get()`](#clientgeturl_or_request))
+and the final response code in the redirect chain is validated against
+`list`.
+
+```sentinel
+resp = http.accept_status_codes([200, 404]).get(url)
+main = rule { resp.status_code is 404 }
+```
+
+### client.accepted_status_codes
+
+Returns a list of the client's accepted status codes.
+
+```sentinel
+client = http.accept_status_codes([200, 404])
+main = rule { client.accepted_status_codes contains 404 }
+```
+
+### client.with_retries(integer)
+
+Sets the maximum number of retries, specified by `integer`, that should be
+attempted on an unsuccessful request. An unsuccessful request is considered
+to be any 5xx response except `501 (Not Implemented)` or a request timeout.
+By default, one retry is attempted.
+
+The maximum number of retries is 10, for a total of 11 request attempts.
+When a request exceeds the maximum number of retries without a response, the
+result is a policy error. Specifying a number larger than this will result
+in a runtime error.
+
+Between each retry, an exponentially increasing delay is observed, allowing
+time for the server to recover. This delay starts at 1 second for the first
+retry and increases each attempt thereafter. The maximum delay for a single
+attempt is 30 seconds.
+
+Retries can be disabled by specifying 0.
+
+### client.retries
+
+Returns the client's configured number of retries as an integer.
+
+### client.with_timeout(integer)
+
+Sets the request timeout to the number of seconds indicated by `integer`.
+
+Each individual request performed by the HTTP client will be limited by the
+given request timeout. This timeout includes connection time, any redirects,
+and reading the response body.
+
+A maximum of 30 seconds is allowed. Specifying a number larger than this
+will result in a runtime error.
+
+By default, requests will time out after 10 seconds.
+
+### client.timeout
+
+Returns the client's configured timeout in whole seconds as an integer.
+
+### client.without_certificate_verification()
+
+Skips verification of TLS certificates during secure HTTP operations,
+allowing for requests to `https://` endpoints which are secured with a TLS
+certificate signed by an untrusted certificate authority. Takes no arguments.
+
+By default, the HTTP client will trigger a runtime error if a TLS certificate
+presented by a server is from an untrusted certificate authority.
+
+Do not change this setting unless you are aware of the risks involved.
+Without verification, TLS accepts any certificate presented by the server
+and any host name in that certificate. In this mode, TLS is susceptible to
+man-in-the-middle attacks. This option should only be used for testing or in
+trusted networks with self-signed certificates.
+
+```sentinel
+http.without_certificate_verification().get(url)
+```
+
+### client.certificate_verification
+
+Returns true if the client requires TLS certificates to be verifiable.
+
+## Type: request
+
+### request.url
+
+Returns the request URL as a string.
+
+### request.headers
+
+Returns the configured request headers as a string-keyed map of strings.
+
+### request.with_header(key, value)
+
+Returns a `request` with the header `key` (string) set to `value` (string).
+
+Values are overridden on subsequent calls to the same `key`. To specify
+multiple values, use the existing value when setting the new one.
+
+```sentinel
+original = http.request("http://example.hashicorp.com").with_header("Foo", "bar")
+overridden = original.with_header("Foo", "baz")
+multi_value = original.with_header("Foo", original.headers["Foo"] + ",baz")
+
+main = rule {
+ original.headers["Foo"] is "bar" and
+ overridden.headers["Foo"] is "baz" and
+ multi_value.headers["Foo"] is "bar,baz"
+}
+```
+
+### request.with_basic_auth(username, password)
+
+Returns a `request` with the `Authorization` header set to use HTTP Basic
+Authentication with the provided username and password.
+
+Note that with HTTP Basic Authentication the provided username and password
+are encoded, but not encrypted.
+
+## Type: response
+
+### response.status_code
+
+The response status code as an integer, e.g. `200`.
+
+### response.headers
+
+The response headers as a map.
+
+### response.body
+
+The response body as a string. Callers should unmarshal the content to a useful
+representation as necessary based on the content type. For example, if the
+response is in JSON:
+
+```sentinel
+import "http"
+import "json"
+
+# This example endpoint returns {"some_key": true}
+req = http.request("https://example.hashicorp.com/some-json")
+
+resp = json.unmarshal(http.get(req).body)
+main = rule { keys(resp) contains "some_key" and resp["some_key"] is true }
+```
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/imports/index.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/imports/index.mdx
new file mode 100644
index 0000000000..e7d16b2ccb
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/imports/index.mdx
@@ -0,0 +1,19 @@
+---
+page_title: Sentinel Language - Standard Imports
+sidebar_title: Standard Imports
+sidebar_current: docs-imports
+description: >-
+ Standard Imports are the built-in imports that are available to all Sentinel
+ policies.
+layout: docs
+---
+
+# Standard Imports
+
+Standard Imports are the built-in [imports](/sentinel/language/imports)
+that are available to all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in import.
+
+Sentinel-embedded applications can choose to whitelist or blacklist certain
+standard imports. Please reference the documentation for the Sentinel-enabled
+application you're using to determine if all standard imports are available.
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/imports/json.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/imports/json.mdx
new file mode 100644
index 0000000000..45a75d30d5
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/imports/json.mdx
@@ -0,0 +1,32 @@
+---
+page_title: 'Import: json'
+sidebar_title: 'json'
+sidebar_current: docs-imports-json
+description: The json import enables a Sentinel policy to parse and access a JSON document.
+layout: docs
+---
+
+# Import: json
+
+The json import enables a Sentinel policy to parse and access a JSON
+document.
+
+### json.unmarshal(obj)
+
+Unmarshals the JSON object `obj` into a native Sentinel structure.
+
+All native JSON types can be represented perfectly as Sentinel native types.
+
+```sentinel
+// Typically the input for this would come from an external source.
+config = json.unmarshal("{ \"foo\": 42 }")
+config.foo // 42
+config.bar // undefined (as usual for accessing a non-existent map key)
+```
+
+### json.marshal(obj)
+
+Marshals the Sentinel object `obj` into a JSON object encoded as a string.
+
+Functions and `undefined` cannot be natively encoded as JSON. If values of
+either type are found in the structure, this will return undefined.
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/imports/runtime.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/imports/runtime.mdx
new file mode 100644
index 0000000000..5bfa156825
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/imports/runtime.mdx
@@ -0,0 +1,17 @@
+---
+page_title: 'Import: runtime'
+sidebar_title: 'runtime'
+sidebar_current: docs-imports-runtime
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: runtime
+
+The runtime import contains various information about the Sentinel runtime. It
+can be used to get information about the runtime as it's embedded in the CLI or
+a specific integration, such as validating a specific runtime version.
+
+### runtime.version
+
+Returns the version of Sentinel within this implementation.
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/imports/sockaddr.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/imports/sockaddr.mdx
new file mode 100644
index 0000000000..c200e69796
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/imports/sockaddr.mdx
@@ -0,0 +1,42 @@
+---
+page_title: 'Import: sockaddr'
+sidebar_title: 'sockaddr'
+sidebar_current: docs-imports-sockaddr
+description: The sockaddr import makes working with IP addresses easy.
+layout: docs
+---
+
+# Import: sockaddr
+
+The sockaddr import makes working with IP addresses easy.
+
+### sockaddr.is_contained(outer, inner)
+
+The is_contained function returns true if `inner` is an address that
+is contained within `outer`.
+
+```sentinel
+sockaddr.is_contained("192.168.0.0/24", "192.168.0.32") // true
+sockaddr.is_contained("192.168.0.0/24", "192.174.1.1") // false
+```
+
+### sockaddr.is_equal(addr1, addr2)
+
+The is_equal function returns true if addr1 is equal to addr2.
+
+```sentinel
+sockaddr.is_equal("192.168.12.24", "192.168.12.24") // true
+```
+
+### sockaddr.is_ipv4(addr)
+
+The is_ipv4 function returns true if addr is an IPv4 address.
+
+```sentinel
+sockaddr.is_ipv4("192.168.12.24") // true
+sockaddr.is_ipv4("2001:0db8:85a3:0000:0000:8a2e:0370:7334") // false
+```
+
+### sockaddr.is_ipv6(addr)
+
+The is_ipv6 function returns true if addr is an IPv6 address.
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/imports/strings.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/imports/strings.mdx
new file mode 100644
index 0000000000..bdacfc68a1
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/imports/strings.mdx
@@ -0,0 +1,92 @@
+---
+page_title: 'Import: strings'
+sidebar_title: 'strings'
+sidebar_current: docs-imports-strings
+description: The strings import exposes common string operations.
+layout: docs
+---
+
+# Import: strings
+
+The strings import exposes common string operations.
+
+### strings.has_prefix(s, prefix)
+
+Returns true if `s` starts with `prefix`.
+
+```sentinel
+strings.has_prefix("billing-id", "billing-") // true
+strings.has_prefix("bill-id", "billing-") // false
+```
+
+### strings.has_suffix(s, suffix)
+
+Returns true if `s` ends with `suffix`.
+
+```sentinel
+strings.has_suffix("billing-id", "id") // true
+strings.has_suffix("billing-name", "id") // false
+```
+
+### strings.join(a, sep)
+
+Joins a list with the string supplied by `sep`.
+
+Multi-dimensional lists are supported, and non-string primitive
+types will be converted to their string equivalents. Maps and
+other complex non-list types are not supported.
+
+```sentinel
+strings.join(["foo", "bar", "baz"], ".") // "foo.bar.baz"
+strings.join([["foo", "bar"], "baz"], ".") // "foo.bar.baz"
+strings.join(["a", 1, 1.01, true], ",") // "a,1,1.01,true"
+```
+
+### strings.trim_prefix(s, prefix)
+
+Trim the prefix from the string `s`. If the string doesn't have the
+prefix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_prefix("billing-id", "billing-") // "id"
+strings.trim_prefix("bill-id", "billing-") // "bill-id"
+```
+
+### strings.trim_suffix(s, suffix)
+
+Trim the suffix from the string `s`. If the string doesn't have the
+suffix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_suffix("billing-id", "-id") // "billing"
+strings.trim_suffix("bill-id", "-foo") // "bill-id"
+```
+
+### strings.to_lower(s)
+
+Lowercase the string s.
+
+```sentinel
+strings.to_lower("FoO") // "foo"
+```
+
+### strings.to_upper(s)
+
+Uppercase the string s.
+
+```sentinel
+strings.to_upper("FoO") // "FOO"
+```
+
+### strings.split(s, sep)
+
+Split the string 's' via a substring 'sep'. If 'sep' is not contained in
+'s', the return value will be a single-element list containing only 's'.
+As a special-case, if the input is already a list, it will be returned
+as-is. This allows calling the split function when not knowing if the input
+is already a list or not.
+
+```sentinel
+strings.split("foo,bar,baz", ",") // ["foo", "bar", "baz"]
+strings.split(["foo", "bar", "baz"], ",") // ["foo", "bar", "baz"]
+```
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/imports/time.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/imports/time.mdx
new file mode 100644
index 0000000000..bad99ce9e0
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/imports/time.mdx
@@ -0,0 +1,258 @@
+---
+page_title: 'Import: time'
+sidebar_title: 'time'
+sidebar_current: docs-imports-time
+description: The time import provides access to the execution time and time functions.
+layout: docs
+---
+
+# Import: time
+
+The time import provides access to the execution time and time functions.
+
+During the execution of a policy the time is fixed to the moment of
+execution. This gives the illusion that a policy instantly executes at a
+single moment of time.
+
+The import uses Coordinated Universal Time (UTC).
+
+As an example of this, if a policy accessed `time.now.second` multiple times,
+for each access the same value would be returned even if they are several seconds apart.
+This is the execution `timespace`, which is mapped to `time.now`.
+
+It is also possible to run functions on a custom time by using the
+`time.load()` function to create a new timespace from the specified value.
+See the documentation for available actions on timespaces.
+
+Times specified as the reference time or via the `time.load()` function can
+be specified in a `timeish` fashion. This is either one of:
+
+* The number of seconds since the Unix epoch (Thursday, 1 January 1970,
+ 00:00:00 UTC),
+* A timestamp matching the format outlined in RFC3339, either in the
+ format `2006-01-02T15:04:05+07:00`, which includes a timezone offset, or
+ `2006-01-02T15:04:05Z`, which indicates UTC.
+
+### time.now
+
+Create a `timespace` locked either to execution time or, if set, the
+reference time. In a given execution, this will always produce the same
+value.
+
+```sentinel
+time.now // {"day": 17, "hour": 23, "minute": 19, "month": 11 ... }
+```
+
+### time.load(timeish)
+
+Create a timespace using the given value. Please see the import
+documentation for valid values for "timeish".
+
+```sentinel
+time.load("2019-11-16T01:20:30Z") // {"day": 16, "hour": 1, "minute": 20, "month": 11 ... }
+```
+
+### time.nanosecond
+
+A nanosecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.nanosecond * 1) // 2019-11-16T01:20:30.000000001Z
+```
+
+### time.microsecond
+
+A microsecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.microsecond * 1) // 2019-11-16T01:20:30.000001Z
+```
+
+### time.millisecond
+
+A millisecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.millisecond * 1) // 2019-11-16T01:20:30.001Z
+```
+
+### time.second
+
+A second duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.second * 1) // 2019-11-16T01:20:31Z
+```
+
+### time.minute
+
+A minute for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.minute * 1) // 2019-11-16T01:21:30Z
+```
+
+### time.hour
+
+An hour duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+## Type: timespace
+
+### timespace.hour
+
+The hour from 0 to 23.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").hour // 1
+```
+
+### timespace.minute
+
+The minute from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").minute // 20
+```
+
+### timespace.second
+
+The second from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").second // 30
+```
+
+### timespace.day
+
+The day of the month, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").day // 16
+```
+
+### timespace.month
+
+The month of the year as an integer, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month // 11
+```
+
+### timespace.month_name
+
+The name of the month of the year, in English. Example: `January`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month_name // November
+```
+
+### timespace.weekday
+
+The weekday as an integer, where 0 is Sunday and 6 is Saturday.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday // 6
+```
+
+### timespace.weekday_name
+
+The name of the day of the week, in English. Example: `Sunday`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday_name // Saturday
+```
+
+### timespace.year
+
+The year as an integer.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").year // 2019
+```
+
+### timespace.unix
+
+The number of seconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix // 1573867230
+```
+
+### timespace.unix_nano
+
+The number of nanoseconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix_nano // 1573867230000000000
+```
+
+### timespace.zone
+
+The timezone offset, in seconds. This can be a negative number to indicate
+time west of UTC.
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone // -28800
+```
+
+### timespace.zone_string
+
+The timezone offset, in the format `+HH:MM` (example: `-08:00` for PST).
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone_string // -08:00
+```
+
+### timespace.before(timeish_or_timespace)
+
+Check if the time in the timespace is before the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").before("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.after(timeish_or_timespace)
+
+Check if the time in the timespace is after the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").after("2019-11-15T01:20:30Z") // true
+```
+
+### timespace.equal(timeish_or_timespace)
+
+Check if two times are equivalent
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").equal("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.add(duration)
+
+Add a duration in nanoseconds to the time in the timespace and return a new timespace. The
+duration can be negative. The duration should be specified as an integer
+multiplied with the correct unit.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+### timespace.sub(timeish_or_timespace)
+
+Subtract a time from the time in the timespace and return a duration in nanoseconds.
+
+```sentinel
+// Gives a duration of 3 hours.
+duration = time.load(
+ "2019-11-16T01:20:30Z",
+).sub("2019-11-16T04:20:30Z")
+
+// Returns 2019-11-16T01:20:30Z
+time.load(
+ "2019-11-16T04:20:30Z",
+).add(duration)
+```
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/imports/types.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/imports/types.mdx
new file mode 100644
index 0000000000..8fd52cef1e
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/imports/types.mdx
@@ -0,0 +1,27 @@
+---
+page_title: 'Import: types'
+sidebar_title: 'types'
+sidebar_current: docs-imports-types
+description: The types import enables a Sentinel policy to parse an object's type.
+layout: docs
+---
+
+# Import: types
+
+The types import enables a Sentinel policy to parse an object's type.
+
+### types.type_of(obj)
+
+Returns the object's type as a string
+
+```sentinel
+import "types"
+
+// Typically the inputs would come from an external source.
+is_boolean = rule { types.type_of(true) is "bool" }
+is_string = rule { types.type_of("Hello!") is "string" }
+is_integer = rule { types.type_of(42) is "int" }
+is_float = rule { types.type_of(42.123) is "float" }
+
+main = rule { is_boolean and is_string and is_integer and is_float }
+```
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/imports/units.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/imports/units.mdx
new file mode 100644
index 0000000000..d1bff53736
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/imports/units.mdx
@@ -0,0 +1,40 @@
+---
+page_title: 'Import: units'
+sidebar_title: 'units'
+sidebar_current: docs-imports-units
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: units
+
+The units import provides access to quick calculations for various byte
+units.
+
+Note that this import uses base-2 terminology:
+
+* One kilobyte is 1024 bytes
+* One megabyte is 1024^2 = 1048576 bytes
+* One gigabyte is 1024^3 = 1073741824 bytes
+* One terabyte is 1024^4 = 1099511627776 bytes
+* One petabyte is 1024^5 = 1125899906842624 bytes
+
+### units.kilobyte
+
+The base-2 representation of a kilobyte (1024 bytes).
+
+### units.megabyte
+
+The base-2 representation of a megabyte (1048576 bytes).
+
+### units.gigabyte
+
+The base-2 representation of a gigabyte (1073741824 bytes).
+
+### units.terabyte
+
+The base-2 representation of a terabyte (1099511627776 bytes).
+
+### units.petabyte
+
+The base-2 representation of a petabyte (1125899906842624 bytes).
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/index.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/index.mdx
new file mode 100644
index 0000000000..d8b540ef4f
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/index.mdx
@@ -0,0 +1,31 @@
+---
+layout: 'docs'
+page_title: 'Documentation'
+sidebar_current: 'docs-home'
+description: |-
+ Sentinel is a language and framework for policy built to be embedded in existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+[introduction](/sentinel/intro) to understand what Sentinel is, how it
+compares to other software, and more.
+
+
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/internals/index.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/internals/index.mdx
new file mode 100644
index 0000000000..fa74af613d
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/internals/index.mdx
@@ -0,0 +1,18 @@
+---
+page_title: Sentinel Internals
+sidebar_title: Internals
+sidebar_current: docs-internals
+description: Imports allow a Sentinel policy to access external data and add new functions.
+layout: docs
+---
+
+# Sentinel Internals
+
+This section covers the internals of Sentinel and explains how evaluation
+occurs, plugins are executed, etc. The goal of this section is to remove
+any notion of "magic" from Sentinel to allow you to trust and understand what
+Sentinel is doing.
+
+-> **Note:** Knowledge of Sentinel internals is not required to use Sentinel.
+If you aren't interested in the internals of Sentinel, you may safely skip this
+section.
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/internals/plugins.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/internals/plugins.mdx
new file mode 100644
index 0000000000..6c0595fa7a
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/internals/plugins.mdx
@@ -0,0 +1,64 @@
+---
+page_title: Plugin Internals
+sidebar_title: Plugins
+sidebar_current: docs-internals-plugins
+description: Imports allow a Sentinel policy to access external data and add new functions.
+layout: docs
+---
+
+# Plugin Internals
+
+Sentinel is built on a plugin-based architecture to support
+[imports](/sentinel/extending). This allows anybody to extend
+Sentinel to support new sources of data for making policy decisions as well
+as to expose new functions for working with data. This page documents
+the internals of how plugins work, not how to write them.
+
+If you're interested in developing an import plugin, please see the
+[developing an import plugin page](/sentinel/extending/dev).
+
+~> **Advanced Topic!** This page covers technical details of Sentinel. You
+don't need to understand these details to effectively use Sentinel. The details
+are documented here for those who wish to learn about them.
+
+## Basics
+
+Sentinel plugins are built on top of the HashiCorp
+[go-plugin system](https://github.com/hashicorp/go-plugin). This is the
+same plugin system powering all pluggable HashiCorp tools such as
+[Terraform](https://www.terraform.io),
+[Vault](https://www.vaultproject.io), and more. go-plugin is a system
+that has been used in production for millions of users for over 5 years.
+
+Plugins are executable binaries. Sentinel is responsible for launching
+and managing the lifecycle of plugins. Once a plugin is running, Sentinel
+communicates with the plugin via [gRPC](https://grpc.io/). The
+[protocol is open source](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/import.proto)
+to allow anyone to write a Sentinel plugin.
+
+## Lifecycle
+
+Sentinel launches plugins and is responsible for plugin lifecycle.
+
+The runtime optimizes for latency by launching the plugin as soon as
+it is configured, rather than when a policy requires it. This ensures that
+the plugin is ready to be used immediately. A plugin is only closed when
+Sentinel is reconfigured to no longer allow that plugin or if the
+Sentinel-enabled application is closing.
+
+When a plugin is removed from the configuration, Sentinel may wait to
+shut down the plugin until all currently executing policies that are using
+that plugin complete. New policy executions will not be allowed to use
+the old plugins.
+
+Plugins are automatically restarted if they shut down unexpectedly.
+Policies that were executing while this happens may fail. The Sentinel
+system is improving to more gracefully understand locations where it is
+safe to retry an execution.
+
+## Communication
+
+An [initial handshake](https://github.com/hashicorp/go-plugin/blob/master/docs/internals.md)
+is done via stdout to determine the main communication location. Following
+the handshake, communication will either occur on a local Unix domain socket
+or via a local-only TCP socket.
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/language/arithmetic.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/language/arithmetic.mdx
new file mode 100644
index 0000000000..3a341db72e
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/language/arithmetic.mdx
@@ -0,0 +1,68 @@
+---
+page_title: Sentinel Language - Arithmetic
+sidebar_title: Arithmetic
+sidebar_current: docs-language-arith
+description: >-
+ Sentinel supports arithmetic operators for integers and floats. Sentinel
+ supports sum, difference, product, quotient, and remainder.
+layout: docs
+---
+
+# Language: Arithmetic
+
+Sentinel supports arithmetic operators for integers and floats. Sentinel
+supports sum, difference, product, quotient, and remainder.
+
+```plaintext
++ sum
+* difference
+* product
+/ quotient
+% remainder
+```
+
+These operators work in a typical infix style:
+
+```sentinel
+4 + 8 // 12
+8 * 2 // 16
+8 / 4 // 2
+8 / 5 // 1
+8 % 5 // 3
+```
+
+## Order of Operations
+
+Arithmetic follows a standard mathematical order of operations.
+Grouping with parentheses can be used to affect ordering.
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+(4 + 5) * 2 // 18
+```
+
+A full table of operator precendence can be found on the
+[boolean expressions page](/sentinel/language/boolexpr). This
+shows how arithmetic operators relate to other operators.
+
+## Integer Division
+
+Integer division with a remainder rounds down to the nearest integer.
+Example: `8 / 3 is 2`.
+
+If the divisor is zero, an error occurs.
+
+## Mixed Numeric Operations
+
+Mixed numeric operations between integer and floating-point values are
+permitted. The result is a floating-point operation with the integer converted
+to a floating-point value for purposes of calculation.
+
+```sentinel
+import "types"
+
+a = 1.1 + 1 // 2.1
+types.type_of(a) // "float"
+```
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/language/boolexpr.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/language/boolexpr.mdx
new file mode 100644
index 0000000000..86432c4153
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/language/boolexpr.mdx
@@ -0,0 +1,193 @@
+---
+page_title: Sentinel Language - Boolean Expressions
+sidebar_title: Boolean Expressions
+sidebar_current: docs-language-boolexpr
+description: >-
+ Boolean expressions are expressions that evaluate to a boolean value from the
+ result of a combination of one or more comparisons and logical operators.
+layout: docs
+---
+
+# Language: Boolean Expressions
+
+Boolean expressions are expressions that evaluate to a boolean value
+from the result of a combination of one or more comparisons and logical
+operators. Boolean expressions form the basis of policy since policy can be broken
+down to a set of logical decisions that turn into true or false.
+
+A single boolean expression is the body of a [rule](/sentinel/language/rules).
+Additionally, boolean expressions are used with [conditionals](/sentinel/language/conditionals),
+and more.
+
+## Order of Operations
+
+The precedence of operators is shown below. Operators with higher
+precedence values (larger numbers) are evaluated first. Operators with
+the same precedence value are evaluated left-to-right.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Examples of this precedence are shown below:
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+```
+
+## Logical Operators
+
+Logical operators are used to change or combine logical expressions.
+There are three binary operators `and`, `or`, and `xor`. There is
+a single unary operator `not` (which can also be specified as `!`).
+
+The binary operators require two boolean operands and the unary
+operator requires a single boolean operand. In both case, the operands
+are values or expressions that are boolean types.
+
+Below is a basic explanation of the logical operators directly from
+the specification:
+
+```sentinel
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+Using parentheses to group boolean expressions, you can combine
+boolean expressions to become more complex or affect ordering:
+
+```sentinel
+(p and q) or r
+p and (q or r)
+```
+
+## Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+For any comparison, the two operands must be equivalent types. The one
+exception are integers and floats. When an integer is compared with
+a float, the integer is promoted to a floating point value.
+
+The available comparison operators are:
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+The behavior of `is` with `==` and `is not` with `!=` is identical.
+
+Example comparisons:
+
+```sentinel
+name is "Mitchell"
+idnumber > 42
+idnumber <= 12
+```
+
+Using the logical operators, these can be combined:
+
+```sentinel
+name is "Mitchell" and idnumber > 42
+```
+
+The language specification provides more detail on exactly
+[how comparison behaves](/sentinel/language/spec#comparison-operators).
+
+## Set Operators
+
+The set operators `contains` and `in` test for inclusion in a collection
+(a list or map).
+
+Set operators may 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.
+
+`contains` tests if the left-hand collection contains the right-hand value.
+`in` tests if the right-hand collection contains the left-hand value. For
+maps, "contains" looks at the keys, not the values.
+
+Examples:
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+```
+
+## Matches Operator
+
+The `matches` operator tests if a string matches a regular expression.
+The `matches` operator can be negated by prefixing the operator with
+`not`, such as `not matches`.
+
+The left-hand value is the string to test. The right-hand value is
+a string value representing a regular expression. The syntax of the regular
+expression is the same general syntax used by Python, Ruby, and other languages.
+The precise syntax accepted is the syntax accepted by RE2.
+
+Regular expressions are not anchored by default; any anchoring must be
+explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+## Any, All Expressions
+
+`any` and `all` expressions allow testing that any or all elements of a
+collection match some boolean expression. The result of an `any` and `all`
+expression is a boolean.
+
+`any` returns the boolean value `true` if any value in the collection expression
+results in the body expression evaluating to `true`. If the body expression
+evalutes to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression
+result in the body expression evaluating to `true`. If any value in the
+collection expression result in the body expression evaluating to `false`,
+the `all` expression returns `false`.
+
+For empty collections, `any` returns `false` and `all` returns `true`.
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" }
+any group.tasks as t { t.driver is "vmware" }
+```
+
+Since `any` and `all` expressions are themselves boolean expressions, they
+can be combined with other operators:
+
+```sentinel
+any ["a", "b"] as char { char is "a" } or
+other_value is "another"
+```
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/language/collection-operations.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/language/collection-operations.mdx
new file mode 100644
index 0000000000..32d10c5c5e
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/language/collection-operations.mdx
@@ -0,0 +1,44 @@
+---
+page_title: Sentinel Language - Collection Operations
+sidebar_title: Collection Operations
+sidebar_current: docs-language-collection-operations
+description: |-
+ Operations for interacting with collections (list, map).
+layout: docs
+---
+
+# Language: Collection Operations
+
+Collection operations are expressions that are performed on a list or map to
+return a variation of the initial data.
+
+At the moment, `filter` is the only collection operation available.
+
+## Filter Expression
+
+`filter` is a [quantifier
+expression](/sentinel/language/spec#quantifier-expressions-any-all-filter) that
+returns a subset of the provided collection. Only elements whose filter body
+returns true will be returned. If any of the elements filter body returns
+undefined, the final result will be undefined.
+
+Filter uses the same syntax as the `any` and `all` [boolean
+expressions](/sentinel/language/boolexpr#any-all-expressions):
+
+```sentinel
+filter list as value { condition } // Single-iterator, list
+filter list as idx, value { condition } // Double-iterator, list
+
+filter map as key { condition } // Single-iterator, map
+filter map as key, value { condition } // Double-iterator, map
+```
+
+Examples:
+
+```sentinel
+l = [1, 1, 2, 3, 5, 8]
+evens = filter l as v { v % 2 is 0 } // [2, 8]
+
+m = { "a": "foo", "b": "bar" }
+matched_foo = filter m as _, v { v is "foo" } // { "a": "foo" }
+```
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/language/conditionals.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/language/conditionals.mdx
new file mode 100644
index 0000000000..af85dafcc6
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/language/conditionals.mdx
@@ -0,0 +1,162 @@
+---
+page_title: Sentinel Language - Conditionals
+sidebar_title: Conditionals
+sidebar_current: docs-language-conditional
+description: |-
+ Conditional statements allow your policy to behave differently depending on a condition.
+layout: docs
+---
+
+# Language: Conditionals
+
+Conditional statements allow your policy to behave differently depending
+on a condition.
+
+Conditional statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## If Statements
+
+`if` statements only execute their bodies if a condition is met.
+The syntax of an `if` statement is:
+
+```sentinel
+if condition {
+ // ... this is executed if condition is true
+}
+```
+
+The `condition` must result in a boolean, such as by calling a function
+or evaluating a [boolean expression](/sentinel/language/boolexpr). If
+the `condition` is `true`, the body (within the `{}`) is executed. Otherwise,
+the body is skipped.
+
+Examples:
+
+```sentinel
+// This would execute the body
+value = 12
+if value is 18 {
+ print("condition met")
+}
+
+// Direct boolean values can be used
+value = true
+if value {
+ print("condition met")
+}
+
+// This would not execute the body since the boolean expression will
+// result in undefined.
+value = {}
+if value["key"] > 12 {
+ print("condition met")
+}
+```
+
+### Else, Else If
+
+An `else` clause can be given to an `if` statement to execute a body
+in the case the condition is _not met_. By putting another `if` statement
+directly after the `else`, multiple conditions can be tested for.
+The syntax is:
+
+```sentinel
+if condition {
+ // ...
+} else {
+ // ...
+}
+
+if condition {
+ // ...
+} else if other_condition {
+ // ...
+} else {
+ // ...
+}
+```
+
+### Scoping
+
+The body of an `if` statement does not create a new
+[scope](/sentinel/language/scope). Any variables assigned within the body
+of an if statement will modify the scope that the `if` statement itself
+is in.
+
+Example:
+
+```sentinel
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+```sentinel
+a = 18
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+## Case Statements
+
+`case` statements are a selection control mechanism that execute a clause based
+on matching expressions. It is worth noting that the expression for `case` is
+optional. When no expression is provided, it defaults the expression to `true`.
+Additionally, the order of clauses is important, as they are evaluated from top
+to bottom, executing the first match.
+The syntax of a case statement is:
+
+```sentinel
+case expression {
+ when clause_expression:
+ // executed when clause_expression and expression are equal
+ else:
+ // executed if no clause matches expression
+}
+```
+
+### When Clause
+
+Any clause that has an expression for comparison must use the `when` keyword.
+It accepts a list of expressions, seperated by a `,`.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+}
+```
+
+```sentinel
+case {
+ when x > 40:
+ return true
+}
+```
+
+### Else Clause
+
+The `else` keyword allows for capturing any expressions that have no matching
+`when` clause.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+ else:
+ return false
+}
+```
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/language/functions.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/language/functions.mdx
new file mode 100644
index 0000000000..c55dfe4d81
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/language/functions.mdx
@@ -0,0 +1,174 @@
+---
+page_title: Sentinel Language - Functions
+sidebar_title: Functions
+sidebar_current: docs-language-functions
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Functions
+
+Functions allow you to create reusable code to perform computations.
+
+Below is a trivial example function that doubles a number and shows
+the main rule using the function:
+
+```sentinel
+double = func(x) {
+ return x * 2
+}
+
+main = rule { double(12) is 24 }
+```
+
+Functions may contain any expressions,
+[conditionals](/sentinel/language/conditionals),
+and [loops](/sentinel/language/loops). They must return a value
+at the end of their execution. If no reasonable return value exists,
+the function should return [undefined](/sentinel/language/undefined).
+
+## Creating a Function
+
+A function is created by assigning a variable to a `func`.
+
+A function can have zero or more parameters. These parameters are specified
+within parentheses `()` separated by commas. If a function has no parameters,
+the parentheses must still be present.
+
+A function must terminate with a `return` statement to return a value back to
+the caller. Only a single value can be returned. The type of a return can
+vary between return statements.
+
+Example:
+
+```sentinel
+add1 = func(x) { return x + 1 }
+```
+
+This example creates a function that adds 1 to the parameter `x`. It is
+assigned to the variable `add1`.
+
+## Calling a Function
+
+Functions are called by accessing their name followed by parentheses with
+a list of comma-separated values for the parameters. If the function takes no
+parameters, an empty set of parentheses must still be used to denote a
+function call.
+
+To call the function in the example above:
+
+```sentinel
+x = 1
+y = add1(x)
+```
+
+## Scoping
+
+The body of a `func` creates a new [scope](/sentinel/language/scope).
+
+The parent scope is the scope in which the function is created. This allows
+for the creation of functions within functions that can access their outer
+scopes.
+
+Example:
+
+```sentinel
+f = func() {
+ a = 42
+ print(a) // 42
+ return undefined
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+f = func() {
+ a = 42
+ return undefined
+}
+
+print(a) // 18
+```
+
+And below is an example of creating a function in a function which uses
+outer values:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double()
+}
+
+print(f()) // 84
+```
+
+A more complex example below shows how scoping works when passing
+functions around as arguments or results:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double
+}
+
+double = f()
+print(double()) // 84
+```
+
+## Pass By Value
+
+The parameters to a function are passed by value, but not deep copied.
+This means that elements of collections can still be modified but the
+root value cannot be changed.
+
+Example:
+
+```sentinel
+f = func(x) {
+ x = "value"
+ return x
+}
+
+x = "outside"
+f(x)
+print(x) // "outside"
+```
+
+```sentinel
+f = func(x) {
+ append(x, "value")
+ return x
+}
+
+x = []
+f(x)
+print(x) // ["value"]
+```
+
+## Recursion
+
+A function is allowed to call other functions, including itself.
+This can be used to implement recursion. For example, the fibonacci sequence
+is implemented below:
+
+```sentinel
+fib = func(x) {
+ if x <= 0 {
+ return undefined
+ }
+
+ if x == 1 {
+ return 1
+ } else {
+ return x + fib(x - 1)
+ }
+}
+```
+
+Note that this example also shows using
+[undefined](/sentinel/language/undefined)
+as a return value in cases where a function has undefined behavior.
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/language/imports.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/language/imports.mdx
new file mode 100644
index 0000000000..945bb95930
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/language/imports.mdx
@@ -0,0 +1,117 @@
+---
+page_title: Sentinel Language - Imports
+sidebar_title: Imports
+sidebar_current: docs-language-imports
+description: >-
+ Imports enable a Sentinel policy to access reusable libraries and external
+ data and functions.
+layout: docs
+---
+
+# Language: Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external
+data and functions. The exact list of available imports is dependent on the
+host system. Host systems may also make the available imports configurable
+via plugins.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that
+the import `calendar` allows access to engineer calendars. This import
+only exists for the purpose of this example and at the time of writing is
+not a real available import.
+
+```sentinel
+import "calendar"
+
+// Get the calendar for Bob for today
+bob_calendar = calendar.for("bob").today
+
+// Allow this policy to pass if Bob is not on vacation.
+main = rule { not bob_calendar.has_event("vacation") }
+```
+
+This example shows an import being used to deny a behavior if Bob is on
+vacation. Hopefully real policies do not depend on a single person being
+in the office, but the example shows the power of imports.
+
+In addition to consuming imports, you can also
+[extend Sentinel by writing custom imports](/sentinel/extending)
+This allows you to add any arbitrary behavior to your Sentinel-protected
+systems.
+
+## Importing
+
+Whether accessing a built-in library or an external plugin, the syntax
+for an import is the same:
+
+```sentinel
+import "name"
+```
+
+This adds the import with the name `name`. It can be referenced using
+the identifier `name`. For example, if the import above exposed first
+and last name data, you could access it via `name.first` or `name.last`.
+
+You can shadow imports by assigning a variable. In the example below,
+the import `name` becomes unavailable within the function because it is
+shadowed by a variable of the same name.
+
+Shadowing an import is not the same as overwriting a variable. Imports
+are not part of the [scope](/sentinel/language/scope), meaning that
+the shadow only exists for the scope it is created within. For everything
+else, the import works even after it is shadowed. The example below shows that
+as well.
+
+```sentinel
+import "name"
+
+f = func() {
+ name = "jane"
+ return name
+}
+
+print(f()) // "jane"
+print(name.first) // "bob"
+```
+
+## Aliases
+
+An import can be aliased to another name using the syntax below:
+
+```sentinel
+import "name" as other
+```
+
+This would make the import "name" available as `other`.
+
+An import may only be imported once, regardless of aliases. The following
+would be **invalid** and would result in an error:
+
+```sentinel
+import "name" as one
+import "name" as two
+```
+
+## Accessing Import Data
+
+Imports are accessed with dot-separated identifiers, such as `name.first` or
+`name.stage.first`. These are called _selector expressions_. An import may
+return nested data that further can be accessed via selector expressions.
+
+In the first example on this page with the "calendar" import, we see this:
+
+```sentinel
+import "calendar"
+
+a = calendar.for("bob") // selector expression calendar.for
+b = a.today // selector expression on the result
+c = b.vacation // accessing further nested data
+```
+
+The exact structure of an import is dependent on the import. Please reference
+the documentation for an import to learn more.
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/language/index.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/language/index.mdx
new file mode 100644
index 0000000000..fe23ee1144
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/language/index.mdx
@@ -0,0 +1,100 @@
+---
+page_title: Sentinel Language
+sidebar_title: Language
+sidebar_current: docs-language-basics
+description: |-
+ Sentinel policies are written using the Sentinel language. This language
+ is easy to learn and easy to write. You can learn the Sentinel language
+ and be productive within an hour. Learning Sentinel doesn't require any
+ formal programming experience.
+layout: docs
+---
+
+# Sentinel Language
+
+Sentinel policies are written using the Sentinel language. This language
+is easy to learn and easy to write. You can learn the Sentinel language
+and be productive within an hour. Learning Sentinel doesn't require any
+formal programming experience.
+
+This language guide will contain many details that are not necessary to
+be productive with Sentinel or are targeted to those who are looking for
+exact information about the language (such as what types of line endings are
+allowed). You can safely ignore these sentences.
+
+You may also view the
+[official language specification](/sentinel/language/spec)
+This is a specific and detailed document on the syntax and behavior of
+the language primarily intended for implementation creators and to disambiguate
+the language.
+
+## Simplest Example
+
+The example below is about the simplest practical example of Sentinel.
+It is reasonable to imagine this as a realistic policy. This shows that
+in most cases, Sentinel will be extremely simple:
+
+```sentinel
+main = rule { request.method is "GET" and request.headers contains "X-Key" }
+```
+
+## Files
+
+Sentinel policies are single files that end in the `.sentinel` file extension.
+There is currently no built-in mechanism to Sentinel for merging multiple
+files. This is purposefully done to make Sentinel policies easy to submit
+to systems that support Sentinel policies.
+
+Sentinel policy files must be UTF-8 encoded and can end in both
+Unix (LF) or Windows (CRLF) line breaks. When running the
+[auto-formatter](#),
+line endings will always use Unix line breaks.
+
+## Ordering
+
+Sentinel policies are executed top-down. For example:
+
+```sentinel
+a = 1 // a = 1 here
+b = a + 1 // b = 2 here
+a = 3 // a = 3, b = 2
+```
+
+In this example, the value of `a` and `b` is shown at each line. Since Sentinel
+executes values top-down, the final value of `a` is 3 and `b` is 2. `b` _does
+not_ become 4.
+
+## Main
+
+Sentinel expects there to be a `main` [rule](/sentinel/language/rules).
+The value of this rule is the result of the entire policy.
+
+If the result of `main` is true, the policy passes. If the value is anything
+else (false or a non-boolean value), the policy fails. The exact meaning
+of what happens a policy passes or fails is dependent on the host system.
+
+## More Complex Example
+
+The simple example above is a full working example. In our experience with
+Sentinel, many policies can be representing using this simple form. However,
+to show more features of the language, a more complex example is shown below.
+This example is also a realistic example of what Sentinel may be used for.
+
+```sentinel
+import "units"
+
+memory = func(job) {
+ result = 0
+ for job.groups as g {
+ for g.tasks as t {
+ result += t.resources.memory else 0
+ }
+ }
+
+ return result
+}
+
+main = rule {
+ memory(job) < 1 * units.gigabyte
+}
+```
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/language/lists.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/language/lists.mdx
new file mode 100644
index 0000000000..5cbe310242
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/language/lists.mdx
@@ -0,0 +1,131 @@
+---
+page_title: Sentinel Language - Lists
+sidebar_title: Lists
+sidebar_current: docs-language-lists
+description: Lists are a collection of zero or more values.
+layout: docs
+---
+
+# Language: Lists
+
+Lists are a collection of zero or more values.
+
+Lists can be created using by wrapping values in `[]` and separating them
+by commas. An optional trailing comma is allowed. List elements can be
+differing types. Examples:
+
+```sentinel
+[] // An empty list
+["foo"] // Single element list
+["foo", 1, 2, true] // Multi element list with different types
+["foo", [1, 2]] // List containing another list
+```
+
+A list can be [sliced](/sentinel/language/slices) to create sublists.
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for value inclusion in a list.
+
+## Accessing Elements
+
+List elements can be accessed with the syntax `name[index]` where
+`index` is zero-indexed.
+
+A negative index accesses the list in reverse. It is the same as
+reversing a list and then using a positive index. Similar to a positive
+index, it is bounded by the length of the list as a negative value.
+
+Accessing beyond the length of the list results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+list = ["foo", 1, true, [1, 2]]
+
+list[0] // "foo"
+list[2] // true
+list[4] // undefined
+list[-2] // true
+list[-4] // "foo"
+list[-5] // undefined
+list[3][1] // 2
+```
+
+## List Append
+
+Values can be appended to a list using the built-in
+[append](/sentinel/language/funcs/append) function.
+
+This modifies the list in-place and returns
+[undefined](/sentinel/language/undefined). For more information on
+why `append` behaves this way, please read the full documentation for the
+[append](/sentinel/language/funcs/append) function.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append([1,2], "foo") // [1, 2, "foo"]
+append([1,2], [3]) // [1, 2, [3]]
+append(1, 3) // error()
+```
+
+## List Concatenation
+
+Two lists can be concatenated using the `+` operator or the shorthand
+`+=` assignment operator. For the `+` operator, a new list is returned.
+For `+=`, the left-hand list is modified in place.
+
+Examples:
+
+```sentinel
+[1] + [2] // [1, 2]
+[1] + [[1]] // [1, [1]]
+[1] + 1 // error
+
+a = [1]
+a += [2] // a = [1, 2]
+a += 3 // error
+```
+
+## List Length
+
+The length of a list can be retrieved using the
+[length](/sentinel/language/funcs/length) function.
+
+Examples:
+
+```sentinel
+length([]) // 0
+length(["foo"]) // 1
+```
+
+## Removing Items From a List
+
+You can use a combination of [list concatenation](#list-concatenation) and
+[slicing](/sentinel/language/slices) to remove elements from a list:
+
+```sentinel
+a = [0, 1, 2]
+a = foo[:1] + foo[2:] // [0, 2]
+```
+
+## List Comparison
+
+Lists may be [compared](/sentinel/language/boolexpr#comparison-operators)
+for equality. Lists are equal if they are of equal length and their
+corresponding elements are comparable and equal. Lists with the same elements
+but in a different order are not equal.
+
+```sentinel
+[1, 2] is [1, 2] // true
+[1, 2] is [2, 1] // false
+["a"] is ["a", "b"] // false
+["a", ["b", "c"]] is ["a", ["b", "c"]] // true
+```
+
+List comparison speed is O(N), meaning that the speed of the comparison is
+_linearly_ proportional to the number of elements in the list. The more
+elements, the more iterations that are necessary to verify equality.
+
+-> The N-value quoted above should account for the sum of the elements of _all_
+lists in the subjects of comparison, as list comparison will recurse into these
+lists to check for equality.
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/language/logging-errors.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/language/logging-errors.mdx
new file mode 100644
index 0000000000..32427697d9
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/language/logging-errors.mdx
@@ -0,0 +1,53 @@
+---
+page_title: Sentinel Language - Logging and Errors
+sidebar_title: Logging and Errors
+sidebar_current: docs-language-log
+description: The built-in functions `print` and `error` can be used for logging and errors.
+layout: docs
+---
+
+# Language: Logging and Errors
+
+The built-in functions `print` and `error` can be used for logging
+and errors. Logging is helpful to understand how a policy is executing
+in development. And errors are useful to halting execution immediately in
+certain scenarios.
+
+## Print
+
+The `print` function is used to output debug information. The exact location
+where this print statements go is dependent on the host application and
+the Sentinel runtime itself.
+
+The `print` function takes one or more Sentinel values as arguments.
+Each value is formatted for human-friendly output and space-separated.
+Example:
+
+```sentinel
+value = 42
+print("the value is", value) // the value is 42
+
+map = { "foo": false }
+print(map) // { "foo": false }
+
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // false
+```
+
+## Errors
+
+Certain actions in Sentinel, such as accessing an undefined variable,
+attempting to add two incompatible types, etc. result in _runtime errors_.
+These errors halt the execution of a policy immediately. The pass/fail result
+of a policy is considered fail.
+
+Runtime errors can be manually triggered using the `error` function.
+The `error` function behaves exactly like the `print` function except that
+execution is halted immediately.
+
+This should only be used in scenarios where the policy _must fail_ due to
+some unexpected or invalid condition. The line between `error` and
+[undefined](/sentinel/language/undefined) can sometimes be unclear. Undefined
+is recommended when a policy can conceivably recover or safely ignore the
+undefined behavior. An error should be used when the policy should fail and
+notify someone regardless.
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/language/loops.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/language/loops.mdx
new file mode 100644
index 0000000000..acaa7b80c6
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/language/loops.mdx
@@ -0,0 +1,93 @@
+---
+page_title: Sentinel Language - Loops
+sidebar_title: Loops
+sidebar_current: docs-language-loops
+description: >-
+ Loop statements allow you to execute a body of code for each element in a
+ collection or for some fixed number of times.
+layout: docs
+---
+
+# Language: Loops
+
+Loop statements allow you to execute a body of code for each element in
+a collection or for some fixed number of times.
+
+Loop statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/docs/language/functions.html) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## For Statements
+
+`for` statements allow repeated execution of a block for each
+element in a collection.
+
+Example:
+
+```sentinel
+// A basic sum
+count = 0
+for [1, 2, 3] as num {
+ count += num
+}
+```
+
+The syntax is `for COLLECTION as value`. This will iterate over the
+collection, assigning each element to `value`. In the example above,
+each element is assigned to `num`. The body is executed for each element.
+In the example above, the body adds `num` to the `count` variable. This
+creates a basic sum of all values in the collection.
+
+For a map, the assigned element is the key in the map. In the example
+below, `name` would be assigned map keys.
+
+```sentinel
+list = []
+for { "a": 1, "b": 2 } as name {
+ append(list, name)
+}
+
+print(list) // ["a" "b"]
+```
+
+An alternate syntax is `for COLLECTION as key, value`. This will assign
+both the key and value to a variable. For a list, the key is the element
+index. For a map, it is the key and value is assigned the element value.
+Example:
+
+```sentinel
+count = 0
+for { "a": 1, "b": 2 } as name, num {
+ count += num
+}
+
+print(count) // 3
+```
+
+## Scoping
+
+The body of a `for` statement creates a new
+[scope](/sentinel/language/scope). If a variable is assigned within
+the body of a for statement that isn't assigned in a parent scope,
+that variable will only exist for the duration of the body execution.
+
+Example:
+
+```sentinel
+for list as value {
+ a = 42
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+for list as value {
+ a = 42
+}
+
+print(a) // 18
+```
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/language/maps.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/language/maps.mdx
new file mode 100644
index 0000000000..8dd30ab02c
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/language/maps.mdx
@@ -0,0 +1,100 @@
+---
+page_title: Sentinel Language - Maps
+sidebar_title: Maps
+sidebar_current: docs-language-maps
+description: >-
+ Maps are a collection of zero or more key/value pairs. These are useful for
+ storing values that are looked up by a unique key.
+layout: docs
+---
+
+# Language: Maps
+
+Maps are a collection of zero or more key/value pairs. These are useful
+for storing values that are looked up by a unique key.
+
+Maps can be created using `{}` and specifying key/value pairs. Keys and
+values can be differing types. An optional trailing comma is allowed.
+Examples:
+
+```sentinel
+// Empty map
+{}
+
+// Map with a single value on one line
+{ "key": "value" }
+
+// Map with multiple values with differing types on multiple lines
+{
+ "key": "value",
+ 42: true,
+}
+```
+
+Maps are **unordered**. When
+[looping](/sentinel/language/loops)
+over a map, the key/value pairs can be returned in any order.
+
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for key inclusion in a map.
+
+## Accessing Elements
+
+Map elements can be accessed with the syntax `name[key]`. This looks up
+a value by a key.
+
+Accessing a key that doesn't exist results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+map = { "key": "value", 42: true, }
+
+map["key"] // "value"
+map[42] // true
+map[0] // undefined
+```
+
+## Modifying or Adding Elements
+
+Elements can be added or modified in a map by assigning to `name[key]`.
+If the key doesn't exist, the value is added. If the key already exists,
+the value is overridden.
+
+```sentinel
+map = { "key": "value" }
+
+map[42] = true // Add a new key/value
+map["key"] = 12 // Modify the value of "key"
+```
+
+## Deleting Elements
+
+An element can be deleted from a map using the
+[delete](/sentinel/language/funcs/delete) function.
+
+Examples:
+
+```sentinel
+map = { "key": "value" }
+delete(map, "key") // map is now empty
+delete(map, "other") // no effect for non-existent key
+```
+
+## Keys and Values
+
+The keys and values of a map can be retrieved as
+[lists](/sentinel/language/lists) using the
+[keys](/sentinel/language/funcs/keys) and
+[values](/sentinel/language/funcs/values) functions.
+
+Because maps are unordered, the keys and values are returned in an
+unspecified order. It should not be assumed that keys and values will be
+returned in a consistent order.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/language/parameters.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/language/parameters.mdx
new file mode 100644
index 0000000000..b5b47d6bf8
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/language/parameters.mdx
@@ -0,0 +1,136 @@
+---
+page_title: Sentinel Language - Parameters
+sidebar_title: Parameters
+sidebar_current: docs-language-parameters
+description: >-
+ Parameteres allow a Sentinel policy to describe variables that are expected
+ to be supplied by the calling application, in addition to any default value.
+layout: docs
+---
+
+# Language: Parameters
+
+Sentinel allows a policy author to supply parameters to help facilitate policy
+reuse and ensure sensitive values do not need to be hard-coded in a policy.
+
+Parameters are supplied by using the `param` keyword, followed by an identifier.
+A default value can also be supplied by using the `default` keyword.
+
+```sentinel
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+```
+
+Once declared, parameters can be used like any other variable, including being
+re-assigned.
+
+```sentinel
+param foo default 1 // 1 (default)
+
+foo = foo + 1 // 2
+```
+
+## Variable Descriptions
+
+You can supply a description to a parameter by adding a comment at the top of
+it. This value can be communicated to a specific implementation of Sentinel to
+provide information about what the parameter is for during configuration.
+
+```sentinel
+// An example parameter. Must be supplied or the policy will fail.
+param foo
+```
+
+## Supplying Parameter Values Using the Sentinel CLI
+
+In a production implementation, supplying parameters to a policy is an
+implementation-specific detail - see the documentation for your particular
+implementation for details.
+
+Using the [Sentinel CLI](/sentinel/commands), you can supply parameters one of
+four ways.
+
+### Supplying Parameter Values Using the Configuration File
+
+You can supply parameters using the
+[`param`](/sentinel/commands/config#parameters) section of the
+[configuration file](/sentinel/commands/config).
+
+```json
+{
+ "param": {
+ "foo": "bar"
+ }
+}
+```
+
+This method works for both `sentinel apply` and `sentinel test`.
+
+### Supplying Parameter Values Using the Environment
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can supply a value using environment variables - prefix the parameter with
+`SENTINEL_PARAM_`, using the name of the parameter to supply.
+
+```plaintext
+SENTINEL_PARAM_foo=bar sentinel apply policy.sentinel
+```
+
+### Supplying Parameter Values Using the CLI
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can also use the `-param` CLI argument to supply parameter in a `key=value`
+pair.
+
+```plaintext
+sentinel apply -param foo=bar policy.sentinel
+```
+
+### Interactive CLI Prompting
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+If a required value has not been supplied when a policy is run with `sentinel apply`, it will be prompted for, along with its description:
+
+
+
+ $ sentinel apply policy.sentinel
+
+ policy.sentinel:2:7: requires value for parameter foo
+
+ An example parameter. Must be supplied or the policy will fail.
+
+
+ Values can be strings, floats, or JSON array or object values. To
+ force
+
+ strings, use quotes.
+
+
+ Enter a value: bar
+
+
+
+ Pass
+
+
+
+
+### CLI Value Format
+
+-> **NOTE:** This section contains details for the parameter features supported by `sentinel apply`.
+
+The CLI takes either strings, or JSON numbers, arrays, or maps. If you need a
+literal string value, quote the value.
+
+```sentinel
+foo // string
+42 // number (float)
+"42" // string ("42", without quotes)
+[1, 2] // array (list)
+{"a": "b"} // object (map)
+```
+
+-> **NOTE:** Boolean values are not supported by this method.
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/language/rules.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/language/rules.mdx
new file mode 100644
index 0000000000..0dd1438f5b
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/language/rules.mdx
@@ -0,0 +1,165 @@
+---
+page_title: Sentinel Language - Rules
+sidebar_title: Rules
+sidebar_current: docs-language-rules
+description: >-
+ Rules form the basis of a policy by representing behavior that is either
+ passing or failing (true or false).
+layout: docs
+---
+
+# Language: Rules
+
+Rules form the basis of a policy by representing behavior that is either
+passing or failing (true or false). A policy can be broken down into a
+set of rules. Breaking down a policy into a set of rules can make it more
+understandable and aids with testing.
+
+An example is shown below:
+
+```sentinel
+is_sunny = rule { weather is "sunny" }
+is_wednesday = rule { day is "wednesday" }
+
+main = rule {
+ is_sunny and
+ is_wednesday
+}
+```
+
+A rule contains a single [boolean expression](/sentinel/language/boolexpr).
+This boolean expression can be split into multiple lines for readability
+as shown in the example above. Boolean expressions should only be split
+into multiple lines after the operator ("and", "or", etc.).
+
+A rule is also _lazy_ and _memoized_. _Lazy_ means that the rule is only
+evaluated when it is actually required, not at the point that it is
+created. This is covered in more detail in the [lazy](#lazy) section.
+_Memoized_ means that the value is only computed once and then saved. Once
+a rule is evaluated, the result of it is reused anytime the rule is referenced
+again.
+
+## Easing Understandability
+
+Rules are an abstraction that make policies much more understandable
+by making it easier for humans to see what the policy is trying to do.
+
+For example, consider the policy before which doesn't abstract into rules:
+
+```sentinel
+main = rule {
+ ((day is "saturday" or day is "sunday") and homework is "") or
+ (day in ["monday", "tuesday", "wednesday", "thursday", "friday"] and
+ not school_today)
+}
+```
+
+Assume that `day`, `homework`, and `school_day` are available.
+
+In plain english, this policy is trying to say: you can play with your friends
+only on the weekend as long as there is no homework, or during the week if
+there is no school and no homework.
+
+Despite the relatively simple nature of this policy (a few lines, about 5
+logical expressions), it is difficult to read and undertand, especially if
+you've not seen it before.
+
+The same policy using rules as an abstraction:
+
+```sentinel
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+is_valid_weekend = rule { is_weekend and homework is "" }
+is_valid_weekday = rule { not is_weekend and not school_today }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+By reading the names of the rules, its much clearer to see what each individual
+part of the policy is trying to achieve. The readability is improved even
+further when adding comments to the rules to explain them further, which is
+a recommended practice:
+
+```sentinel
+// A weekend is Sat or Sun
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+// A valid weekend is a weekend without homework
+is_valid_weekend = rule { is_weekend and homework is "" }
+
+// A valid weekday is a weekday without school
+is_valid_weekday = rule { not is_weekend and not school_today }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+## "When" Predicates
+
+A rule may have an optional `when` predicate attached to it. When this is
+present, the rule is only evaluated when the predicate results in `true`.
+Otherwise, the rule is not evaluated and the result of the rule is always
+`true`.
+
+"When" predicates should be used to define the context in which the rule
+is semantically meaningful. It is common when translating human language
+policy into Sentinel to have scenarios such as "when the key has a prefix
+of `/account/`, the remainder must be numeric." In that example, when the
+key _does not_ have the specified prefix, the policy doesn't apply.
+
+Assume you have rules `is_prefix` and `is_numeric` to check both the
+conditions in the example above. An example is shown with and without
+the "when" predicate:
+
+```sentinel
+example_no_when = rule { (is_prefix and is_numeric) or not is_prefix }
+
+example_when = rule when is_prefix { is_numeric }
+```
+
+The rules are equivalent in behavior for all values of `is_prefix` and
+`is_numeric`, but the second rule is more succint and easier to understand.
+
+## Testing
+
+To verify a policy works correct, the
+[built-in Sentinel test framework](#)
+uses rules as the point where you can assert behavior. You say that
+you expect certain rules to be true or false. By doing so, you ensure that
+the policy results in the value you expect using the rule flow that you
+expect.
+
+Therefore, in addition to readability, we recommend splitting policies into
+rules to aid testability.
+
+## Lazy and Memoized
+
+A rule is _lazy_ and _memoized_.
+
+_Lazy_ means that the rule is only evaluated when it is actually required,
+not at the point that it is created. And _memoized_ means that the value is
+only computed once and then saved. Once a rule is evaluated, the result of it
+is reused anytime the rule is referenced again.
+
+Both of these properties have important implications for Sentinel
+policies:
+
+**Performance:** Rules are a way to improve the performance of a Sentinel
+policy. If you have a boolean expression that is reused a lot, a rule will
+only be evaluated once. In the example shown above, `is_weekend` is used
+multiple times, but will only have to be evaluated once.
+
+**Behavior:** Rules accessing variables see the value of those variables
+at _the time they are evaluated_. This can lead to surprising behavior
+in some cases. For example:
+
+```sentinel
+a = 1
+b = rule { a == 1 }
+a = 2
+main = b
+```
+
+In this example, `main` will actually result to `false`. It is evaluated
+when it is needed, which is when the policy executes `main`. At this point,
+`a` is now 2 and `b` has not been evaluated yet. Therefore, `b` becomes `false`.
+All future references to `b` will return `false` since a rule is memoized.
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/language/scope.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/language/scope.mdx
new file mode 100644
index 0000000000..2fb562a75d
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/language/scope.mdx
@@ -0,0 +1,42 @@
+---
+page_title: Sentinel Language - Scope
+sidebar_title: Scope
+sidebar_current: docs-language-scope
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Scope
+
+Scope is the context in which variables are created and accessed. If a
+variable exists in a parent scope, it is accessed and can be modified.
+Otherwise, the variable is created in your current scope.
+
+Scopes are created with _blocks_. A block is a possibly empty sequence
+of statements within matching brace brackets `{}`. You may nest blocks
+to create nested scopes.
+
+In addition to explicitly created blocks, there are implicit blocks:
+
+1. The policy block encapsulates an entire policy.
+2. Each `any`, `all`, and `for` statement is considered to be in
+ its own block. Note that `if` statements _do not_ create their
+ own block.
+
+The example policy below shows various effects of scopes:
+
+```sentinel
+a = 1
+print(a) // 1
+
+f = func() {
+ print(a) // 1
+ a = 12
+ b = 1
+ return undefined
+}
+f()
+
+print(a) // 12
+print(b) // undefined
+```
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/language/slices.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/language/slices.mdx
new file mode 100644
index 0000000000..d8faa5cece
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/language/slices.mdx
@@ -0,0 +1,33 @@
+---
+page_title: Sentinel Language - Slices
+sidebar_title: Slices
+sidebar_current: docs-language-slices
+description: >-
+ Slices are an efficient way to create a substring or sublist from an existing
+ string or list, respectively.
+layout: docs
+---
+
+# Language: Slices
+
+Slices are an efficient way to create a substring or sublist from an
+existing string or list, respectively.
+
+The syntax for creating a slice is:
+
+```sentinel
+a[low : high]
+```
+
+`low` is the lowest index to slice from. The resulting slice includes
+the value at `low`. `high` is the index to slice to. The resulting slice
+will not include the value at `high`. The length of the resulting list
+or string is always `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+
+a = "hello"
+b = a[1:4] // "ell"
+```
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/language/spec.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/language/spec.mdx
new file mode 100644
index 0000000000..526c6b39b2
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/language/spec.mdx
@@ -0,0 +1,1297 @@
+---
+page_title: Sentinel Language Specification
+sidebar_title: Specification
+sidebar_current: docs-language-spec
+description: This is the specification for the Sentinel policy language.
+layout: docs
+---
+
+# Sentinel Language Specification
+
+This is the specification for the Sentinel policy language.
+
+The Sentinel language is designed with policy enforcement in mind. It is dynamically typed and garbage collected and has explicit support for _rule_ construction representing boolean logic.
+
+The language is designed to be easy to learn and use by non-programmers. It is expected to be embedded within applications.
+
+## Table of Contents
+
+
+
+
+* [Source code representation](#source-code-representation)
+* [Declarations and Scope](#declarations-and-scope)
+* [Blocks](#blocks)
+* [Lexical Elements](#lexical-elements)
+ * [Comments](#comments)
+ * [Identifiers](#identifiers)
+ * [Keywords](#keywords)
+ * [Operators and Delimiters](#operators-and-delimiters)
+ * [Integer Literals](#integer-literals)
+ * [Floating-point Literals](#floating-point-literals)
+ * [String Literals](#string-literals)
+ * [Implicit Line Joining](#implicit-line-joining)
+ * [Whitespace](#whitespace)
+ * [Semicolons](#semicolons)
+* [Variables](#variables)
+* [Undefined](#undefined)
+* [Expressions](#expressions)
+ * [Operand](#operand)
+ * [Primary Expressions](#primary-expressions)
+ * [Null](#null)
+ * [Booleans](#booleans)
+ * [Boolean Literals](#boolean-literals)
+ * [Boolean Expressions](#boolean-expressions)
+ * [List Literals](#list-literals)
+ * [Map Literals](#map-literals)
+ * [Function Literals](#function-literals)
+ * [Rule Expressions](#rule-expressions)
+ * [Index Expressions](#index-expressions)
+ * [Selectors](#selectors)
+ * [Slice Expressions](#slice-expressions)
+ * [Calls](#calls)
+ * [Operators](#operators)
+ * [Operator Precedence](#operator-precedence)
+ * [Arithmetic Operators](#arithmetic-operators)
+ * [Integer operators](#integer-operators)
+ * [Integer overflow](#integer-overflow)
+ * [Floating-point operators](#floating-point-operators)
+ * [String Concatenation](#string-concatenation)
+ * [List Concatenation](#list-concatenation)
+ * [Comparison Operators](#comparison-operators)
+ * [Logical Operators](#logical-operators)
+ * [Set Operators](#set-operators)
+ * [Matches Operator](#matches-operator)
+ * [Else Operator](#else-operator)
+ * [Quantifier Expressions (any, all, filter)](#quantifier-expressions-any-all-filter)
+* [Statements](#statements)
+ * [Expression Statements](#expression-statements)
+ * [Assignments](#assignments)
+ * [If Statements](#if-statements)
+ * [Case Statements](#case-statements)
+ * [For Statements](#for-statements)
+ * [Break Statements](#break-statements)
+ * [Continue Statements](#continue-statements)
+ * [Return Statements](#return-statements)
+* [Imports](#imports)
+* [Parameters](#parameters)
+* [Built-in Functions](#built-in-functions)
+ * [Length](#length)
+ * [Collections](#collections)
+ * [List Append](#list-append)
+ * [Map Delete](#map-delete)
+ * [Keys and Values](#keys-and-values)
+ * [Range](#range)
+ * [Type Conversion](#type-conversion)
+ * [Printing](#printing)
+ * [Errors](#errors)
+* [Program Execution](#program-execution)
+
+
+
+## Source code representation
+
+Source code is Unicode text encoded in UTF-8. A "character" referenced in this document refers to a Unicode code point. Each code point is distinct: upper and lower case letters are different characters.
+
+The underscore character \_ (U+005F) is considered a "letter".
+
+```ebnf
+letter = unicode_letter | "_" .
+decimal_digit = "0" … "9" .
+octal_digit = "0" … "7" .
+hex_digit = "0" … "9" | "A" … "F" | "a" … "f" .
+```
+
+## Declarations and Scope
+
+A declaration binds an identifier to a value. An identifier is declared at the point it is first assigned. An assignment is considered a first assignment if the identifier hasn't already been previously declared in the current scope or any parent scopes.
+
+The scope of an identifier is the extent of source text in which the identifier denotes the specified value. Sentinel is lexically scoped using blocks.
+
+An identifier declared in a block may be redeclared in an inner block. While the identifier of the inner declaration is in scope, it denotes the value assigned in the inner declaration.
+
+## Blocks
+
+A block is a possibly empty sequence of statements within matching brace brackets.
+
+```ebnf
+Block = "{" StatementList "}" .
+StatementList = { Statement ";" } .
+```
+
+Blocks nest and affect scoping.
+
+In addition to explicit blocks in the source code, there are implicit blocks:
+
+1. The universe block encompasses all source text.
+2. Each program has a program block containing all source text for that program.
+3. Each "any", "all", and "for" statements is considered to be in its own implicit block. "if" does not create an implicit block.
+
+## Lexical Elements
+
+### Comments
+
+Comments are sections of source text used for documentation.
+
+```plaintext
+# Single line comment
+// Single line comment
+/* multi
+line
+ comment */
+```
+
+Three forms of comments are supported: two single-line forms and one multi-line form. A single-line comment begins with the token `//` or `#`. Everything between the starting token and the end of the line is ignored.
+
+A multi-line comment begins with the token `/*` and ends with the token `*/`. Everything between `/*` and `*/` is ignored.
+
+A comment may not start inside a string literal or inside another comment.
+
+A multi-line comment containing no newlines acts like a space.
+
+### Identifiers
+
+Identifiers name program entities such as rules, variables, and functions. An identifier is a sequence of one or more letters and digits. The first character in an identifier must be a letter.
+
+```ebnf
+identifier = letter { letter | unicode_digit } .
+```
+
+```sentinel
+a
+_a
+_A
+αβ
+```
+
+#### Pre-Declared Identifiers
+
+The following identifiers are implicitly declared in the [universe block](#blocks):
+
+```plaintext
+Constants:
+ false null true undefined
+
+Functions:
+ append bool delete error float int keys length print range string values
+```
+
+### Keywords
+
+The following keywords are reserved and may not be used as identifiers:
+
+```plaintext
+all
+any
+as
+break
+case
+continue
+default
+else
+for
+func
+if
+import
+param
+return
+rule
+when
+```
+
+### Operators and Delimiters
+
+The following character sequences represent operators, delimiters, and other special tokens:
+
+```plaintext
++
+-
+*
+/
+%
++=
+-=
+*=
+/=
+%=
+==
+<
+>
+=
+!
+!=
+<=
+>=
+( )
+[ ]
+{ }
+,
+.
+:
+;
+and
+contains
+else
+in
+is
+matches
+not
+or
+xor
+```
+
+As a special case, `else` is both a keyword and operator, depending on the context. See [Else Operator](#else-operator) for more details.
+
+### Integer Literals
+
+An integer literal is a sequence of digits representing an integer constant. An optional prefix sets a non-decimal base: `0` for octal, `0x` or `0X` for hexadecimal. In hexadecimal literals, letters `a-f` and `A-F` represents values 10 through 15.
+
+Integers are signed 64-bit values (-9223372036854775808 to 9223372036854775807).
+
+```ebnf
+int_lit = decimal_lit | octal_lit | hex_lit .
+decimal_lit = ( "1" … "9" ) { decimal_digit } .
+octal_lit = "0" { octal_digit } .
+hex_lit = "0" ( "x" | "X" ) hex_digit { hex_digit } .
+```
+
+```
+42
+0600
+0xBadFace
+170141183460469231731687303715884105727
+```
+
+### Floating-point Literals
+
+A floating-point literal is a decimal representation of a floating-point constant. It has an integer part, a decimal point, a fractional part, and an exponent part. The integer and fractional part comprise decimal digits; the exponent part is an e or E followed by an optionally signed decimal exponent. One of the integer part or the fractional part may be elided; one of the decimal point or the exponent may be elided.
+
+Floating-point numbers are IEEE-754 64-bit floating numbers.
+
+```ebnf
+float_lit = decimals "." [ decimals ] [ exponent ] |
+ decimals exponent |
+ "." decimals [ exponent ] .
+decimals = decimal_digit { decimal_digit } .
+exponent = ( "e" | "E" ) [ "+" | "-" ] decimals .
+```
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+### String Literals
+
+String literals are character sequences between double quotes, as in "bar". Within the quotes, any character may appear except newline and unescaped double quote. The text between the quotes forms the value of the literal.
+
+A multi-character sequence beginning with a backslash encode values in various formats.
+
+Backslash escapes allow arbitrary values to be encoded as ASCII text. There are four ways to represent the integer value as a numeric constant: `\x` followed by exactly two hexadecimal digits; `\u` followed by exactly four hexadecimal digits; `\U` followed by exactly eight hexadecimal digits, and a plain backslash `\` followed by exactly three octal digits. In each case the value of the literal is the value represented by the digits in the corresponding base.
+
+After a backslash, certain single-character escapes represent special values:
+
+```plaintext
+\a U+0007 alert or bell
+\b U+0008 backspace
+\f U+000C form feed
+\n U+000A line feed or newline
+\r U+000D carriage return
+\t U+0009 horizontal tab
+\v U+000b vertical tab
+\\ U+005c backslash
+\" U+0022 double quote
+```
+
+The three-digit octal (\nnn) and two-digit hexadecimal (\xnn) escapes represent individual bytes of the resulting string; all other escapes represent the (possibly multi-byte) UTF-8 encoding of individual characters. Thus inside a string literal \377 and \xFF represent a single byte of value 0xFF=255, while ÿ, \u00FF, \U000000FF and \xc3\xbf represent the two bytes 0xc3 0xbf of the UTF-8 encoding of character U+00FF.
+
+A string value is a (possibly empty) sequence of bytes. Strings are immutable: once created, it is impossible to change the contents of a string.
+
+The length of a string s (its size in bytes) can be discovered using the built-in function `length`. An individual character (of type string) can be accessed by integer indices 0 through `length(s)-1`.
+
+```ebnf
+string_lit = `"` { unicode_value | byte_value } `"` .
+unicode_value = unicode_char | little_u_value | big_u_value | escaped_char .
+byte_value = octal_byte_value | hex_byte_value .
+octal_byte_value = `\` octal_digit octal_digit octal_digit .
+hex_byte_value = `\` "x" hex_digit hex_digit .
+little_u_value = `\` "u" hex_digit hex_digit hex_digit hex_digit .
+big_u_value = `\` "U" hex_digit hex_digit hex_digit hex_digit
+ hex_digit hex_digit hex_digit hex_digit .
+escaped_char = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | `"` ) .
+```
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+### Implicit Line Joining
+
+Expressions can be split over more than one line. For example:
+
+```sentinel
+a or
+b or # This is a comment
+c
+```
+
+Implicitly continued lines can have trailing comments. Blank continued lines are allowed.
+
+### Whitespace
+
+Whitespace is needed to separate tokens, but no distinction is made between the number and combination of whitespace characters. Whitespace characters can be space (U+0020), horizontal tabs (U+0009), carriage returns (U+000D), and newlines (U+000A).
+
+```sentinel
+a or b
+
+a or b
+
+a or
+ b
+
+rule { a or b }
+
+rule {
+ a or b }
+
+rule {
+ a or
+ b
+}
+```
+
+### Semicolons
+
+The formal grammar uses semicolons ";" as terminators in a number of productions.
+It is idiomatic Sentinel source to omit semicolons in most cases.
+
+A semicolon is automatically inserted into the token stream immediately after
+a line's final token if that token is:
+
+* An identifier
+* An integer, float, or string literal
+* The keyword `break`, `continue`, or `return`
+* The delimiter `)`, `]`, or `}`
+
+## Variables
+
+A variable is a storage location for holding a value.
+
+A variable has a dynamic type, which is the concrete type of the value assigned to the variable at run time. The dynamic type may vary during execution and change as a result of further assignments.
+
+A variable's value is retrieved by referring to the variable in an expression; it is the most recent value assigned to the variable. If a variable has not yet been declared (initially assigned), it is an error.
+
+```sentinel
+x = 7 // x is type int
+x = "foo" // x is now type string
+
+x = y // error if y is not previously assigned
+```
+
+## Undefined
+
+The value denoted by the keyword `undefined` represents undefined behavior or values. It can be created directly using the keyword `undefined`. It is also returned as a result of expressions in specified cases.
+
+`undefined` is a valid operand for any operations. Only `undefined or true` will result in true. All other operations result in `undefined`. An exception is if `undefined` is not reached as a result of short-circuit operations.
+
+```sentinel
+undefined OR true = true
+undefined OR false = undefined
+undefined OR undefined = undefined
+undefined AND true = undefined
+undefined AND false = undefined
+undefined AND undefined = undefined
+undefined XOR true = undefined
+undefined XOR false = undefined
+undefined XOR undefined = undefined
+
+// Short-circuit examples
+false OR true OR undefined = true
+false OR undefined OR true = true
+true AND false AND undefined = false
+true AND undefined AND false = undefined
+
+// Non-logical operators
+undefined + 5 = undefined
+-undefined = undefined
+!undefined = undefined
+```
+
+If the result of the main rule is `undefined`, it is treated as `false` but is indicative of erroneous logic.
+
+## Expressions
+
+### Operand
+
+Operands denote the elementary values in an expression. An operand may be a literal, an identifier denoting a variable, rule, or function, or a parenthesized expression.
+
+```ebnf
+Operand = Literal | OperandName | MethodExpr | "(" Expression ")" .
+Literal = BasicLit | CompositeLit | FunctionLit | RuleLit .
+BasicLit = int_lit | float_lit | string_lit .
+OperandName = identifier | QualifiedIdent.
+```
+
+### Primary Expressions
+
+Primary expressions are the operands for unary and binary expressions.
+
+```ebnf
+PrimaryExpr =
+ Operand |
+ PrimaryExpr Selector |
+ PrimaryExpr Index |
+ PrimaryExpr Slice |
+ PrimaryExpr Arguments .
+
+Selector = "." identifier .
+Index = "[" Expression "]" .
+Slice = "[" [ Expression ] ":" [ Expression ] "]" |
+ "[" [ Expression ] ":" Expression ":" Expression "]" .
+Arguments = "(" [ ( ExpressionList | Type [ "," ExpressionList ] ) [ "..." ] [ "," ] ] ")" .
+```
+
+```sentinel
+x
+2
+(s + ".txt")
+f(3.1415, true)
+m["foo"]
+s[i : j + 1]
+obj.color
+f.p[i].x()
+```
+
+### Null
+
+The reserved word `null` denote the singleton value `null`. Null represents the explicit absense of a value. Behavior of `null` within expressions is specified explicitly for each expression.
+
+### Booleans
+
+### Boolean Literals
+
+The reserved words `true` and `false` denote objects that represent the boolean values `true` and `false`, respectively. These are boolean literals.
+
+```ebnf
+BoolLit = "true" | "false" .
+```
+
+#### Boolean Expressions
+
+Boolean expressions are expressions that must result in a boolean value. Any other value type becomes the `undefined` value.
+
+```ebnf
+BoolExpr = Expression .
+```
+
+### List Literals
+
+A list literal denotes a list, which is an integer indexed collection of values.
+
+```ebnf
+ListLit = "[" [ ElementList [ "," ] ] "]" .
+ElementList = Element { "," Element } .
+Element = Expression | LiteralValue .
+```
+
+A list may contain zero or more values. The number of values in a list is its length.
+
+A list has a set of indices. An empty list has an empty set of indices. A non-empty list has the index set `{0...n - 1}` where `n` is the length of the list. Attempting to access a list using an index that is not a member of its set of indices results in the `undefined` value.
+
+### Map Literals
+
+A map literal denotes a map, which is an unordered group of elements indexed by a set of unique keys.
+
+```ebnf
+MapLit = "{" [ KeyedElementList [ "," ] ] "}" .
+KeyedElementList = KeyedElement { "," KeyedElement } .
+KeyedElement = Element ":" Element .
+```
+
+Keys can only be a boolean, numeric, or string type.
+
+The value of a non-existent key is the `undefined` value.
+
+### Function Literals
+
+A function literal represents a function.
+
+```ebnf
+FunctionLit = "func" Function .
+Function = Parameters FunctionBody .
+FunctionBody = Block .
+Parameters = "(" [ IdentifierList [ "," ] ] ")" .
+IdentifierList = identifier { "," identifier } .
+```
+
+```sentinel
+func(a, b) { return b }
+```
+
+Function literals are only allowed in the file scope. They may refer to variables defined in surrounding blocks.
+
+A function must terminate with a return statement. If a function has no meaningful return value, it should return `undefined`.
+
+### Rule Expressions
+
+A rule is a boolean expression that is evaluated lazily and the result is memoized.
+
+If the optional "when" predicate is present, the rule is evaluated only when
+the "when" boolean expression results in true. If the predicate is false,
+the rule is not evaluated and returns true. The predicate is evaluated when
+the rule would be evaluated; it is also lazy and memoized in the same way.
+
+```ebnf
+RuleExpr = "rule" [ "when" BoolExpr ] "{" BoolExpr "}" .
+```
+
+```sentinel
+rule { x is y }
+
+rule {
+ x == "value" or
+ y == "other"
+}
+
+rule when x is y { y > 42 }
+```
+
+### Index Expressions
+
+A primary expression of the form `a[x]` denotes the element of a list, map, or string indexed by x. The value x is called the index or key.
+
+For `a` of type map:
+
+* If `a` contains a key `x`, `a[x]` is the map value with key `x`.
+* If `a` does not contain key `x`, `a[x]` is the `undefined` value.
+
+For `a` of type list:
+
+* `x` must be an integer
+* `x` must be in the range `[-1 * length(a), length(a)-1]`
+* If `x` is contained in the set of indices of `a`, `a[x]` is the list element at index `x`. If `x` is negative, `a[x]` is equivalent to `a[length(a)+x]`.
+* If `x` is not contained in the set of indices of `a`, `a[x]` is the `undefined` value.
+
+For `a` of type string:
+
+* `x` must be an integer
+* `x` must be in the range `[-1 * length(a), length(a)-1]`
+* If `x` is in range, `a[x]` is the byte value at index `x` as a string. If `x` is negative, `a[x]` is equivalent to `a[length(a)+x]`.
+* If `x` is out of range, `a[x]` is the `undefined` value.
+
+For `a` of value `null`:
+
+* `a[x]` is the `undefined` value.
+
+Otherwise `a[x]` is an error.
+
+### Selectors
+
+For a primary expression x, the selector expression `x.f` denotes the field `f` of the value `x`. The identifier `f` is called the selector. The type of the selector expression is the type of the selector.
+
+As a special case, selectors can be [reserved words](#keywords) and keyword [operators](#operators-and-delimiters), but cannot be any other non-identifier element.
+
+Selectors are used to access data from [imports](#imports). The first primary expression `x` in `x.f` denotes the import name. The field `f` is the selector to access data from the import.
+
+Selectors may also be used to access map data with static keys. They are syntactic sugar over index expressions. `math.pi` is equivalent to `math["pi"]` and exhibit the same limitations and behavior as an index expression. Selectors cannot, however, be used to assign values to map data.
+
+Selectors on undefined result in undefined.
+
+```sentinel
+math.pi
+time.pst.hour
+```
+
+### Slice Expressions
+
+Slice expressions construct a substring or list from a string or list.
+
+The primary expression
+
+```sentinel
+a[low : high]
+```
+
+constructs a substring or list. The indices `low` and `high` select which elements of operand `a` appear in the result. The result has indices starting at 0 and length equal to `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+```
+
+For convenience, any of the indices may be omitted. A missing `low` index defaults to zero; a missing `high` index defaults to the length of the sliced operand:
+
+```sentinel
+a[2:] // same as a[2 : length(a)]
+a[:3] // same as a[0 : 3]
+a[:] // same as a[0 : length(a)]
+```
+
+The indices are in range if `0 <= low <= high <= length(a)`, otherwise they are out of range. If the indices are out of range at run time, the result is the `undefined` value.
+
+If `a` is the value `null`, the result is the `undefined` value.
+
+If `a` is any other value type, it is an error.
+
+### Calls
+
+Given an expression `f` where `f` is a function value:
+
+```sentinel
+f(a1, a2, … an)
+```
+
+calls `f` with arguments `a1, a2, ... an`. The type of the expression is the result of `f`. The arguments are evaluated left to right. Arguments are passed by value.
+
+### Operators
+
+```ebnf
+Expression = UnaryExpr | Expression binary_op Expression .
+UnaryExpr = PrimaryExpr | unary_op UnaryExpr .
+
+binary_op = logical_op | set_op | rel_op | add_op | mul_op | else_op .
+logical_op = "and" | "or" | "xor" .
+set_op = ["not"] ( "contains" | "in" ).
+rel_op = "==" | "!=" | "<" | "<=" | ">" | ">=" |
+ "is" | "is not" | "matches" | "not matches" .
+add_op = "+" | "-" .
+mul_op = "*" | "/" | "%" .
+else_op = "else" .
+unary_op = "+" | "-" | "!" | "not" .
+```
+
+#### Operator Precedence
+
+Unary operators have the highest precedence.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Binary operators of the same precedence associate from left to right. For instance, x / y _ z is the same as (x / y) _ z.
+
+### Arithmetic Operators
+
+Arithmetic operators apply to numeric values and yield a result of the same type when both operands are the same.
+
+Arithmetic operations between integer and floating-point types result in a floating-point value with the integer operand treated as a floating-point value for purposes of calculation.
+
+Arithmetic operations between numeric values (integer, floating-point) and non-numeric values (strings, lists) are not permitted.
+
+All five supported arithmetic operators (+, -, \*, /, %) apply to both integer and floating-point types; + also applies to strings.
+
+```plaintext
++ sum integers, floats, strings, lists
+* difference integers, floats
+* product integers, floats
+/ quotient integers, floats
+% remainder integers, floats
+```
+
+#### Integer operators
+
+For two integer values x and y, the integer quotient `q = x / y` and remainder `r = x % y` satisfy the following relationships:
+
+```sentinel
+x = q*y + r and |r| < |y|
+```
+
+with x / y truncated towards zero.
+
+```plaintext
+ x y x / y x % y
+ 5 3 1 2
+-5 3 -1 -2
+ 5 -3 -1 2
+-5 -3 1 -2
+```
+
+As an exception to this rule, if the dividend x is the most negative value for the int type of x (-9223372036854775808), the quotient `q = x / -1` is equal to x (and r = 0).
+
+If the divisor is a constant, it must not be zero. If the divisor is zero at run time, an error occurs.
+
+#### Integer overflow
+
+For signed integers, the operations `+`, `-`, and `*` may legally overflow and the resulting value exists and is deterministically defined by the signed integer representation, the operation, and its operands. No exception is raised as a result of overflow.
+
+#### Floating-point operators
+
+For floating-point numbers, +x is the same as x, while -x is the negation of x. The result of a floating-point division by zero is not specified beyond the IEEE-754 standard.
+
+#### String Concatenation
+
+Strings can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = "hi"
+y = "hello"
+x = x + ", " + y // "hi, hello"
+x += " and good bye" // "hi, hello and good bye"
+```
+
+String addition creates a new string by concatenating the operands.
+
+#### List Concatenation
+
+Lists can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = [1, 2]
+x = x + [2, 3] // [1, 2, 2, 3]
+x += [4] // [1, 2, 2, 3, 4]
+```
+
+List addition creates a new list by concatenating the operands.
+
+### Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+In any comparison, the two operands must be equivalent types. The only exception are integers and floats, which are considered numeric and may be compared. Any comparison between other non-matching types results in the `undefined` value.
+
+The equality operators `==`, `!=`, `is`, and `is not` apply to operands that are comparable. The ordering operators `<`, `<=`, `>`, and `>=` apply to operands that are ordered. The behavior of `is` with `==` and `is not` with `!=` is identical. These terms and the result of the comparisons are defined as follows:
+
+* Boolean values are comparable. Two boolean values are equal if they are either both true or both false.
+* Integer values are comparable and ordered, in the usual way.
+* Floating point values are comparable and ordered, as defined by the IEEE-754 standard.
+* An integer compared with floating point value treats the integer as the converted floating point value.
+* String values are comparable and ordered, lexically byte-wise.
+* Lists are comparable. Lists are equal if they are of equal length and their
+ corresponding elements are comparable and equal.
+
+Maps are not comparable. As a special case, maps may be compared to the `null` value for equality or inequality.
+
+If either operand is the `undefined` value, the result of the expression is the `undefined` value.
+
+### Logical Operators
+
+Logical operators apply to boolean values and yield a boolean result.
+
+Logical operators are evaluated left-to-right and perform short-circuit logic. The right-hand side is not guaranteed to be evaluated if a short-circuit can be performed.
+
+```plaintext
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+### Set Operators
+
+The set operators `contains` and `in` test for set inclusion for lists
+and maps, and substring inclusion for strings.
+
+Set operators may be negated by prefixing the operator with `not`: `not contains` and `not in`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+`contains` tests if the left-hand collection contains the right-hand value. For lists, this tests equality of any value. For maps, this tests equality of any key. For strings, this tests for substring existence.
+
+`in` tests if the right-hand collection contains the left-hand value. The behavior is equivalent to `contains`.
+
+The collection must be a list, map, or string. If it is the `undefined` value, the result is the `undefined` value. For any other value, the result is an error.
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+
+"test" contains "est" // true
+"test" contains "best" // false
+"test" in "testing" // true
+"best" in "testing" // false
+```
+
+### Matches Operator
+
+The matches operator tests if a string matches a regular expression.
+
+The matches operators may be negated by prefixing the operator with `not`: `not matches`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+The left-hand value is the string to test. The right-hand value is a string value representing a regular expression.
+
+The syntax of the regular expression is the same general syntax used by Python, Ruby, and other languages. More precisely, it is the syntax accepted by RE2. The regular expression is not anchored by default; any anchoring must be explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+If either operand is `undefined`, the result is the `undefined` value. For any other non-string value, the result is an error.
+
+### Else Operator
+
+Else operators can be used to provide a default value for an expression that may evaluate to the `undefined` value. Else operators return their left-hand value unless it is `undefined`, otherwise they return their right-hand value.
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else null
+```
+
+### Quantifier Expressions (any, all, filter)
+
+Quantifiers are expressions that apply a predicate to a collection (list or map). The purpose of the quantifier is to produce a result based on its particular type.
+
+`any` and `all` expressions are existential and universal quantifiers, respectively. `any` expressions are equivalent to a chain of `or` and `all` expressions are equivalent to a chain of `and`. Both expressions implement short-circuiting equivalent to logical operators.
+
+The `filter` expression is a subset quantifier and returns the subset of all values in the input collection for which the supplied predicate applies. This will be zero or more elements, up to the length of the input.
+
+The body of a quantifier expression is a boolean expression.
+
+`any` returns the boolean value `true` if any value in the collection expression results in the body expression evaluating to `true`. If the body expression evaluates to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression result in the body expression evaluating to `true`. If any value in the collection expression result in the body expression evaluating to `false`, the all expression returns `false`.
+
+For empty collections, `any` returns false and `all` returns `true`.
+
+`filter` returns a list or map, the same type as its input collection. Only the elements for which the expression body evaluated to `true` will be returned. If an iteration of the expression body results in `undefined`, the entire result set is `undefined`.
+
+When the collection is a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When the collection is a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+QuantExpr = QuantOp Expression "as" [ identifier "," ] identifier "{" BoolExpr "}" .
+QuantOp = "all" | "any" | "filter" .
+```
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" } // true if every iterations is true
+any group.tasks as t { t.driver is "vmware" } // true if any iteration is true
+filter group.tasks as t { t.driver is "vmware" } // subset of tasks that is true
+```
+
+## Statements
+
+### Expression Statements
+
+Function call expressions may exist in the statement context.
+
+```ebnf
+ExpressionStmt = Expression .
+```
+
+### Assignments
+
+Assignments set the value of an expression to the value of another expression.
+
+```ebnf
+Assignment = AssignExpr assign_op Expression .
+AssignExpr = identifier | IndexExpr .
+assign_op = [ add_op | mul_op ] "=" .
+```
+
+The assignment `x op= y` is equivalent to `x = x op (y)` for supported values of `op`.
+
+```sentinel
+a = 1
+b[12] = 42
+c["key"] = "value"
+
+a *= 12
+b[12] += 8
+```
+
+Assignments to lists and maps can be carried out using [index expressions](#index-expressions), with the operands of the index expression being evaluated alongside the right hand side expression in a right-to-left (RHS, LHS) order.
+
+In addition to the rules detailed in the section describing their use, the following rules apply when assigning to maps or lists using index expressions:
+
+* The [variable](#variables) must exist and be a list or map. Attempts to use an index assignment on an unknown variable, or a variable that not a list or map, results in a runtime error.
+* Assigning to a list or map index that already exists overwrites that index's data with evaluated right hand side's value.
+* Assigning to an unknown key in a map creates a key for that map with the evaluated right hand side's value assigned to that key.
+* Attempting to assign a value to a list index that is out of range results in a runtime error.
+
+### If Statements
+
+If statements specify the conditional execution of two branches according to the value of a boolean expression. If the expression evaluates to true, the "if" branch is executed, otherwise, if present, the "else" branch is executed.
+
+```ebnf
+IfStmt = "if" BoolExpr Block [ "else" ( IfStmt | Block ) ] .
+```
+
+```sentinel
+if x < y {
+ return x
+} else if x > z {
+ return z
+} else {
+ return y
+}
+```
+
+### Case Statements
+
+Case statements specify a selection control mechanism to conditionally execute a branch based on expression equality.
+
+Each clause that wishes to provide an expression must use the "when" keyword. Multiple expressions can be provided, separated with a comma (",").
+
+There can be one, optional, "else" clause within a `case` statement. The "else" clause is executed if all other clauses failed to run.
+
+The clauses are evaluated in the order they are listed, with the first successful evaluation being executed.
+
+A `case` statement can optionally provide an expression. If no expression is provided, it is the equivalent of writing `case true {`.
+
+```ebnf
+CaseStmt = "case" [ Expression ] "{" { CaseWhenClause } "}" .
+CaseWhenClause = CaseWhenCase ":" StatementList .
+CaseWhenCase = "when" Expression { "," Expression } | "else" .
+```
+
+```sentinel
+case x {
+when y, z:
+ return true
+else:
+ return false
+}
+
+case {
+when x > 42:
+ return true
+else:
+ return false
+}
+```
+
+### For Statements
+
+A `for` statement specifies repeated execution of a block.
+
+The expression must evaluate to a list or a map.
+
+When iterating over a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When iterating over a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+ForStmt = "for" Expressions "as" [ identifier "," ] identifier Block .
+```
+
+```sentinel
+for [1, 2, 3] as v { count += v } // basic sum
+
+for [1, 2, 3] as idx, v {
+ if idx > 1 { count += v }
+}
+
+data = { "a": 12, "b": 32 }
+for data as k { count += data[k] } // sum map values
+for data as k, v { count += v }
+```
+
+### Break Statements
+
+A `break` statement terminates execution of the innermost `for` statement
+within the same function.
+
+```ebnf
+BreakStmt = "break" .
+```
+
+```sentinel
+// Will only print "1"
+for [1, 2, 3] as v {
+ print(v)
+ break
+}
+```
+
+### Continue Statements
+
+A `continue` statement begins the next iteration of the innermost `for` loop.
+The `for` loop must be within the same function.
+
+```ebnf
+ContinueStmt = "continue" .
+```
+
+```sentinel
+// Will print 1, 3
+for [1, 2, 3] as v {
+ if v == 2 {
+ continue
+ }
+
+ print(v)
+ break
+}
+```
+
+### Return Statements
+
+A return statement in a function F terminates the execution of F and provides the result value.
+
+```ebnf
+ReturnStmt = "return" Expression .
+```
+
+A function must terminate with a return statement.
+
+The return statement can be invoked at any time during a function to terminate execution.
+
+## Imports
+
+An import adds an assignment to the global scope to external definitions.
+
+The import declarations must only appear at the top of the source file, before any other statements. Comments may appear before imports.
+
+```ebnf
+ImportDecl = "import" Name ["as" identifier] .
+Name = string_lit .
+```
+
+An import name and identifier must be distinct. Two names may not repeated even if they're declared with different identifiers.
+
+If an identifier is not specified, the identifier is the name of the import itself.
+
+An import is not a valid value. The identifier representing the import may not be used as an expression, such as in assignment statements or call expressions.
+
+Values in imports are not assignable directly via their selectors. Function calls may be used to alter the state of the external data represented by the import. Whether or not this is supported is specific to the import implementation. The exception to this assignment restriction is the memoized data returned by a call to an import (see below).
+
+Data returned by imports can be memoized, meaning that data is returned by the import in the form of a map which is then used as much as possible without having to return to the import for more data. For example, `t = time.now` returns a map so that `t.hour` will be able to be looked up without having to go back to the import for that data.
+
+Data returned by imports may be callable, meaning that methods may be called on the memoized data returned by an import. For example, given `t = time.now`, `t.after(some_previous_time)` is valid, with the receiver data being set to the local memoized data stored in `t`. It is a valid case to accept modifications to the receiver data (example: assignment to the memoized data via index expressions), as long as all data in in the receiver continues to be string-keyed. It is an invalid case to accept receiver data with non-string-typed keys. Methods may also alter the contents of receiver data so that it is different after the call is finished.
+
+As with methods, imports may also have callable keys where the data may not be memoized. Example: in the event of `v = foo.bar`, if `v.baz` is not included in the memoized data, a call will be made to the import to fetch it. The same rules and restrictions to receiver data apply to callable keys as do to method calls.
+
+The support for callable methods and non-memoized keys on data returned by imports is specific to the import implementation.
+
+The handling of import loading is specific to the runtime implementation.
+
+Implicit imports may be added by the runtime, for example for standard library definitions. An explicit import of the same name should override a matching implicit import. For example, if "time" is an implicit import and the program specifies `import "time" as cal`, then only `cal` is defined.
+
+```sentinel
+import "time"
+import "math" as m
+```
+
+## Parameters
+
+```ebnf
+ParamDecl = "param" identifier [ "default" Expression ]
+```
+
+A parameter is a way for a policy to describe variables that are expected to be supplied by the calling application, in addition to any default value.
+
+Parameter declarations must appear at the top of the source file, following imports.
+
+Like imports, comments may appear before parameters; this is mainly pertinent when the policy is not importing anything, which would make parameters the first declarations that appear in a policy.
+
+A parameter declaration describes a valid variable that is added to the scope of a policy at runtime. This variable has the same properties and rules as other variables assigned during the course of policy evaluation as defined by the specification. This includes having a dynamic type and being able to be re-assigned during execution.
+
+Parameters may only be strings, integers, floating point numbers, booleans, lists, or maps. More complex types such as rules or functions are not allowed.
+
+A parameter can specify a default value by adding one in the declaration via use of the "default" keyword, and supplying a literal for the default value. The literal for a default is a very limited expression, comprising of:
+
+* For strings, a simple string literal;
+* For integers and floating-point numbers, either a literal denoting the value, or a unary expression with the literal, and the operators - or +;
+* For booleans, the pre-declared identifiers true or false;
+* For lists and maps, their respective literals composed of values and keys (for maps) of the above values only.
+
+Any other type of expression or identifier is not allowed.
+
+A parameter that does not have a default value defined is required by the policy. Evaluating a policy with a required parameter that has not been supplied at execution results in a runtime error.
+
+Parameters must not conflict with imports or any other identifiers currently defined in the global scope, including any reserved or pre-declared identifiers. Attempting to assign a parameter to an existing value results in a runtime error.
+
+```sentinel
+import "time"
+
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+param time default "11:00" // error, conflicts with "time" import
+param undefined // error, reserved identifier
+```
+
+## Built-in Functions
+
+Built-in functions are predeclared. They are called like any other function.
+
+### Length
+
+The built-in function `length` returns the length of a collection of string.
+
+The length of a string is the number of bytes in the string. It is not necessarilly the number of characters.
+
+The length of a collection is the number of elements in that collection.
+
+The length of `undefined` is `undefined`.
+
+### Collections
+
+#### List Append
+
+The built-in function `append` appends a value to the end of a list in-place.
+
+Appending to a non-list value results in an immediate `fail`.
+
+The return value of `append` is always the `undefined` value.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+#### Map Delete
+
+The built-in function `delete` deletes elements from a map by key. The map is modified in-place.
+
+Deleting a key that does not exist does nothing.
+
+Calling delete for a non-map value results in an immediate `fail`.
+
+The return value of `delete` is always the `undefined` value.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
+
+#### Keys and Values
+
+The built-in function `keys` and `values` return the keys and values of a map, respectively. The return value is a list. Both functions return values in an unspecified order.
+
+The `keys` or `values` of `undefined` is `undefined`.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+### Range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it defaults to `1`.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4]
+range(1, 5, 2) // [1,3]
+range(0, -3, -1) // [0,-1,-2]
+```
+
+### Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a value of that type according to the rules below.
+
+For `int`:
+
+* Integer values are unchanged
+* String values are converted according to the syntax of integer literals
+* Float values are rounded down to their nearest integer value
+* Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+* Float values are unchanged
+* Integer values are converted to the nearest equivalent floating point value
+* String values are converted according to the syntax of float literals
+* Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+* String values are unchanged
+* Integer values are converted to the base 10 string representation
+* Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+* Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+* The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+* The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+* Any non-zero integer or float value converts to `true`
+* Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
+
+### Printing
+
+The built-in function `print` can be used to output formatted debug information. The runtime may omit print function calls depending on its execution mode. The runtime may choose where the output of a print function goes.
+
+`print` is a variadic function, accepting one or more values to be printed; values are separated by a single whitespace character (`" "`).
+
+The return value of `print` is always `true` to allow it to be used in boolean expressions.
+
+```sentinel
+print("hello") // "hello"
+print("hello", "world") // "hello world"
+print("The", "number", "is", 42) // "The number is 42"
+print([1, 2, 3]) // "[1, 2, 3]"
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // "false"
+```
+
+### Errors
+
+The built-in function `error` is equivalent to `print` but immediately causes execution to halt and the main rule to evaluate to `false`. The program execution is considered to have failed.
+
+## Program Execution
+
+Program execution begins by evaluating the source top to bottom. If evaluation is successful, the `main` rule is evaluated and its result is returned. Execution can be interrupted at any time by an error, which can be called explicitly or implicitly through illegal or undefined behavior.
+
+---
+
+> The content of this page is licensed under the [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
+>
+> Based on [The Go Programming Language Specification](https://golang.org/ref/spec) licensed under [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/language/undefined.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/language/undefined.mdx
new file mode 100644
index 0000000000..fa8dd50cc4
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/language/undefined.mdx
@@ -0,0 +1,98 @@
+---
+page_title: Sentinel Language - Undefined
+sidebar_title: Undefined
+sidebar_current: docs-language-undefined
+description: >-
+ The value `undefined` is a special value representing undefined behavior or an
+ unknown value. Any operation on an `undefined` value results in `undefined`.
+layout: docs
+---
+
+# Language: Undefined
+
+The value `undefined` is a special value representing undefined behavior
+or an unknown value.
+
+Undefined is not an error because there are situations where the undefined
+value can be safely ignored and the language also provides construts for
+recovering from an undefined value.
+
+The undefined value can be created manually with `undefined`. In most cases,
+undefined is created by accessing a non-existent list element or value.
+The undefine value is created in a number of other circumstances. The documentation
+will note when an undefined value may be created.
+
+Almost any operation with `undefined` results in `undefined`. For example,
+performing math on undefined, attempting to call a function that is undefined,
+etc.
+
+## Main and Undefined
+
+If the value `undefined` is returned from the top-level `main` rule, then
+the policy is considered `false`. However, the runtime provides mechanisms
+for the host application to determine the policy failure was caused by
+an undefined value and report that to the user.
+
+The `main` rule returning the undefined value should be considered a logical
+error in most cases and should probably be corrected by the policy writer.
+
+## Debugging Undefined
+
+When an `undefined` value is first created (either explicitly or implicitly),
+the runtime will record the position where the undefined was created. This
+location can be used to find logic that could be erroneous.
+
+## Boolean Logic and Undefined
+
+Boolean logic is one way to safely recover from `undefined`. If boolean
+logic can safely determine a result despite an undefined value, that result
+will be returned.
+
+For example: `undefined or true` will result in `true`, because no matter
+what actual value `undefined` could've been if the policy behaved properly,
+the boolean expression would still be true.
+
+Another scenario where `undefined` can be safely ignored is short-circuit
+logic with `and`. `false and undefined` will result in `false`.
+
+The full table of logical operations on `undefined` is below:
+
+```sentinel
+undefined or true = true
+undefined or false = undefined
+undefined or undefined = undefined
+undefined and true = undefined
+undefined and false = undefined
+undefined and undefined = undefined
+undefined xor true = undefined
+undefined xor false = undefined
+undefined xor undefined = undefined
+
+// Short-circuit examples
+false or true or undefined = true
+false or undefined or true = true
+true and false and undefined = false
+true and undefined and false = undefined
+```
+
+## Else Expressions
+
+The `else` expression allows explicit recovery from undefined and is useful
+for setting default values.
+
+`else` is an operator where the left and right hand side are values. If the
+left-hand value is `undefined`, the right-hand value is returned. Otherwise,
+the left-hand value is returned.
+
+Examples:
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else "default"
+
+// In more complex scenarios
+
+foo() else 42 == 12
+a = config.value else "default"
+```
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/language/values.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/language/values.mdx
new file mode 100644
index 0000000000..a131ef30dc
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/language/values.mdx
@@ -0,0 +1,195 @@
+---
+page_title: Sentinel Language - Values
+sidebar_title: Values
+sidebar_current: docs-language-values
+description: Values are the data that Sentinel policies operate on.
+layout: docs
+---
+
+# Language: Values
+
+Values are the _data_ that Sentinel policies operate on. You can create this
+data yourself, for example by just typing the number `42`, or you may access
+this data from an external source to make policy decisions.
+
+Values have a _type_, which determines what kind of operatins can be performed
+on the data. Example types are booleans (true and false), numbers,
+and strings (text).
+
+This page documents all the available value types. You can also
+[convert between types](#type-conversion).
+
+## Boolean
+
+A boolean is a value that is either true or false.
+
+A boolean value is created literally with `true` or `false`.
+
+As a policy language, booleans are central to the behavior of Sentinel.
+Booleans are used as conditions in [if statements](/sentinel/language/if),
+are the result of [rules](/sentinel/language/rules), and more. The ultimate
+result of a Sentinel policy is true or false.
+
+## Integer
+
+An integer is a whole number.
+
+An integer is a 64-bit value. This means it can represent numbers from
+-9,223,372,036,854,775,808 to 9,223,372,036,854,775,808.
+
+Integers are created by typing them out literally with no
+separators (such as a comma). An optional prefix can set a non-decimal base:
+`0` for octal, and `0x` for hexadecimal. In hexadecimal literals, letters
+`a-f` and `A-F` represent values 10 to 15.
+
+Example integers are shown below:
+
+```sentinel
+42
+0600
+0xBadFace
+170141183460469
+```
+
+Integers are used for math and numerical comparison.
+
+## Float
+
+A float is a number with a decimal point. It has an integer part, a decimal
+point, a fractional part, and optionally an exponent part. Example
+floats are shown below:
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+Floats are IEEE-754 64-bit floating point numbers.
+
+## String
+
+Strings are text values.
+
+Strings are created by wrapping text in double quotes, such as `"hello"`.
+Within the quotes, any character may appear except newline and an unescaped
+double quote. The text between the quotes is the value.
+
+Because Sentinel policies must be UTF-8 encoded text, strings themselves are
+UTF-8 encoded text. This means you can put any value UTF-8 character into
+a string, such as `"日本語"`.
+
+You can have a string with a literal double-quote by _escaping_ it with
+a backslash. For example: `"they said \"hello\""` turns into the value
+`they said "hello"`.
+
+Backslash escapes can be used for much more than only escaping a double quote.
+Newlines are `\n`, a literal backslash is `\\`, and many more. The full list
+of available backslash escapes can be found
+[in the language specification](/sentinel/language/spec#string-literals).
+
+Example strings:
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+Strings support indexing. Indexing a string will access that _byte_
+in the string as if the string were a byte array. This is an important
+distinction: it _will not_ access the character at that position if you're
+using multi-byte characters.
+
+Strings support [slicing](/sentinel/language/slices) to efficiently create
+substrings.
+
+## List
+
+See [Lists](/sentinel/language/lists).
+
+## Map
+
+See [Maps](/sentinel/language/maps).
+
+## Rule
+
+See [Rules](/sentinel/language/rules).
+
+## Function
+
+See [Functions](/sentinel/language/functions).
+
+## Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a
+value of that type. Some examples are shown below followed by a list of exact
+rules for type conversion.
+
+```sentinel
+int(42) // 42
+int("42") // 42
+int(42.8) // 42
+int(true) // 1
+
+float(1.2) // 1.2
+float(1) // 1.0
+float("4.2") // 4.2
+float(true) // 1.0
+
+string("foo") // "foo"
+string(88) // "88"
+string(0xF) // "15"
+string(true) // "true"
+
+bool("true") // true
+bool(1) // true
+bool(-1) // true
+bool(0.1) // true
+bool("false") // false
+bool(0) // false
+```
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/language/variables.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/language/variables.mdx
new file mode 100644
index 0000000000..69655b0c63
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/language/variables.mdx
@@ -0,0 +1,100 @@
+---
+page_title: Sentinel Language - Variables
+sidebar_title: Variables
+sidebar_current: docs-language-vars
+description: Variables store values that can be used later.
+layout: docs
+---
+
+# Language: Variables
+
+Variables store values that can be used later.
+
+Most notably, a variable assignment of `main` is required for all
+Sentinel policies. This is the main [rule](/sentinel/language/rules) that
+is executed to determine the result of a policy. The `main` rule may
+use other variables that are assigned in the policy.
+
+Example:
+
+```sentinel
+a = 1
+b = a + 1
+```
+
+In this example, two variables are assigned: `a` and `b`. `a` is given
+the value of "1" and `b` uses the `a` to calculate its own value.
+
+## Syntax
+
+The syntax for assigning a variable is:
+
+```sentinel
+IDENTIFIER = VALUE
+```
+
+On the left of the equal sign is an _identifier_. This is a name for your
+variable and will be how you reference it later. An identifier is any
+combination of letters and digits, but must start with a letter. An
+underscore `_` is also a valid letter.
+
+On the right is any valid Sentinel value or expression. A value is a
+literal value such as a number or string. An expression is some computed
+value such as doing math, calling a function, etc.
+
+## Assignment and Reassignment
+
+A variable is _assigned_ when it is given a value. You can also _reassign_
+variables at any time by setting it to a new value. The new value takes
+effect for any subsequent use of that variable. A variable can be reassigned
+to a different type.
+
+For example:
+
+```sentinel
+a = 1 // a = 1
+b = a // b = 1
+a = "value" // a = "value", b = 1
+c = a // c = "value", b = 1
+```
+
+In the above example you can see that the variables are set and reassigned.
+Notice that the value of a variable is the _current_ value, and that reassigning
+a variable only affects _future_ uses of that variable. You can see this with
+`c` and `b` being different values.
+
+## Unassigned Variables
+
+Using a variable that is _unassigned_ is an error.
+
+In the example below, the first line would result in the policy erroring.
+Sentinel is executed top-down and the value of `c` is not available yet
+on the first line.
+
+```sentinel
+a = c // Error!
+c = 1
+```
+
+## Type Conversion
+
+While a topic more related to [values][lang-values], variables can be assigned
+(or-reassigned) a different value type via type conversion.
+
+[lang-values]: /sentinel/language/values
+
+```sentinel
+s = "1.1"
+a = int(s) // a = 1
+a = float(s) // a = 1.1
+
+a = 1
+s = string(a) // s = "1"
+```
+
+For more details, see the type conversion sections in the
+[values][lang-values-type-conversion] page, or the [Sentinel Language
+Specification][lang-spec-type-conversion].
+
+[lang-values-type-conversion]: /sentinel/language/values#type-conversion
+[lang-spec-type-conversion]: /sentinel/language/spec#type-conversion
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/nomad/index.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/nomad/index.mdx
new file mode 100644
index 0000000000..b519c1bf29
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/nomad/index.mdx
@@ -0,0 +1,50 @@
+---
+page_title: Nomad and Sentinel
+sidebar_title: Nomad
+sidebar_current: docs-app-nomad
+description: >-
+ Nomad Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies can currently execute on job
+ submission (creation, update).
+layout: docs
+---
+
+# Nomad
+
+[Nomad Enterprise](https://www.hashicorp.com/products/nomad/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies can currently execute on job submission (creation, update).
+
+Sentinel policies have full access to the job structure. This allows the
+Sentinel policy to control behavior based on any attribute within a job,
+such as the driver, resource requests, network configuration, volume
+configuration, and more. The information that Sentinel policies have access
+to will expand over time.
+
+Nomad fully supports all [enforcement levels](/sentinel/concepts/enforcement-levels).
+For soft mandatory policies, the `sentinel-override` capability must be
+available on the user's ACL policy to allow override. Overrides are always
+logged.
+
+The Nomad integration with Sentinel is documented in depth in the
+[Nomad Enterprise documentation](https://www.nomadproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Only allow Docker-based jobs.**
+
+```sentinel
+# Test policy only allows Docker based tasks
+main = rule { all_drivers_docker }
+
+# all_drivers_docker checks that all the drivers in use are Docker
+all_drivers_docker = rule {
+ all job.task_groups as tg {
+ all tg.tasks as task {
+ task.driver is "docker"
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/terraform/index.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/terraform/index.mdx
new file mode 100644
index 0000000000..4825ad24fe
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/terraform/index.mdx
@@ -0,0 +1,60 @@
+---
+page_title: Terraform and Sentinel
+sidebar_title: Terraform
+sidebar_current: docs-app-terraform
+description: >-
+ Sentinel can be used with Terraform and Terraform Enterprise to enforce policy
+ on Terraform configurations, states, and plans.
+layout: docs
+---
+
+# Terraform
+
+[Terraform Enterprise](https://www.hashicorp.com/products/terraform/) uses Sentinel to enforce
+policy on [Terraform](https://www.terraform.io) configurations, states, and plans.
+
+The Sentinel integration with Terraform runs within
+[Terraform Enterprise](https://www.hashicorp.com/products/terraform/)
+after a `terraform plan` and before a `terraform apply`. The policies
+have access to the created plan, the state at the time of the plan,
+and the configuration at the time of the plan.
+
+The Terraform integration with Sentinel is documented in depth in the [Terraform Enterprise documentation](https://www.terraform.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show basic examples.
+
+### Examples
+
+**Example: All AWS instances must have a tag**
+
+```sentinel
+import "tfplan"
+
+main = rule {
+ all tfplan.resources.aws_instance as _, instances {
+ all instances as _, r {
+ (length(r.applied.tags) else 0) > 0
+ }
+ }
+}
+```
+
+**Example: Only allow GCP instance sizes smaller than `n1-standard-16`**
+
+```sentinel
+import "tfplan"
+
+allowed_machine_types = [
+ "n1-standard-1",
+ "n1-standard-2",
+ "n1-standard-4",
+ "n1-standard-8",
+]
+
+main = rule {
+ all tfplan.resources.google_compute_instance as _, instances {
+ all instances as _, r {
+ r.applied.machine_type in allowed_machine_types
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/vault/index.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/vault/index.mdx
new file mode 100644
index 0000000000..9c37e0eedb
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/vault/index.mdx
@@ -0,0 +1,65 @@
+---
+page_title: Vault and Sentinel
+sidebar_title: Vault
+sidebar_current: docs-app-vault
+description: >-
+ Vault Enterprise uses Sentinel to augment the built-in policy system to
+ provide Role Governing Policies (RGPs) and Endpoint Governing Policies (EGPs)
+ to enable complex, flexible policies across identities and endpoints.
+layout: docs
+---
+
+# Vault
+
+[Vault Enterprise](https://www.hashicorp.com/products/vault/) uses Sentinel
+to augment the built-in policy system to provide Role Governing Policies (RGPs)
+and Endpoint Governing Policies (EGPs) to enable complex, flexible policies
+across identities and endpoints.
+
+**Role Governing Policies (RGPs)** are Sentinel policies that are tied to
+particular tokens, Identity entities, or Identity groups. They have access to
+a rich set of controls across various aspects of Vault. These are evaluated
+whenever a token they're attached to is used.
+
+**Endpoint Governing Policies (EGPs)** are Sentinel policies that are tied to
+particular paths instead of tokens. They have access to as much request
+information as possible, but they can take effect even on unauthenticated
+paths, such as login paths.
+
+The Vault integration with Sentinel is documented in depth in the
+[Vault Enterprise documentation](https://www.vaultproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Endpoint policy that requires MFA authentication from a corporate network.**
+
+```sentinel
+import "sockaddr"
+
+# We expect logins to come only from our private IP range
+cidrcheck = rule {
+ sockaddr.is_contained(request.connection.remote_addr, "10.20.0.0/16")
+}
+
+# Require Ping MFA validation to succeed
+ping_valid = rule {
+ mfa.methods.ping.valid
+}
+
+main = rule when request.path is "auth/ldap/login" {
+ ping_valid and cidrcheck
+}
+```
+
+**Example: Endpoint policy that disallows tokens created before a certain time.**
+
+```sentinel
+import "time"
+import "strings"
+
+main = rule when not strings.has_prefix(request.path, "auth/ldap/login") {
+ time.load(token.creation_time).unix > time.load("2017-09-17T13:25:29Z").unix
+}
+```
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/writing/basic.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/writing/basic.mdx
new file mode 100644
index 0000000000..1a881476a4
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/writing/basic.mdx
@@ -0,0 +1,107 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Basics
+sidebar_current: docs-writing-basic
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Policy Basics
+
+Sentinel policies are easy to write while still supporting advanced constructs
+for creating complex policies. This page will explain the basics of writing
+Sentinel policies to get started. You don't need any prior Sentinel knowledge,
+but we do recommend reading the
+[getting started guide](/sentinel/intro/getting-started/first-policy) and
+[language guide](/sentinel/language) after this.
+
+Sentinel policies are text files written using the [Sentinel language](/sentinel/language).
+The policies are evaluated top-to-bottom. The value of `main` after execution
+determines whether a policy passes or fails.
+
+## The Simplest Policy
+
+Sentinel only requires that a policy have a `main` variable that evaluates to
+a boolean value.
+
+A valid example is shown below:
+
+```sentinel
+main = 10 > 5
+```
+
+This type of minimal policy is not purely academic. In practice, simple
+policies can often be reduced to a single line logical statement resulting
+in `true` or `false`. However, the expression is usually wrapped in a
+[rule](/sentinel/language/rules) for testing reasons.
+
+You can verify Sentinel will execute this minimal policy using the CLI:
+
+```
+$ sentinel apply minimal.sentinel
+Pass
+```
+
+## Logical Expressions
+
+Policy is at its core a set of logic: you can or can not perform some action
+under a certain set of circumstances. Those circumstances are logical
+expressions. Therefore, Sentinel policies primarily translate into logical
+expressions.
+
+Detailed documentation on boolean expressions is
+[available in the language guide](/sentinel/language/boolexpr).
+
+A simple numerical comparison was seen in the first example on this page.
+Sentinel also provides inclusion operators such as `contains`, `any`, `all`, and
+more. Sentinel allows some operators to have aliases to promote readability
+while remaining programmer-familiar, such as `==` which can equivalently be `is`.
+
+The example below verifies that all numbers in a list are even:
+
+```sentinel
+numbers = [6, 22, 8, 4, 12]
+main = all numbers as n { n % 2 is 0 }
+```
+
+## Variables
+
+A policy will very often use variables. Applications such as
+[Nomad](https://www.nomadproject.io) inject variables into the global
+scope of a policy for making policy decisions. For example, Nomad injects
+the job that is being run into the policy scope. Knowing how to use
+variables is critical to effectively using Sentinel.
+
+Detailed documentation on how to define and access variables is
+[available in the language guide](/sentinel/language/variables).
+
+Variables can be defined and used explicitly. For example:
+
+```sentinel
+value = 10
+main = value > 5
+```
+
+But they may also be introduced implictly by the host system. Nomad
+injects `job` into policies to describe the job that is being run. The
+policy below is a valid policy that requires a job have two task groups.
+Notice that `job` is not defined anywhere. It is implicitly inserted by
+the host application (in this case Nomad). Refer to the application you're
+writing policy for to determine if it implicitly inserts values.
+
+```sentinel
+main = length(job.task_groups) is 2
+```
+
+## And more!
+
+The Sentinel language supports many more features such as functions,
+loops, and more. You can learn about all of this in the
+[complete language guide](/sentinel/language).
+
+The other pages in the [writing policy](/sentinel/writing)
+will cover other information you need to know about writing Sentinel
+policies that isn't simply a language reference.
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/writing/debugging.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/writing/debugging.mdx
new file mode 100644
index 0000000000..eb7b6ea2a9
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/writing/debugging.mdx
@@ -0,0 +1,46 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Debugging
+sidebar_current: docs-writing-debugging
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Debugging
+
+As policies become more complex, it becomes harder to understand exactly why
+a policy may be behaving differently than you expect.
+
+Prior to actively debugging, there are multiple best practices we recommend
+following. We recommend breaking your policy down into a set of
+[rules](/sentinel/writing/rules). This will make it easier to understand
+[traces](/sentinel/writing/traces) and make it easier to
+[test](/sentinel/writing/testing) your policies.
+This should help to debug policies up to a significant complexity.
+
+## Print Logging
+
+The built-in [print function](/sentinel/language/logging-errors#print)
+can be used to log data. The print output is only shown during failures
+or when tracing is explicitly enabled.
+
+The print function is [variadic](https://en.wikipedia.org/wiki/Variadic_function).
+Each argument specifies a value to print together, concatenated with a space
+in between each. You can put any type as an argument and Sentinel will
+convert that to a human-friendly string format.
+
+Example:
+
+```sentinel
+value = 42
+print("the value is", value) // the value is 42
+
+map = { "foo": false }
+print(map) // { "foo": false }
+```
+
+The `print` function returns the value `true` so that it can also be
+used within rule expressions. Note that if short-circuiting is occuring
+within the boolean logic, the `print` function may never be reached.
+The runtime contains a special case where `print` will not short-circuit
+its own logic, so `print() or expr` will always evaluate `expr`.
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/writing/imports.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/writing/imports.mdx
new file mode 100644
index 0000000000..3b1e6732e0
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/writing/imports.mdx
@@ -0,0 +1,124 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Imports
+sidebar_current: docs-writing-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom import](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only
+local context for making policy decisions.
+
+Sentinel also comes with a set of [standard imports](/sentinel/imports).
+Standard imports are available to every Sentinel policy to help policy writers
+with common tasks such as working with the time, network addresses, and more.
+
+-> **This page is about writing policies that _use_ imports.**
+If you're interested in
+_creating_ a new import, please see the section on
+[writing import plugins](/sentinel/extending). Anyone
+can write a custom import to access new data.
+
+## Using Imports
+
+To use an import, you use the `import` keyword at the top of your policy.
+This specifies the name of the import you want to use. The application
+you're writing the policy for must already be
+[configured](/sentinel/extending/install) to provide that import.
+
+Details on imports can be found in the
+[import section in the language reference](/sentinel/language/imports).
+
+In the example below, we use the [time](/sentinel/imports/time) import:
+
+```sentinel
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+There are two options to develop a policy locally when using imports:
+configure Sentinel to launch the import on `apply`, or mock the import
+using `mock`. The former requires access to the import plugin while the
+latter is faster (doesn't have to launch a process) and doesn't require
+the plugin.
+
+## Mocking Imports
+
+The first option to developing policies locally is to mock the import values.
+When mocking an import, you don't need the import plugin available. This can be
+useful since some imports may not be available as a plugin and may only be
+available to the application the policy runs in.
+
+Mocks are specified via the [configuration
+file](/sentinel/commands/config). Mocks can also be used for
+[testing](/sentinel/writing/testing).
+
+You can supply mock configuration one of two ways, depending on your use case:
+
+* **[Using static data](/sentinel/commands/config#mocking-static-data):**
+ Use this method when you can accurately represent your mock data in JSON and
+ do not need to mock complex Sentinel features such as functions.
+* **[Using Sentinel code](/sentinel/commands/config#mocking-with-sentinel-code):**
+ Use this method when using a static JSON object is insufficient, such as when
+ you need to mock functions or other complex Sentinel features.
+
+Our example does not require complex data to be mocked, so a static JSON object
+is sufficient:
+
+```json
+{
+ "mock": {
+ "time": {
+ "now": {
+ "hour": 12,
+ "weekday_name": "Tuesday"
+ }
+ }
+ }
+}
+```
+
+This can be used via the CLI:
+
+```
+$ sentinel apply -config=config.json policy.sentinel
+Pass
+```
+
+## Launching Imports
+
+If you have access to the plugin binary, you can launch the import. The
+benefit of this is that it is really using the import to test your
+policy. If the import changes, your policies may start failing. If you
+only use mock data and the import changes, your policies will still
+appear to work.
+
+Imports are configured in the configuration file:
+
+```json
+{
+ "imports": {
+ "time": {
+ "path": "/path/to/sentinel-time-import"
+ }
+ }
+}
+```
+
+This would require the `sentinel-time-import` binary. For this example
+this doesn't currently exist. We plan on writing one to provide for this
+section of the documentation.
+
+## Custom Imports
+
+You can also [create your own imports](/sentinel/extending).
+
+If your policy decisions could benefit from accessing external information,
+then you can use custom imports as a way to do this.
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/writing/index.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/writing/index.mdx
new file mode 100644
index 0000000000..e7743c0011
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/writing/index.mdx
@@ -0,0 +1,36 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Writing Policy
+sidebar_current: docs-writing
+description: >-
+ Sentinel provides a language and workflow for building policy across any
+ system that embeds Sentinel. By learning Sentinel once, you are able to
+ effectively control access to many systems using Sentinel's powerful features.
+ Basic concepts that are important to understand for Vault usage.
+layout: docs
+---
+
+# Writing Sentinel Policy
+
+This section covers how to write Sentinel policies.
+
+It is recommended that you complete the
+[getting started guide](/sentinel/intro/getting-started/first-policy) prior to
+reading this section of the documentation. The getting started guide will
+explain all the basics of writing and testing policies. This section of the
+documentation will serve as more of a reference guide to all available
+features of Sentinel.
+
+Sentinel provides a language and workflow for building policy across any system
+that embeds Sentinel. By learning Sentinel once, you are able to effectively
+control access to many systems using Sentinel's powerful features.
+
+Sentinel also provides a [local CLI](/sentinel/commands) for developing and testing
+Sentinel policies. This CLI can be integrated into a daily developer's workflow
+along with CI to treat [policy as code](/sentinel/concepts/policy-as-code).
+
+Sentinel uses its own [language](/sentinel/language) for writing
+policies. You can view a [language reference](/sentinel/language)
+as well as the [specification](/sentinel/language/spec) for details. You
+don't have to read those documents immediately, since the language should
+be easy enough to pick up throughout this section.
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/writing/rules.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/writing/rules.mdx
new file mode 100644
index 0000000000..a99b58a04e
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/writing/rules.mdx
@@ -0,0 +1,49 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Rules
+sidebar_current: docs-writing-rules
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Rules
+
+Rules form the basis of a policy by representing behavior that is either
+passing or failing (true or false). Rules are a first class language construct
+in Sentinel. A policy can and should be broken down into rules to aid with
+readability, testability, and performance.
+
+Rules provide:
+
+* **Readability:** A policy that is broken down into rules can be read
+ more easily. The logic becomes clearer to see for policy writers when
+ they come back to it.
+
+* **Debuggability:** When [tracing](/sentinel/writing/tracing) is enabled,
+ the trace is formatted by rule names. A policy that is broken down into
+ more rules is more easily debugging by noticing unexpected rule values.
+
+* **Testability:** [Policy testing](/sentinel/writing/testing) is
+ built on asserting the values of rules. By breaking logic down into
+ rules, you're able to more effectively test your policies.
+
+* **Performance:** As explained in the next section, rules are only
+ evaluated once on demand. This means that a rule referenced multiple
+ time only has a one-time performance cost. For complex logic, this
+ could result in improved performance.
+
+An example usage of rules is shown below:
+
+```sentinel
+is_sunny = rule { weather is "sunny" }
+is_wednesday = rule { day is "wednesday" }
+main = rule { is_sunny and is_wednesday }
+```
+
+Details about rules and their behavior can be found in
+[the language reference](/sentinel/language/rules). Rules have important
+behavior so we recommend reading the language reference on rules.
+Rules will be used throughout the remainder of the documentation.
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/writing/testing.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/writing/testing.mdx
new file mode 100644
index 0000000000..b812ccfe73
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/writing/testing.mdx
@@ -0,0 +1,340 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Testing
+sidebar_current: docs-writing-testing
+description: >-
+ Sentinel has a built-in test framework to validate a policy behaves as
+ expected.
+layout: docs
+---
+
+# Testing
+
+Sentinel has a built-in test framework to validate a policy behaves as expected.
+
+With an ever-increasing amount of automation surrounding technology,
+the guardrails provided by policies are a critical piece towards ensuring
+expected behavior. As a reliance on correct policy increases, it is important
+to test and verify policies.
+
+Testing is a necessary step to fully realize
+[policy as code](/sentinel/concepts/policy-as-code). Just as good software
+is well tested, a good set of policies should be equally well tested.
+
+## Test Folder Structure
+
+Policies are tested by asserting that [rules](/sentinel/writing/rules)
+are the expected values given a pre-configured input. Tests are run
+by executing the [test command](/sentinel/commands/test).
+
+Sentinel is opinionated about the folder structure required for tests.
+This opinionated structure allows testing to be as simple as running
+`sentinel test` with no arguments. Additionally, it becomes simple to test
+in a CI or add new policies.
+
+The structure Sentinel expects is `test//*.json` where ``
+is the name of your policy file without the file extension. Within
+that folder is a list of JSON files. Each JSON file represents a single
+test case. Therefore, each policy can have multiple tests associated with
+it.
+
+## Test Case Format
+
+Each JSON file within the test folder for a policy is a single test case.
+
+The JSON file is the same configuration format as the
+[apply configuration file](/sentinel/commands/apply#configuration-file).
+The format lets you define mock data, imports to use, and more.
+This mock data is the key piece in being able to test policies: you
+craft a specific scenario and assert your policy behaves as you expect.
+
+Test cases also use the special key `test` within the configuration file
+to assert the boolean value of [rules](/sentinel/writing/rules). If
+the `test` key is omitted, the policy is expected to pass (the same
+as asserting that the `main` rule is true). If the test key is specified,
+only the rules specified in the map will be asserted. This means if you
+omit `main`, then the final policy result is not asserted.
+
+Example with assertions:
+
+```json
+{
+ "param": {
+ "day": "monday",
+ "hour": 7
+ },
+
+ "test": {
+ "main": false,
+ "is_open_hours": false,
+ "is_weekday": true
+ }
+}
+```
+
+The configuration above specifies some parameter data, and asserts the result of
+some rules. This is the same configuration used in the example section below.
+
+## Example
+
+Lets use the following file as an example. Save this file to a directory
+and name it `policy.sentinel`. It can be named anything with the `sentinel`
+extension, but by naming it `policy.sentinel` your output should match
+the example output on this page.
+
+```sentinel
+// The day of the week.
+param day
+
+// The hour of the day.
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+### A Passing Test
+
+Next, let's define a single test case. Relative to where you saved
+the policy, create a file at the path `test/policy/good.json`.
+
+```json
+{
+ "param": {
+ "day": "monday",
+ "hour": 14
+ }
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/good.json
+```
+
+This passed because the policy passed. We didn't assert any specific
+rules. By not specifying any assertions, `test` expects the policy itself
+to fully pass.
+
+### A Failing Test
+
+Define another test case to fail. We want to verify our policy fails when expected, too.
+
+Save the following as `test/policy/7-am.json`:
+
+```json
+{
+ "param": {
+ "day": "monday",
+ "hour": 7
+ }
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+FAIL - policy.sentinel
+ FAIL - test/policy/7-am.json
+ expected "main" to be true, got: false
+
+ trace:
+ FALSE - policy.sentinel:9:1 - Rule "main"
+ FALSE - policy.sentinel:9:15 - is_open_hours
+ FALSE - policy.sentinel:8:24 - hour > 8 and hour < 17
+ FALSE - policy.sentinel:8:24 - hour > 8
+
+ FALSE - policy.sentinel:8:1 - Rule "is_open_hours"
+ FALSE - policy.sentinel:8:24 - hour > 8
+ PASS - test/policy/good.json
+```
+
+As you can see, the test fails because "main" is false. This is good
+because the policy _should_ have failed since we specified an invalid
+hour. But, we expect main to be false and don't want our test to fail!
+Update `7-am.json` to add test assertions:
+
+```json
+{
+ "param": {
+ "day": "monday",
+ "hour": 7
+ },
+
+ "test": {
+ "main": false,
+ "is_open_hours": false,
+ "is_weekday": true
+ }
+}
+```
+
+And when we run the tests:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/7-am.json
+ PASS - test/policy/good.json
+```
+
+The test passes. We asserted that we expect the `main` rule to be false,
+the `is_open_hours` rule to be false, and the `is_weekday` rule to be
+true. By asserting some rules are true, we can verify that our policy
+is failing for reasons we expect.
+
+## Mocking
+
+The above example demonstrates how to test by supplying different
+[parameters](/sentinel/language/parameters). Parameters in a policy can be
+specifically useful when you want to control user-defined input values to a
+policy.
+
+However, generally, when testing, you will need mimic the conditions you will
+see in production. Production implementations of Sentinel will supply data using
+one of two methods:
+
+* **Global data:** Data is injected directly into the policy's scope and is
+ accessible using normal identifiers, similar to variables.
+* **Imports:** Data is stored behind an [import](/sentinel/language/imports)
+ and loaded on demand as needed by the policy author.
+
+Proper testing of a policy requires that these values be able to be _mocked_ -
+or, in other words, simulated in a way that allows the accurate testing of the
+scenarios that a policy could reasonably pass or fail under.
+
+Mocking both globals and imports can be done by setting various parts of the
+configuration file.
+
+### Mocking Globals
+
+Demonstrating the mocking of globals can be seen by making a few modifications to
+our example policy, removing the `param` declarations:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Then, change the `param` section in the configuration file to
+[`global`](/sentinel/commands/config#global-1).
+
+```json
+{
+ "global": {
+ "day": "monday",
+ "hour": 14
+ }
+}
+```
+
+This test should still pass, as if nothing had happened, although what we've
+done is shifted our parameters to globals, simulating an environment where `day`
+and `hour` are already defined for us.
+
+### Mocking Imports
+
+To mock imports, we need to use the
+[`mock`](/sentinel/commands/config#mock-imports) section of the
+configuration file.
+
+Let's say the above example is behind an import named `time`.
+
+-> **NOTE:** [`time`](/sentinel/imports/time) is a valid standard import.
+This example may not be accurate to the
+import's syntax.
+
+The code now looks like this:
+
+```sentinel
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To mock this import, we can [mock it as static
+data](/sentinel/commands/config#mocking-static-data). The configuration
+file now looks like, without assertions:
+
+```json
+{
+ "mock": {
+ "time": {
+ "now": {
+ "weekday_name": "Monday",
+ "hour": 14
+ }
+ }
+ }
+}
+```
+
+The policy will now pass, with the `time` import mocked.
+
+Data can also be [mocked as Sentinel
+code](/sentinel/commands/config#mocking-with-sentinel-code). In this case,
+the above configuration file would look like:
+
+```json
+{
+ "mock": {
+ "time": "mock-time.sentinel"
+ }
+}
+```
+
+And a file named `mock-time.sentinel` would now hold your mock values:
+
+```sentinel
+day = "monday"
+hour = 14
+```
+
+Mocking as Sentinel code allows more complex details to be mocked as well, such
+as functions. Say we wanted to mock the `time.load()` function. To mock this,
+just add it to the `mock-time.sentinel` file:
+
+```sentinel
+load = func(_) {
+ return {
+ "weekday_name": "Monday",
+ "hour": 14,
+ }
+}
+```
+
+Your code can now be written as:
+
+```sentinel
+import "time"
+
+t = time.load("a_mock_timestamp")
+
+is_weekday = rule { t.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { t.hour > 8 and t.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To see more details, see the [Mock
+Imports](/sentinel/commands/config#mock-imports) section in the
+configuration file.
+
+## Test Automation
+
+The `sentinel test` command was designed to be automation friendly.
+We encourage you to enable a CI such as [Travis CI](https://travis-ci.com/)
+on your policy repositories to continuously run tests. An example
+`.travis.yml` configuration file is shown below:
+
+```yaml
+language: bash
+script: sentinel test
+```
diff --git a/content/sentinel/v0.14.x/content/sentinel/docs/writing/tracing.mdx b/content/sentinel/v0.14.x/content/sentinel/docs/writing/tracing.mdx
new file mode 100644
index 0000000000..691dbbe8f4
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/docs/writing/tracing.mdx
@@ -0,0 +1,98 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Traces
+sidebar_current: docs-writing-tracing
+description: >-
+ Sentinel provides execution traces to better understand the execution of a
+ policy.
+layout: docs
+---
+
+# Traces
+
+Sentinel provides execution traces to better understand the execution of a policy.
+
+When using the Sentinel [CLI](/sentinel/commands), traces are shown
+on policy failure during `apply` or `test`, or when the `-trace` flag is
+explicitly specified. For Sentinel-enabled applications, traces are optional
+and may require special configuration to enable. Most Sentinel-enabled
+applications log traces on failure.
+
+-> **Note:** We're actively improving Sentinel tracing in each release
+to provide better data and improve readability. The exact format of a
+trace may not match the Sentinel version embedded in the application you're
+using, but the data should be similar.
+
+## Analyzing a Trace
+
+To show traces, let's use the example policy below with the CLI:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Let's cause the policy to fail so the trace is more interesting.
+If you're on a terminal that supports it, the trace output will be colored
+so you can more clearly see passes, failures, and rules.
+
+```
+$ sentinel apply -global 'day=sunday' -global 'hour=14' policy.sentinel
+Fail
+
+FALSE - policy.sentinel:6:1 - Rule "main"
+ TRUE - policy.sentinel:6:15 - is_open_hours
+ TRUE - policy.sentinel:5:24 - hour > 8 and hour < 17
+ TRUE - policy.sentinel:5:24 - hour > 8
+ TRUE - policy.sentinel:5:37 - hour < 17
+ FALSE - policy.sentinel:6:33 - is_weekday
+ FALSE - policy.sentinel:4:21 - day not in ["saturday", "sunday"]
+
+TRUE - policy.sentinel:5:1 - Rule "is_open_hours"
+ TRUE - policy.sentinel:5:24 - hour > 8
+ TRUE - policy.sentinel:5:37 - hour < 17
+
+FALSE - policy.sentinel:4:1 - Rule "is_weekday"
+```
+
+The least-nested values are all rules. It begins with the
+value of that rule (`TRUE` or `FALSE`) followed by the location and name
+of the rule. Notice that the `main` rule is false, the `is_open_hours`
+rule is true, and the `is_weekday` rule is false.
+
+We know that main requires _both_ `is_open_hours` and `is_weekday` to be
+true, so based on the trace, we can tell that main failed because
+`is_weekday` was false. We can then go to the definition of `is_weekday`
+in the source and determine the logical failure.
+
+The trace also breaks down more complex logical expressions. Notice that
+`is_open_hours` is split into the two sides of the `and`, but `is_weekday`
+has no values under it. Because `is_weekday` is itself a single boolean
+expression, the failure isn't repeated. But for `is_open_hours`, you can also
+see the result of each side of the boolean expression.
+
+## Missing Rules or Expressions
+
+The Sentinel runtime sometimes optimizes away executions completely. This
+can result in a trace being incomplete. If you don't see a rule or boolean
+expression within the trace, it means the runtime didn't execute it at all.
+
+Consider the example policy:
+
+```sentinel
+main = rule { 42 > 40 or 10 < 5 }
+```
+
+If we run it with trace:
+
+```
+$ sentinel apply -trace policy.sentinel
+TRUE - policy.sentinel:1:1 - Rule "main"
+ TRUE - policy.sentinel:1:15 - 42 > 40
+```
+
+Notice that the expression `10 < 5` is not present in the trace.
+Since `42 > 40` is true, the entire `or` can be short-circuited and
+the result is known to be true. Remaining parts of the expression are not
+executed and are therefore not present in the trace.
diff --git a/content/sentinel/v0.14.x/content/sentinel/intro/getting-started/first-policy.mdx b/content/sentinel/v0.14.x/content/sentinel/intro/getting-started/first-policy.mdx
new file mode 100644
index 0000000000..e0cc4cef50
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/intro/getting-started/first-policy.mdx
@@ -0,0 +1,54 @@
+---
+page_title: Your First Sentinel Policy
+sidebar_title: Your First Policy
+sidebar_current: intro-getting-started-first
+description: Let's begin by writing a working Sentinel policy.
+layout: intro
+---
+
+# Your First Sentinel Policy
+
+Sentinel is a system to enforce complex policies on an integrated application.
+
+Writing Sentinel policy requires minimal programming experience. The Sentinel
+language is designed to be approachable and learned quickly and easily. Whether
+you're a professional programmer or someone who uses SQL and Excel, you can
+learn to write Sentinel policies.
+
+Let's begin by writing a simple, working Sentinel policy:
+
+```sentinel
+hour = 4
+main = rule { hour >= 0 and hour < 12 }
+```
+
+This is a valid Sentinel policy. It will pass since we hardcoded the
+`hour` to be 4. In a real system, `hour` may be something that is provided
+to us and actually set to the current hour. We'll learn more about that later.
+
+For now, try running this policy locally. Save the above example to a file
+named `policy.sentinel` and execute it. Then, modify the policy to make it
+fail. Play around more if you'd like.
+
+```
+$ sentinel apply policy.sentinel
+Pass
+```
+
+## Main
+
+Every Sentinel policy must have a `main` rule. This is the rule that
+is evaluated to determine the result of a policy. A rule describes a
+condition that must be true for the rule to be true. Within the rule,
+a single boolean expression describes the condition. In the example above,
+it is a straightforward check that the time is greater than zero (midnight)
+and less than 12.
+
+It is easy to imagine that such a rule might be used in a system such
+as [Nomad](https://www.nomadproject.io) to restrict the times when a
+deploy can occur. The power of arbitrary logical statements within Sentinel
+allows Sentinel policies to restrict almost any behavior.
+
+Next, we'll
+[introduce and explain rules](/sentinel/intro/getting-started/rules)
+so we can use them in our policies.
diff --git a/content/sentinel/v0.14.x/content/sentinel/intro/getting-started/imports.mdx b/content/sentinel/v0.14.x/content/sentinel/intro/getting-started/imports.mdx
new file mode 100644
index 0000000000..b2b3eb94e7
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/intro/getting-started/imports.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Imports
+sidebar_title: Imports
+sidebar_current: intro-getting-started-imports
+description: Imports enable Sentinel to access external data and functions.
+layout: intro
+---
+
+# Imports
+
+Imports enable Sentinel to access external data and functions.
+
+Sentinel ships with a set of [standard imports](/sentinel/imports).
+Standard imports are available by default to every Sentinel policy. Note that
+the application embedding Sentinel may whitelist or blacklist the available
+set of imports.
+
+To use an import, use the `import` statement. Then, access data and
+functions on the import using the import name followed by a period and
+the field you want to access. The example below checks whether the request
+path starts with a prefix. Assume that `request_path` is available.
+
+```sentinel
+import "strings"
+
+main = rule { strings.has_prefix(request_path, "/admin") }
+```
+
+## External Data
+
+The true power in imports is their ability to reference external data.
+Imports can be added to a Sentinel-enabled application through
+[plugins](/sentinel/extending). This enables any Sentinel-enabled
+application to make policy decision based on any source of data.
+
+This ability allows the policy system to enforce almost any necessary
+organizational policy, since the ability of a policy isn't restricted
+purely to the embedding application's data model.
+
+For example, policies in [Nomad](https://www.nomadproject.io) can
+access data in [Consul](https://www.consul.io) to determine attributes
+of a policy. In the example below, we use a hypothetical Consul import:
+
+```sentinel
+import "consul"
+
+main = rule {
+ job.tasks[0].resources.memory <= int(consul.get("policy/nomad/max-memory"))
+}
+```
+
+In this example, a Nomad job's memory usage is limited to the value of
+a Consul key/value item. By simply changing a Consul KV entry, policy
+can be changed. Imports can do anything, you can [write your own import plugins](/sentinel/extending)
+to extend Sentinel.
+
+Next, we'll learn how to
+[test policies](/sentinel/intro/getting-started/testing)
+to ensure our policy logic is correct.
diff --git a/content/sentinel/v0.14.x/content/sentinel/intro/getting-started/index.mdx b/content/sentinel/v0.14.x/content/sentinel/intro/getting-started/index.mdx
new file mode 100644
index 0000000000..0942cdb637
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/intro/getting-started/index.mdx
@@ -0,0 +1,17 @@
+---
+page_title: Install Sentinel CLI - Getting Started
+sidebar_title: Getting Started
+sidebar_current: intro-getting-started-overview
+description: The first step to using Sentinel is to get the CLI installed.
+layout: intro
+---
+
+# Getting Started With Sentinel
+
+This section covers getting started with Sentinel. Here, we will take you
+through the first steps that you will need to do to get started with installing
+the Sentinel CLI, writing your first policy, and getting familiar with rules,
+boolean logic, imports, and testing.
+
+You can select a topic on the menu to get started. The first step is to [install
+the Sentinel CLI](/sentinel/intro/getting-started/install).
diff --git a/content/sentinel/v0.14.x/content/sentinel/intro/getting-started/install.mdx b/content/sentinel/v0.14.x/content/sentinel/intro/getting-started/install.mdx
new file mode 100644
index 0000000000..4c3959d617
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/intro/getting-started/install.mdx
@@ -0,0 +1,57 @@
+---
+page_title: Install Sentinel CLI - Getting Started
+sidebar_title: Installing the CLI
+sidebar_current: intro-getting-started-install
+description: The first step to using Sentinel is to get the CLI installed.
+layout: intro
+---
+
+# Install Sentinel CLI
+
+Sentinel is a policy framework that is embedded in the enterprise versions of
+HashiCorp tools. The policies you write are deployed to these applications and
+enforced there.
+
+The Sentinel CLI is a command-line interface for local development and testing.
+For the getting started guide, we'll use the CLI to learn how to write policies
+for Sentinel-enabled applications. The Sentinel CLI is distributed as a [binary
+package](/sentinel/downloads) for all supported platforms and architectures.
+
+## Installation Instructions
+
+To install the Sentinel CLI, find the [appropriate package](/sentinel/downloads) for your
+system and download it. The CLI is packaged as a zip archive.
+
+After downloading Sentinel, unzip the package. The CLI runs as a single binary
+named `sentinel`. Any other files in the package can be safely removed and
+Sentinel will still function.
+
+The final step is to make sure that the `sentinel` binary is available on the `PATH`.
+See [this page](https://stackoverflow.com/questions/14637979/how-to-permanently-set-path-on-linux)
+for instructions on setting the PATH on Linux and Mac.
+[This page](https://stackoverflow.com/questions/1618280/where-can-i-set-path-to-make-exe-on-windows)
+contains instructions for setting the PATH on Windows.
+
+## Verifying the Installation
+
+After installing the Sentinel CLI, verify the installation worked by opening a
+new terminal session and checking that the `sentinel` binary is available. By
+executing `sentinel`, you should see help output similar to the following:
+
+```
+$ sentinel
+Usage: sentinel [--version] [--help] []
+
+Available commands are:
+ apply Execute a policy and output the result
+ fmt Format Sentinel policy to a canonical format
+ test Test policies
+ version Prints the Sentinel version
+```
+
+If you get an error that the binary could not be found, then your `PATH`
+environment variable was not setup properly. Please go back and ensure that your
+`PATH` variable contains the directory where Sentinel was installed.
+
+Otherwise, the CLI is installed and ready to go. Let's celebrate by [writing our
+first policy!](/sentinel/intro/getting-started/first-policy)
diff --git a/content/sentinel/v0.14.x/content/sentinel/intro/getting-started/logic.mdx b/content/sentinel/v0.14.x/content/sentinel/intro/getting-started/logic.mdx
new file mode 100644
index 0000000000..893d63c851
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/intro/getting-started/logic.mdx
@@ -0,0 +1,55 @@
+---
+page_title: Logic
+sidebar_title: Logic
+sidebar_current: intro-getting-started-logic
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Logic
+
+The core of a policy is a set of logical expressions to determine whether
+a behavior is allowed or not.
+Sentinel makes it easy to write readable logical expressions to express
+the intended policy.
+
+The logical expression syntax will be familiar to anyone who has programmed
+before. Sentinel also supports a couple more unique constructs to assist
+with common policy requirements. Detailed documentation on how to write
+logical expressions and the supported operators is available in
+[the boolean expression reference](/sentinel/language/boolexpr).
+
+Example logic:
+
+```sentinel
+a is b // Equality, you can also use ==
+a is not b // Inequality, you can also use !=
+
+a > b // Relational operators, comparison
+a < b
+a >= b
+a <= b
+
+a contains b // Inclusion check, substrings, etc.
+a not contains b // Negated version of above
+```
+
+The full list of available operators can be found in
+[the boolean expression reference](/sentinel/language/boolexpr).
+
+Logic can be combined with `and` or `or`. When combined with `and`, both
+sides must result in `true`. When combined with `or`, only one side needs
+to be true to result in `true`.
+
+With these building blocks, basic rules can be built up:
+
+```sentinel
+main = rule {
+ hour >= 8 and
+ hour < 17
+}
+```
+
+Next, we'll introduce
+[imports](/sentinel/intro/getting-started/imports)
+so that we can write rules that interact with external data.
diff --git a/content/sentinel/v0.14.x/content/sentinel/intro/getting-started/next-steps.mdx b/content/sentinel/v0.14.x/content/sentinel/intro/getting-started/next-steps.mdx
new file mode 100644
index 0000000000..8c01c3e6f3
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/intro/getting-started/next-steps.mdx
@@ -0,0 +1,21 @@
+---
+page_title: Next Steps
+sidebar_current: intro-getting-started-nextsteps
+description: That concludes the getting started guide for Sentinel.
+layout: intro
+---
+
+# Next Steps
+
+That concludes the getting started guide for Sentinel. Hopefully you're excited
+about the possibilities of Sentinel and ready to put this knowledge to use to
+improve the safety of your environments.
+
+We've covered the basics of the core features of Sentinel in this guide.
+We recommend the following as next steps:
+
+* [Documentation](/sentinel) - The documentation is an in-depth
+ reference guide of all the features of Sentinel.
+
+* [Language Reference](/sentinel/language) - The language reference
+ is a complete reference to the features of the Sentine policy language.
diff --git a/content/sentinel/v0.14.x/content/sentinel/intro/getting-started/rules.mdx b/content/sentinel/v0.14.x/content/sentinel/intro/getting-started/rules.mdx
new file mode 100644
index 0000000000..6a72af190e
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/intro/getting-started/rules.mdx
@@ -0,0 +1,73 @@
+---
+page_title: Rules
+sidebar_title: Rules
+sidebar_current: intro-getting-started-rules
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Rules
+
+A rule describes a set of conditions that result in either true or false.
+
+Rules in Sentinel are a first-class concept. They are used for making complex
+logic more understandable by breaking it down into a set of rules, for
+testing policies by asserting certain rules are passed, and more.
+
+## Writing Rules
+
+Let's look at the Sentinel policy we wrote in the previous section:
+
+```sentinel
+hour = 4
+main = rule { hour >= 0 and hour < 12 }
+```
+
+The logic in this policy is straightforward and easy to understand. However,
+it is common for policy logic to become much more complicated. Rules are the
+key abstraction for making complex logic understandable. We can turn the
+policy above into a set of rules:
+
+```sentinel
+hour = 4
+
+past_midnight = rule { hour >= 0 }
+before_noon = rule { hour < 12 }
+
+main = rule {
+ past_midnight and
+ before_noon
+}
+```
+
+This policy is easier to read. Our original policy was already quite
+simple, but it is easy to imagine a more complex policy becoming much
+more readable by extracting logical expressions into a set of rules.
+
+Rules don't just exist to make a policy more readable. They're also a
+testing and debugging point. For testing, you can assert that certain rules
+are certain values given a specific input to verify that your policy works
+as you expect. Testing and debugging are covered in later sections.
+
+## Conditional Rules
+
+In many cases, a rule is only valid when something else is also true.
+
+Consider the human language statement "before you eat, you must wash your hands."
+The rule "you must wash your hands" is only valid "before you eat". In any
+other scenario, the rule is meaningless.
+This is also true in a technical context. Imagine the rule "if the driver
+is Docker, you must not expose any ports." For this example, assume
+rules `is_docker` and `has_no_exposed_ports` exist. You can write this rule
+like this:
+
+```sentinel
+main = rule when is_docker { has_no_exposed_ports }
+```
+
+When `is_docker` is true, the rule is evaluated as normal. However,
+when `is_docker` is false, the rule always returns `true`, because the
+logic of the rule is meaningless.
+
+Let's learn how to create more complex rules with
+[logical expressions](/sentinel/intro/getting-started/logic).
diff --git a/content/sentinel/v0.14.x/content/sentinel/intro/getting-started/testing.mdx b/content/sentinel/v0.14.x/content/sentinel/intro/getting-started/testing.mdx
new file mode 100644
index 0000000000..ee245f1c0e
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/intro/getting-started/testing.mdx
@@ -0,0 +1,70 @@
+---
+page_title: Testing
+sidebar_current: intro-getting-started-testing
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Testing
+
+It is important to ensure the policies you write are correct. Sentinel
+includes a built-in test framework that can be run locally and in CI
+environments to test that policies behave as expected.
+
+A common pitfall with many simple ACL systems is that they provide no easy
+way to verify their correctness. You basically have to set the ACL and try
+the behaviors against a real system to verify it is working as expected.
+This requires a lot of setup and is unique to each system.
+
+[Sentinel's built-in test framework](/sentinel/writing/testing) has
+zero dependencies. It is a single binary that can mock the data that real
+systems are exposing to the policy. It is designed to be CI-friendly and
+enables continuous testing of your policies. This is necessary for [policy
+as code](/sentinel/concepts/policy-as-code).
+
+Detailed documentation on testing policies is available in [the Sentinel Testing reference](/sentinel/writing/testing).
+
+## Writing Tests
+
+For this example, save the following policy as `policy.sentinel`:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Next, let's write a passing test case. This will test that the policy tests
+when we expect it to pass. Save the following in `test/policy/good.json`:
+
+```json
+{
+ "global": {
+ "day": "monday",
+ "hour": 14
+ }
+}
+```
+
+And run `sentinel test`:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/good.json
+```
+
+The `sentinel test` command will automatically find all Sentinel policies
+and their associated test cases and run them all. The test framework will
+run all JSON files as individual test cases, allowing you to test a variety
+of scenarios for your policies.
+
+Try adding another test case to force the policy to fail. This test case can
+be saved at `test/policy/fail.json`. For test cases with intentional
+failures, you'll need to use the [test assertions described in the testing
+reference](/sentinel/writing/testing#a-failing-test).
+
+Now that you have a good grasp of what Sentinel is and how to use it, please feel
+free to look through the
+[next steps](/sentinel/intro/getting-started/next-steps)
+you can take to further your Sentinel knowledge.
diff --git a/content/sentinel/v0.14.x/content/sentinel/intro/index.mdx b/content/sentinel/v0.14.x/content/sentinel/intro/index.mdx
new file mode 100644
index 0000000000..785a442fb8
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/intro/index.mdx
@@ -0,0 +1,32 @@
+---
+layout: intro
+page_title: Documentation
+sidebar_current: docs-home
+description: >-
+ Sentinel is a language and framework for policy built to be embedded in
+ existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+[introduction](/sentinel/intro) to understand what Sentinel is, how it
+compares to other software, and more.
+
+
diff --git a/content/sentinel/v0.14.x/content/sentinel/intro/what/index.mdx b/content/sentinel/v0.14.x/content/sentinel/intro/what/index.mdx
new file mode 100644
index 0000000000..fba1f3a0e8
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/intro/what/index.mdx
@@ -0,0 +1,65 @@
+---
+page_title: Introduction
+sidebar_title: What is Sentinel?
+sidebar_current: intro-what
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: intro
+---
+
+# Introduction to Sentinel
+
+Welcome to the intro guide to Sentinel! This guide is the best
+place to start with Sentinel.
+
+If you are already familiar with the basics of Sentinel, the
+[documentation](/sentinel) provides a better reference
+guide for all available features as well as internals.
+
+## What is Sentinel?
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+Sentinel provides a language for writing policy and a workflow for developing
+and testing policies independent of the software they'll be written to. We
+also provide a framework for developers to build plugins that allow a
+Sentinel-enabled system to access external information to make policy
+decisions.
+
+Most systems today have some degree of access control. You are able to define
+identities and what they have access to. These ACL systems solve an immediate
+and necessary problem of locking down a system in very broad strokes. Sentinel
+is a reusable system for more advanced software policy decisions. Sentinel
+enables:
+
+* **Fine-Grained Policy**: Most ACL systems only enable coarse-grained
+ behaviors: "read", "write", etc. Sentinel enables fine-grained behavior such
+ as disallowing a certain API call when specific parameters are present.
+
+* **Logic-Based Policy**: You can write policy using full
+ conditional logic. For example, you may only allow a certain application behavior
+ on Monday to Thursday unless there is a manager override.
+
+* **Accessing External Information**: Sentinel can source external information
+ to be used in policy decisions. For example, a policy that restricts the size
+ of a payload may read data from [Consul](https://www.consul.io) to determine
+ the payload size limit.
+
+* **Enforcement levels**: Sentinel allows policies to be defined along
+ with an "enforcement level" that dictates the pass/fail behavior of a policy.
+ Advisory policies warn if they fail, soft mandatory policies can have their
+ failures overridden, and hard mandatory policies must pass under all
+ circumstances. Having this as a built-in concept enables you to model
+ policy more accurately and completely for your organization.
+
+## Next Steps
+
+See the page on [Why Sentinel?](/sentinel/intro/why) to learn more
+about the origins of the product. Then, continue onwards with
+the [getting started guide](/sentinel/intro/getting-started/install) to write
+your first policies and see how Sentinel works in practice.
diff --git a/content/sentinel/v0.14.x/content/sentinel/intro/why/index.mdx b/content/sentinel/v0.14.x/content/sentinel/intro/why/index.mdx
new file mode 100644
index 0000000000..37e01c6fc3
--- /dev/null
+++ b/content/sentinel/v0.14.x/content/sentinel/intro/why/index.mdx
@@ -0,0 +1,50 @@
+---
+page_title: Why Sentinel?
+sidebar_title: Why Sentinel?
+sidebar_current: intro-why
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: intro
+---
+
+# Why Sentinel?
+
+The growth of infrastructure and applications has been enabled in part
+by an increasing trend towards automation everywhere.
+Configuration as code (such as Chef or Puppet with [Packer](https://www.packer.io))
+has enabled automated machine configuration. Infrastructure
+as code (such as [Terraform](https://www.terraform.io)) has enabled
+automatic infrastructure creation. And schedulers (such as
+[Nomad](https://www.nomadproject.io) and Kubernetes) have enabled
+automatic application deployment.
+Sentinel enables guardrails to be put in place
+on automation while allowing the codification and automatic enforcement
+of business requirements in critical areas of your infrastructure.
+
+Meanwhile, businesses have business requirements and sometimes legal
+requirements which must be expressed in policies. Traditionally, these
+policies are enforced by humans. But in a highly automated world, the
+automation is only as fast as its slowest component. In many cases, this
+is the human verification step.
+
+As an example: before infrastructure as code and autoscaling, if an order
+came through for 5,000 new machines, a human would likely respond to the
+ticket verifying that the user really intended to order 5,000 new machines.
+Today, automation can almost always freely order 5,000 new compute instances
+without any hesitation, which can result in unintended expense or system
+instability.
+
+Sentinel introduces [policy as code](/sentinel/concepts/policy-as-code)
+and a powerful framework built-in to HashiCorp tooling to allow automation
+guardrails, business requirements, legal compliance, and more to be
+actively enforced by the running systems in realtime.
+
+With Sentinel, you can require an override to create certain numbers of
+infrastructure resources. You can disallow unsafe deployment configurations
+with Nomad. You can enforce certain key/value formats in Consul. You
+can restrict secret access by time in Vault. And more.
+
+Sentinel is available today in HashiCorp enterprise products. You can
+try Sentinel immediately with the [getting started guide](/sentinel/intro/getting-started/install) or learn more about the integrations in the
+[documentation](/sentinel).
diff --git a/content/sentinel/v0.14.x/data/docs-nav-data.json b/content/sentinel/v0.14.x/data/docs-nav-data.json
new file mode 100644
index 0000000000..c963d65ecb
--- /dev/null
+++ b/content/sentinel/v0.14.x/data/docs-nav-data.json
@@ -0,0 +1,336 @@
+[
+ {
+ "title": "Release Notes",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "changelog"
+ }
+ ]
+ },
+ {
+ "title": "Basic Concepts",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "concepts"
+ },
+ {
+ "title": "Policy as Code",
+ "path": "concepts/policy-as-code"
+ },
+ {
+ "title": "Policy Language",
+ "path": "concepts/language"
+ },
+ {
+ "title": "Imports",
+ "path": "concepts/imports"
+ },
+ {
+ "title": "Enforcement Levels",
+ "path": "concepts/enforcement-levels"
+ }
+ ]
+ },
+ {
+ "title": "Commands (CLI)",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "commands"
+ },
+ {
+ "title": "Configuration File Syntax",
+ "path": "commands/config"
+ },
+ {
+ "title": "apply",
+ "path": "commands/apply"
+ },
+ {
+ "title": "fmt",
+ "path": "commands/fmt"
+ },
+ {
+ "title": "test",
+ "path": "commands/test"
+ }
+ ]
+ },
+ {
+ "title": "Writing Policy",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "writing"
+ },
+ {
+ "title": "Basics",
+ "path": "writing/basic"
+ },
+ {
+ "title": "Rules",
+ "path": "writing/rules"
+ },
+ {
+ "title": "Traces",
+ "path": "writing/tracing"
+ },
+ {
+ "title": "Testing",
+ "path": "writing/testing"
+ },
+ {
+ "title": "Imports",
+ "path": "writing/imports"
+ },
+ {
+ "title": "Debugging",
+ "path": "writing/debugging"
+ }
+ ]
+ },
+ {
+ "title": "Import Plugins",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "extending"
+ },
+ {
+ "title": "Install",
+ "path": "extending/install"
+ },
+ {
+ "title": "Develop",
+ "path": "extending/dev"
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Language",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "language"
+ },
+ {
+ "title": "Variables",
+ "path": "language/variables"
+ },
+ {
+ "title": "Values",
+ "path": "language/values"
+ },
+ {
+ "title": "Lists",
+ "path": "language/lists"
+ },
+ {
+ "title": "Maps",
+ "path": "language/maps"
+ },
+ {
+ "title": "Rules",
+ "path": "language/rules"
+ },
+ {
+ "title": "Imports",
+ "path": "language/imports"
+ },
+ {
+ "title": "Parameters",
+ "path": "language/parameters"
+ },
+ {
+ "title": "Boolean Expressions",
+ "path": "language/boolexpr"
+ },
+ {
+ "title": "Arithmetic",
+ "path": "language/arithmetic"
+ },
+ {
+ "title": "Slices",
+ "path": "language/slices"
+ },
+ {
+ "title": "Conditionals",
+ "path": "language/conditionals"
+ },
+ {
+ "title": "Loops",
+ "path": "language/loops"
+ },
+ {
+ "title": "Collection Operations",
+ "path": "language/collection-operations"
+ },
+ {
+ "title": "Functions",
+ "path": "language/functions"
+ },
+ {
+ "title": "Scope",
+ "path": "language/scope"
+ },
+ {
+ "title": "Undefined",
+ "path": "language/undefined"
+ },
+ {
+ "title": "Logging and Errors",
+ "path": "language/logging-errors"
+ },
+ {
+ "title": "Specification",
+ "path": "language/spec"
+ }
+ ]
+ },
+ {
+ "title": "Builtin Functions",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "functions"
+ },
+ {
+ "title": "append",
+ "path": "functions/append"
+ },
+ {
+ "title": "delete",
+ "path": "functions/delete"
+ },
+ {
+ "title": "error",
+ "path": "functions/error"
+ },
+ {
+ "title": "keys",
+ "path": "functions/keys"
+ },
+ {
+ "title": "length",
+ "path": "functions/length"
+ },
+ {
+ "title": "print",
+ "path": "functions/print"
+ },
+ {
+ "title": "range",
+ "path": "functions/range"
+ },
+ {
+ "title": "values",
+ "path": "functions/values"
+ }
+ ]
+ },
+ {
+ "title": "Standard Imports",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "imports"
+ },
+ {
+ "title": "decimal",
+ "path": "imports/decimal"
+ },
+ {
+ "title": "http",
+ "path": "imports/http"
+ },
+ {
+ "title": "json",
+ "path": "imports/json"
+ },
+ {
+ "title": "runtime",
+ "path": "imports/runtime"
+ },
+ {
+ "title": "sockaddr",
+ "path": "imports/sockaddr"
+ },
+ {
+ "title": "strings",
+ "path": "imports/strings"
+ },
+ {
+ "title": "time",
+ "path": "imports/time"
+ },
+ {
+ "title": "types",
+ "path": "imports/types"
+ },
+ {
+ "title": "units",
+ "path": "imports/units"
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Consul",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "consul"
+ }
+ ]
+ },
+ {
+ "title": "Nomad",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "nomad"
+ }
+ ]
+ },
+ {
+ "title": "Terraform",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "terraform"
+ }
+ ]
+ },
+ {
+ "title": "Vault",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "vault"
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Internals",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "internals"
+ },
+ {
+ "title": "Plugins",
+ "path": "internals/plugins"
+ }
+ ]
+ }
+]
diff --git a/content/sentinel/v0.14.x/data/intro-nav-data.json b/content/sentinel/v0.14.x/data/intro-nav-data.json
new file mode 100644
index 0000000000..c95181af70
--- /dev/null
+++ b/content/sentinel/v0.14.x/data/intro-nav-data.json
@@ -0,0 +1,47 @@
+[
+ {
+ "title": "What is Sentinel?",
+ "path": "what"
+ },
+ {
+ "title": "Why Sentinel?",
+ "path": "why"
+ },
+ {
+ "title": "Getting Started",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "getting-started"
+ },
+ {
+ "title": "Installing the CLI",
+ "path": "getting-started/install"
+ },
+ {
+ "title": "Your First Policy",
+ "path": "getting-started/first-policy"
+ },
+ {
+ "title": "Rules",
+ "path": "getting-started/rules"
+ },
+ {
+ "title": "Logic",
+ "path": "getting-started/logic"
+ },
+ {
+ "title": "Imports",
+ "path": "getting-started/imports"
+ },
+ {
+ "title": "Testing",
+ "path": "getting-started/testing"
+ },
+ {
+ "title": "Next Steps",
+ "path": "getting-started/next-steps"
+ }
+ ]
+ }
+]
diff --git a/content/sentinel/v0.14.x/data/version.js b/content/sentinel/v0.14.x/data/version.js
new file mode 100644
index 0000000000..50f0ac331f
--- /dev/null
+++ b/content/sentinel/v0.14.x/data/version.js
@@ -0,0 +1,2 @@
+// This file is auto-generated - DO NOT EDIT
+export default "0.14.4"
\ No newline at end of file
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/changelog/index.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/changelog/index.mdx
new file mode 100644
index 0000000000..c2a2f1cb3f
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/changelog/index.mdx
@@ -0,0 +1,497 @@
+---
+page_title: Sentinel Runtime Release Notes
+sidebar_title: Release Notes
+sidebar_current: docs-changelog
+description: >-
+ This are the public release notes for the Sentinel runtime. See this document
+ for the latest updates.
+layout: docs
+---
+
+# Sentinel Runtime Release Notes
+
+These are the release notes for the Sentinel runtime.
+
+Each version of the runtime is released with a corresponding version of
+[Sentinel CLI](/sentinel/commands). To download the CLI, see the [downloads
+page](/sentinel/downloads).
+
+Sentinel integrations and embedded runtimes may not always have the latest
+version installed, depending on the product's individual release cycle. For more
+information, contact the support team for your specific integration.
+
+
+
+## 0.15.6 (July 7, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.15.5 (May 20, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel integrations. There are no user-facing changes.
+
+## 0.15.4 (May 13, 2020)
+
+BUG FIXES:
+
+- `imports`: Fixed an issue with the standard imports that was affecting the handling of null data within lists.
+
+## 0.15.3 (April 16, 2020)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where `IndexExpr` were not rewritten, as well as ensuring `SelectorExpr` uses any nested rewrites.
+- `lang/printer`: Fixed issue printing deeply nested structures and/or loops.
+
+IMPROVEMENTS:
+
+- `imports/decimal`: Added alias methods to improve consistency of method names within the `decimal` import. The alias methods are `is_nan` for `isnan`, as well as both `is_inf` and `is_infinite` for `isinfinite`.
+- `command/apply`: `sentinel apply` will now output the policy description when a trace is forced via `-trace`.
+- `sentinel`: Improved `String` output formatting of Policy docstring for `EvalPolicyResult`.
+
+## 0.15.2 (April 2, 2020)
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue with the `printer` that was causing a panic
+ when a `#` line comment was used with no text following it.
+- `imports/types`: Fixed an issue with the `types` import where calls to
+ `type_of` for undefined types were incorrectly returning as map types. These
+ will now be correctly be identified as undefined as expected.
+
+## 0.15.1 (March 6, 2020)
+
+BUG FIXES:
+
+- `sentinel`: Fixed how modules are managed internally in the runtime to correct
+ race conditions in concurrent scenarios and to allow for per-evaluation module
+ restrictions. This functionality is only of interest to embedded applications
+ with long-lived runtimes and is currently not implemented in any HashiCorp
+ integration.
+
+## 0.15.0 (March 5, 2020)
+
+NOTES:
+
+- **Windows Digital Signature**. Our windows releases for this version and up will be signed
+ and verified according to Microsoft's requirements.
+
+FEATURES:
+
+- `cmd/config`: Modules can now be loaded off of local disk using the Sentinel
+ CLI. For information on how to do so, see the Sentinel documentation.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: Changed modules so that they now have singleton scope when
+ loaded. Importing a module under two different aliases, or within a nested
+ module, will now share scopes - modification of state in one will alter the
+ other.
+- `lang/object`: Introduced an interface to allow the duplication of various
+ types, like collections.
+- `runtime/eval`: Changed how collection data is returned from modules. This
+ data is now duplicated to prevent accidental or deliberate circumvention of
+ the prohibition on assignment to import data, and brings behavior to parity
+ with binary imports.
+
+## 0.14.4 (February 6, 2020)
+
+IMPROVEMENTS:
+
+- `imports/time`: Changed the `add` timespace function in the `time` import to
+ allow it to take float types as durations. These values will be truncated to
+ the appropriate integer-value duration.
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where out-of-place right-hand braces were being
+ parsed as empty statements, instead of raising syntax errors.
+
+## 0.14.3 (January 22, 2020)
+
+IMPROVEMENTS:
+
+- `cmd/test`: Fail when an assertion is not found in a trace.
+- `cmd/test`: Added a message to notify when no rules were evaluated in a test.
+
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue where import aliases (the `as baz` in `import
+ "foo/bar" as baz`) were being removed when formatting with `sentinel fmt`.
+
+- `imports/http`: Fixed an issue with the http import where the client library's
+ debug logs were being printed to standard error.
+
+## 0.14.2 (January 15, 2020)
+
+NOTES:
+
+With this release, we are discontinuing support for MacOS 32-bit. 64-bit builds
+are now the only builds available for MacOS.
+
+IMPROVEMENTS:
+
+- `lang/printer`: A newline is now added to files formatted via `sentinel fmt`.
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue in printing where multi-line expressions would
+ indent infinitely. They will now only indent once at the most. This is mostly
+ seen when using `sentinel fmt`.
+- `runtime/encoding`: Fixed an issue where if a plugin returned a deeply
+ embedded undefined value, the value was instead decoded into a map.
+
+## 0.14.1 (January 6, 2020)
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where reserved words could not be used as
+ selectors when the selector expression was the last lexeme on a line.
+
+## 0.14.0 (December 18, 2019)
+
+LANGUAGE CHANGES:
+
+- **Filter Expression** `filter`. A new quantifier expression, `filter` has been added.
+ `filter` returns a subset of the provided collection based on a boolean condition that
+ is asserted for each element.
+
+NOTES:
+
+- **Apple Notarization**. Our darwin releases for this version and up will be signed
+ and notarized according to Apple's requirements.
+
+## 0.13.1 (November 25, 2019)
+
+BUG FIXES:
+
+- `runtime/eval`: Parameters are no longer allowed in mock files. Adding one to
+ a mock will result in a runtime error.
+- `runtime/eval`: Fixed an issue where import calls from within mocks were
+ failing under certain circumstances.
+- `imports/decimal`: `int` should now correctly provide the truncated integer
+ representation of a decimal number, not the rounded one.
+
+## 0.13.0 (November 15, 2019)
+
+LANGUAGE CHANGES:
+
+- **Policy Parameters**. The new [policy
+ parameters](https://docs.hashicorp.com/sentinel/language/parameters) feature
+ allows authors to use a `param` declaration at the top of a policy to supply
+ values that are expected to come from outside of a policy. See the linked
+ documentation for more details.
+
+FEATURES:
+
+- `imports/http`: The [`http`
+ import](https://docs.hashicorp.com/sentinel/imports/http) is a new addition
+ to the standard library enabling the use of HTTP-accessible data from outside
+ the runtime in policy rules.
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where loading other imports before a mocked
+ import would cause those imports to no longer be visible from within the
+ policy.
+
+## 0.12.0 (October 7, 2019)
+
+LANGUAGE CHANGES:
+
+- **New `case` statement**. This statement is a selection control mechanism to
+ conditionally execute a branch based on expression equality, allowing
+ simplification of complex conditional chains that may otherwise need to be
+ written with `else if`. See the [Case
+ Statements](https://docs.hashicorp.com/sentinel/language/spec#case-statements)
+ section of the Sentinel language specification for more details.
+
+IMPROVEMENTS:
+
+- `lang/semantic`: Added a semantic check to ensure usage of append is not using
+ a return value.
+
+BUG FIXES:
+
+- `runtime/eval`: Corrected an issue where calling a method on an import object
+ value that was the result of a method call on another import object value
+ would have erroneously tried to call an import of the name of the "parent"
+ import object value. Example: in `a = subject.new(); b = a.call(); c =
+ b.call()`, `b.call()` would attempt to call a method named `call` on the root
+ namespace for an import named `a`. This has now been corrected so that
+ `b.call()` will now correctly call the `call` method for the respective object
+ namespace residing in the import named `subject`.
+- `imports`: Some standard imports may have been returning null for some unknown
+ keys in objects when they should have been returning undefined. This was due
+ to an SDK issue which was corrected in SDK version 0.3.2, which has now been
+ corrected.
+- `cmd/test`: `sentinel test` will now correctly fail a policy if it encounters
+ an error.
+- `cmd/test`: `sentinel test` will now correctly display errors and other output
+ that were missing due to a formatting issue.
+- `runtime/eval`: The `append` builtin now correctly returns undefined for all
+ calls, as called for by the Specification. Note that in most cases, the
+ semantic check outlined above will trigger an error if the return value is
+ used.
+
+## 0.11.0 (September 5, 2019)
+
+LANGUAGE CHANGES:
+
+- **New builtin function:** `range()`. This function existed in the spec in
+ earlier versions but was removed as it lacked an implementation. This has now
+ been implemented and re-added to the spec. See the
+ [Range](https://docs.hashicorp.com/sentinel/language/spec#range) section of
+ the Sentinel Specification for more details.
+- Lists are now comparable. Lists are equal if their corresponding elements are
+ comparable and equal.
+- Method calls on values returned by imports are now supported. See the
+ [Imports](https://docs.hashicorp.com/sentinel/language/spec#imports) section
+ of the Sentinel Specification for more details.
+
+FEATURES:
+
+- `imports/decimal`: This is a new import designed to do exact precision
+ mathematical calculations.
+- `runtime/eval`: Compound call expressions that refer to imports (example:
+ foo.bar().baz() when `foo` is a loaded import) will now function as expected.
+ Previously, this was only supported up to the first call (example:
+ `foo.bar()`).
+- `imports/time`: Timespaces returned by calls such as `time.now` are now
+ callable. Example: `t = time.now; t.after(some_previous_time)` will now
+ function.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: The implementation of comparison of non-comparable types has
+ now changed. Rather than triggering a runtime error, non-comparable types will
+ now return false when attempting to compare.
+
+## 0.10.4 (August 15, 2019)
+
+BUG FIXES:
+
+- `runtime/encoding`: Fixed an issue with conversion of null values that could
+ lead to crashes in imports.
+
+## 0.10.3 (July 15, 2019)
+
+BUG FIXES:
+
+- `command/config`: Parsing a configuration file where malformed data is
+ encountered after apparently properly-formed data will now report an error. An
+ example would be a situation where misplaced braces would cause a JSON object
+ to only parse part of the configuration file.
+
+## 0.10.2 (June 25, 2019)
+
+BUG FIXES:
+
+- `lang/parser`: Corrected an issue where parsing certain compound binary
+ expressions where the negation predicate (`not`) was in use would cause the
+ negation to have no effect. Example: `foo else "bar" not in "baz"` would have
+ been parsed and evaluated as `foo else "bar" in "baz"`, effectively producing
+ the opposite result.
+
+## 0.10.1 (May 9, 2019)
+
+BUG FIXES:
+
+- `command/test`: `sentinel test` now displays policies without
+tests as an unknown result with [no test files], instead of the somewhat
+erroneous behavior of displaying it as a PASS. A full test run with a mixture
+of passing tests and no tests still results in an overall successful result.
+
+## 0.10.0 (April 18, 2019)
+
+LANGUAGE CHANGES:
+
+- Mixed-number arithmetic operations are now allowed. Addition (`+`),
+ subtraction (`-`), multiplication (`*`), division (`/`), and remainder
+ operations (`%`) are no longer restricted to number values of the same
+ type, and can mix integer and floating point. The result of these operations
+ is always a floating-point number.
+- Remainder (modulo, `%`) operations are now allowed on floating-point numbers.
+
+
+BUG FIXES:
+
+- `lang/parser`: Fixed a bug that affected the use of `not` with `contains`,
+ `matches`, or `in` in a compound expression.
+- `runtime/eval`: Fixed error messages on evaluation errors with `contains` and
+ `in` to indicate that strings are an allowed type along with lists and maps.
+
+
+## 0.9.2 (March 15, 2019)
+
+This is a dependency update related to the changes mentioned in 0.9.1. No other
+changes have been made.
+
+## 0.9.1 (March 14, 2019)
+
+This is a patch release that is required to integrate with the latest versions
+of the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). No other
+changes have been made.
+
+## 0.9.0 (January 28, 2019)
+
+FEATURES:
+
+- `imports/strings`: Added a `join` function to the `strings` import. This can
+ be used to join a list into a string with a specific separator.
+ Multi-dimensional lists and all Sentinel primitives are supported.
+
+## 0.8.1 (January 17, 2019)
+
+BUG FIXES:
+
+- `lang/eval`: Fixed a bug that prevents the effective use of `return`
+ statements in `for` loops.
+
+## 0.8.0 (January 14, 2019)
+
+FEATURES:
+
+- Mocks can now be represented by Sentinel code. This allows for the mocking of
+ functions and other complex data structures that cannot be represented in
+ JSON. For more information on using this feature via mocks, click
+ [here](https://docs.hashicorp.com/sentinel/commands/config#mocking-with-sentinel-code).
+
+
+IMPROVEMENTS:
+
+- Import validation has now been moved to the semantic checking phase. This
+ should result in better reporting of validation errors. In addition, import
+ validation will now enforce the use of an `as` identifier when an import path
+ is not a valid identifier on its own (example: `import "foo/bar"`).
+
+## 0.7.0 (December 12, 2018)
+
+BUG FIXES:
+
+- There have been changes to the runtime in how scope is handled over multiple
+ policy executions. Scope is now correctly unique per single policy execution,
+ and values set or builtins that are overridden in one policy will no longer
+ affect those values within another.
+
+## 0.6.0 (November 30, 2018)
+
+FEATURES:
+
+- `imports/runtime`: This new import allows for one to check various aspects of
+ the Sentinel runtime as it may be embedded in the simulator or a specific
+ implementation. For now, it allows the version to be checked.
+
+## 0.5.1 (November 28, 2018)
+
+IMPROVEMENTS:
+
+- `imports/time`: Added the `zone` and `zone_string` attributes to assist with
+ validation of a timespace's zone.
+- `command/fmt`: Added a new -check flag. This option does not commit changes,
+ but instead checks to see what files need formatting and outputs them on
+ stdout.
+
+BUG FIXES:
+
+- `command/test`: Ensure that passing test results are correctly output one per
+ line. Tests are also now run in a deterministic fashion based on
+ lexicographical (alphabetical) order.
+- `imports/time`: `month_name` and `weekday_name` will now show up correctly in
+ a returned timespace result.
+
+
+## 0.5.0 (November 5, 2018)
+
+IMPROVEMENTS:
+
+- `spec`: Selectors can now contain any reserved word (example: `rule`) or
+ keyword operator (example: `any`, `all`, `is`, `not`). This only works for the
+ _selector_ part of the expression (after the first period) - the first primary
+ expression (before the first period) still needs to be an identifier that does
+ not conflict with reserved words.
+
+BUG FIXES:
+
+- The simulator should now display import function call names correctly in
+ import errors.
+
+## 0.4.0 (October 1, 2018)
+
+FEATURES:
+
+- `builtin`: Added the `bool` built-in type conversion function. Booleans will
+ also now accepted as conversion into other values as well, with the full list
+ of behaviors available in the spec.
+
+## 0.3.2 (September 27, 2018)
+
+FEATURES:
+
+- `command/apply`: `sentinel apply` now prints out messages output by the
+ `print()` function when a trace is output on policy failure, or when a trace
+ is forced with `-trace`.
+- `imports/time`: Added the `month_name` and `weekday_name` keys to the
+ timespace, which return full-English names for the month and day of the week.
+
+
+BUG FIXES:
+
+- `command/fmt`: `sentinel fmt -` Will no longer print out the filter status
+ message on the output stream when `-write=false` Is not explicitly stated.
+ This brings the behavior of the command in line with the help text.
+- `runtime`: Index operations on the right-hand-side that have negative indexes
+ that go out of range (example: `length(list) * -1 - 1`) now correctly return
+ `undefined`. left-hand-side index assignments with a out-of-range negative
+ index still return runtime errors.
+
+## 0.3.1 (August 3, 2018)
+
+BUG FIXES:
+
+- `runtime`: Basic index assignment has been implemented as per the spec.
+- `runtime`: Index expressions for lists with negative indexes will no longer panic if the
+ list index is less than `length(list) * -1`.
+
+## 0.3.0 (July 20, 2018)
+
+FEATURES:
+
+- **New standard import: `types`.** This can be used to dynamicaly detect the
+ type of some value.
+
+## 0.2.0 (April 11, 2018)
+
+FEATURES:
+
+- **New standard import: `json`.** Marshal and unmarshal JSON documents and
+ access their contents as native Sentinel values.
+- **`break` and `continue`.** These are now both specified and implemented.
+ `break` allows loop exiting and `continue` allows immediate execution of the
+ next iteration.
+
+IMPROVEMENTS:
+
+- runtime: `print()` map values are now ordered alphabetically by keys.
+
+BUG FIXES:
+
+- command/test: If no `test` block exists, test behaves like it is asserting
+ `main: true`.
+- runtime: default maximum stack depth to 500
+- runtime: `print()` map values now appear like more typical maps.
+- runtime: division by zero is an error, not a crash
+- runtime: plugins that send map values with `null` values now decode properly
+ into native Sentinel values.
+
+## 0.1.0 (September 19, 2017)
+
+Initial release.
+
+
+
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/commands/apply.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/commands/apply.mdx
new file mode 100644
index 0000000000..b715c5c77f
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/commands/apply.mdx
@@ -0,0 +1,47 @@
+---
+page_title: 'Command: apply'
+sidebar_title: 'apply'
+sidebar_current: docs-commands-apply
+description: >-
+ The `sentinel apply` command is used to execute a policy locally for
+ development purposes.
+layout: docs
+---
+
+# Command: `apply`
+
+The `sentinel apply` command is used to execute a policy locally for development
+purposes.
+
+## Usage
+
+Usage: `sentinel apply [options] POLICY`
+
+This command executes the policy file at the path specified by POLICY.
+
+Use the exit code of this command to determine the exact status of the policy
+evaluation. `0` is pass, `1` is fail, `2` is undefined (fail, but because the
+result was undefined), and `3` is a runtime error. Errors unrelated to the
+policy status itself are returned with an exit status of `9`.
+
+To control the behavior of the `apply` command, create a [configuration
+file](/sentinel/commands/config). With this, you can define available
+[import plugins](/sentinel/concepts/imports), mock data, and global values.
+This can help you simulate a policy embedded within an application.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-config=path` - Path to a configuration file specifying available imports,
+ mock data, globals, etc. The default is `sentinel.json`.
+
+- `-global key=value` - Set global values. This is the same as setting `global`
+ in the configuration file, and will override any of these respective values
+ set in the configuration. The value is either a string, or a JSON number,
+ array, or object. To force strings, use quotes.
+
+- `-param key=value` - Set parameters, the same as setting `param` in the
+ configuration file. Values are handled in the same way they are with the
+ `-global` flag.
+
+- `-trace` - Always show the execution trace. This shows intermediate
+ boolean expression values. This always shows for failed policies.
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/commands/config.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/commands/config.mdx
new file mode 100644
index 0000000000..9e42c42fa9
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/commands/config.mdx
@@ -0,0 +1,289 @@
+---
+page_title: Sentinel CLI Configuration File Syntax
+sidebar_title: Configuration File Syntax
+sidebar_current: docs-commands-config
+description: >-
+ The Sentinel CLI's configuration file can be used to control the
+ behavior of the simulator during apply and test operations.
+layout: docs
+---
+
+# Sentinel CLI Configuration File Syntax
+
+The Sentinel CLI's configuration file can be used to control the behavior
+of the simulator during [`apply`](/sentinel/commands/apply) and
+[`test`](/sentinel/commands/test) operations.
+
+## Usage
+
+The configuration file is used in different ways depending on what operation you
+are trying to execute.
+
+### Apply
+
+When using `sentinel apply`, supply the configuration file by using the
+`-config=FILE` flag, where `FILE` is the path to the configuration file. The
+default is `sentinel.json`.
+
+See the [apply command](/sentinel/commands/apply) reference for more
+details.
+
+### Test
+
+When using sentinel test, each file matching `test//*.json` is a
+configuration file representing a single test case, where `` is the name
+of the policy being tested. Configure assertions for each [test
+case](#test-cases) using the test section.
+
+See the [test command](/sentinel/commands/test) reference for more details.
+
+## Configuration File Reference
+
+The format of the configuration file is JSON. The available keys are:
+
+- `mock` - A map of mock import data.
+- `modules` - A map of available [modules](/sentinel/extending/modules).
+- `imports` - A map of available [plugins](/sentinel/extending/plugins).
+- `global` - Data that is inserted into the global scope.
+- `param` - Values for parameters defined in the policy.
+- `test` - Test cases for the [test command](/sentinel/commands/test).
+
+### Mock Imports
+
+Mock imports allow running a policy with an
+[import](/sentinel/concepts/imports) that you may not have access to or is
+too difficult to run. For example, it can be easier to mock data for [Terraform
+Enterprise](https://www.terraform.io/docs/enterprise/sentinel/index.html)
+locally instead of having to run a workspace through a plan/policy check cycle.
+
+Mock imports are specified in the `mock` key, as a map to mock data, keyed by
+the import name that you want to mock. For example, if you wanted to mock the
+`time` import, you could create an entry with the `time` key pointing to the
+data you want to mock.
+
+The data can take one of two forms:
+
+#### Mocking static data
+
+Static data can be mocked directly by supplying a JSON object as the mock data.
+
+Example:
+
+```json
+{
+ "mock": {
+ "time": {
+ "now": {
+ "hour": 9,
+ "minute": 42
+ }
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```sentinel
+import "time"
+
+main = time.now.hour == 9
+```
+
+#### Mocking with Sentinel code
+
+There are some Sentinel types that static JSON data cannot mock, such as
+functions, and maps that don't have string keys. To mock this data, you can use
+a Sentinel file itself. In this case, the parameter to your import key is the
+file with the Sentinel code in it, relative to the current working directory in
+the event of `apply`, or relative to the configuration file location in the
+event of `test`.
+
+Note that `main` is not required in a mock data file.
+
+Example:
+
+```json
+{
+ "mock": {
+ "foo": "mock-foo.sentinel"
+ }
+}
+```
+
+`mock-foo.sentinel` would contain:
+
+```sentinel
+bar = func() {
+ return "baz"
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```sentinel
+import "foo"
+
+main = foo.bar() == "baz"
+```
+
+### Modules
+
+The `modules` section allows you to map a Sentinel import to a
+[module](/sentinel/extending/modules). The Sentinel CLI will parse the module
+source and make it available for evaluation with the policy.
+
+The value of the `modules` section is a map with the key of an element aligning
+to the name of the import, with the value set to the configuration object for
+the module. Currently, the only value within the configuration object is `path`,
+which points to the local path of the module.
+
+```json
+{
+ "modules": {
+ "foo": {
+ "path": "modules/foo.sentinel"
+ },
+ "bar": {
+ "path": "modules/bar.sentinel"
+ }
+ }
+}
+```
+
+Note when using [`sentinel test`](/sentinel/commands/test), module paths must be
+specified relative to the location of the configuration file.
+
+```json
+{
+ "modules": {
+ "foo": {
+ "path": "../../modules/foo.sentinel"
+ },
+ "bar": {
+ "path": "../../modules/bar.sentinel"
+ }
+ }
+}
+```
+
+See [Writing and Using a
+Module](/sentinel/extending/modules#writing-and-using-a-module) for more details
+on using a module with this configuration.
+
+### Plugins
+
+Plugins allow you to run a real [import plugin](/sentinel/extending/plugins)
+with a policy. The Sentinel CLI will launch the plugin, connect to it, configure
+it, and execute it as needed by the policy.
+
+Import plugins are specified by the `import` key. The value of this is a map
+where the key is the name of the import and the value is another map with
+configuration about the plugin. The configuration map has the following keys:
+
+- `path` (string, required) - Path to the import plugin executable.
+- `args` (list of string, optional) - A list of arguments to pass to the
+ executable when starting it.
+- `env` (map of string to string, optional) - A set of environmental variables
+ to set when launching the plugin.
+- `config` (map, optional) - Configuration for the plugin itself. This is
+ specific to each individual plugin. Please reference the documentation for
+ the plugin for more information.
+
+Example:
+
+```json
+{
+ "imports": {
+ "time": {
+ "path": "/path/to/sentinel-import-time",
+ "config": { "fixed_time": 1504155600 }
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass assuming the
+plugin configuration is valid:
+
+```sentinel
+import "time"
+
+main = time.now.day == 31
+```
+
+### Global
+
+Global data is injected directly into the global scope of the policy.
+This can be used to simulate global data that may be injected by a
+Sentinel-enabled application.
+
+Global data is specified by the `global` key. The value is a map of
+variables to inject directly into the running policy.
+
+Example:
+
+```json
+{
+ "global": {
+ "time": {
+ "now": {
+ "day": 31
+ }
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass. Notice
+that we don't have to do any `import`. The values of `global` are
+injected directly into the global scope.
+
+```sentinel
+main = time.now.day == 31
+```
+
+### Parameters
+
+The `param` section allows you to supply values for the
+[parameters](/sentinel/language/parameters) found in a policy. Values
+entered here will satisfy required parameters in a policy, in addition to
+override any defaults. Note that any of the manual parameter value methods
+supported by `sentinel apply` will override the values set here.
+
+```json
+{
+ "param": {
+ "foo": "bar"
+ }
+}
+```
+
+### Test Cases
+
+Test cases specify the test cases for the [test command](/sentinel/commands/test).
+
+Tests are specified by the `test` key. The value of this is a map of
+string to boolean. The key is the name of a rule and the value is the
+expected value of that rule. If a rule is not specified, than any value is
+allowed.
+
+Example:
+
+```json
+{
+ "test": {
+ "main": true,
+ "valid_day": false
+ }
+}
+```
+
+For the policy:
+
+```sentinel
+valid_day = rule { 2 < 1 } # This is just an example!
+main = rule { not valid_day }
+```
+
+For more information, read about the [test command](/sentinel/commands/test).
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/commands/fmt.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/commands/fmt.mdx
new file mode 100644
index 0000000000..6e9660fb43
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/commands/fmt.mdx
@@ -0,0 +1,30 @@
+---
+page_title: 'Command: fmt'
+sidebar_title: 'fmt'
+sidebar_current: docs-commands-fmt
+description: The `sentinel fmt` command formats a policy source to a canonical format.
+layout: docs
+---
+
+# Command: `fmt`
+
+The `sentinel fmt` command formats a policy source to a canonical format.
+
+## Usage
+
+Usage: `sentinel fmt [options] FILE ...`
+
+This command formats all the specified policy files to a canonical format.
+
+By default, policy files are overwritten in place. This behavior can be
+changed with the `-write` flag. If a specified FILE is `-` then stdin is
+read and the output is always written to stdout.
+
+The command-line flags are all optional. The list of available flags are:
+
+* `-write=true` - Write formatted policy to the named source file. If false,
+ output will go to stdout. If multiple files are specified, the output will
+ be concatenated directly.
+* `-check=false` - Don't format, only check if formatting is necessary. Files
+ that require formatting are printed, and a non-zero exit code is returned if
+ changes are required.
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/commands/index.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/commands/index.mdx
new file mode 100644
index 0000000000..7177093cec
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/commands/index.mdx
@@ -0,0 +1,23 @@
+---
+page_title: Commands (CLI)
+sidebar_current: docs-commands
+description: >-
+ Sentinel provides a `sentinel` command-line interface (CLI) for developing and
+ testing policies.
+layout: docs
+---
+
+# Sentinel CLI Commands
+
+The Sentinel command-line interface (CLI) allows for the developing and testing
+of policies outside of a particular Sentinel implementation. Having a standard
+workflow to develop policies is critical for our mission of [policy as
+code](/sentinel/concepts/policy-as-code).
+
+The CLI takes a subcommand to execute. The complete list of subcommands is in
+the navigation to the left.
+
+The Sentinel CLI is a well-behaved command line application. In erroneous cases,
+a non-zero exit status will be returned. It also responds to -h and --help as
+you'd expect. To view a list of the available commands at any time, just run
+`sentinel` with no arguments.
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/commands/test.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/commands/test.mdx
new file mode 100644
index 0000000000..ec96d3a72e
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/commands/test.mdx
@@ -0,0 +1,40 @@
+---
+page_title: 'Command: test'
+sidebar_title: 'test'
+sidebar_current: docs-commands-test
+description: The `sentinel test` command runs policies against the Sentinel test framework.
+layout: docs
+---
+
+# Command: `test`
+
+The `sentinel test` command runs policies against the Sentinel test framework.
+
+To learn more about testing, see the [testing
+policies](/sentinel/writing/testing) page.
+
+## Usage
+
+Usage: `sentinel test [options] [PATH] ...`
+
+This command runs the defined test cases against a set of policies.
+
+The test cases are expected to live at the path `test//*.json` relative
+to the policy being tested. `` should be the name of the policy
+excluding the file extension. The JSON files should be in the [configuration
+file structure](/sentinel/commands/config). The `test` key is used for
+assertions.
+
+The PATH arguments specified may be a list of zero or more files or directories.
+When no arguments are given, it defaults to the current directory. When a
+directory is given, all policies ending with the extension `.sentinel` are
+tested.
+
+The command-line flags are all optional. The list of available flags are:
+
+* `-run=regexp` - Run only tests matching the regular expression pattern.
+ A `/` splits policy name and test case name.
+
+* `-verbose` - Show tracing and log output for tests that succeed in addition
+ to tests that fail. By default, traces and logs are only shown for tests that
+ fail.
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/concepts/enforcement-levels.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/concepts/enforcement-levels.mdx
new file mode 100644
index 0000000000..7c7fbcf996
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/concepts/enforcement-levels.mdx
@@ -0,0 +1,45 @@
+---
+page_title: Enforcement Levels
+sidebar_title: Enforcement Levels
+sidebar_current: docs-concepts-levels
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Enforcement Levels
+
+Enforcement levels are a first class concept in Sentinel allowing pass/fail
+behavior to be associated separately from the policy logic. This enables
+any policy to be a warning, allow overrides, or be absolutely mandatory.
+Because this level is not part of the policy body itself, different uses of
+the same policy can have different enforcement levels.
+
+Sentinel has three enforcement levels:
+
+* **Advisory:** The policy is allowed to fail. However, a warning should be
+ shown to the user or logged.
+
+* **Soft Mandatory:** The policy must pass unless an override is specified.
+ The semantics of "override" are specific to each Sentinel-enabled application.
+ The purpose of this level is to provide a level of privilege separation
+ for a behavior. Additionally, the override provides non-repudiation since
+ at least the primary actor was explicitly overriding a failed policy.
+
+* **Hard Mandatory:** The policy must pass no matter what. The only way to
+ override a hard mandatory policy is to explicitly remove the policy.
+ Hard mandatory is the default enforcement level. It should be used in
+ situations where an override is not possible.
+
+## Configuring Enforcement Levels
+
+Enforcement levels are configured when a policy is deployed to a
+Sentinel-enabled application. The exact mechanism that the level is specified
+is determined by each application. Please reference the documentation for
+your Sentinel-enabled application for more information.
+
+Enforcement levels are not configured and are not known by the policy body
+itself. All policies should be written to describe exactly the behavior
+they're attempting to control. For example, a policy that restricts deploys
+to business hours should be written exactly like so. When that policy is
+configured on an application, the operator may specify that it is advisory,
+soft, or hard mandatory.
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/concepts/imports.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/concepts/imports.mdx
new file mode 100644
index 0000000000..844a06ed59
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/concepts/imports.mdx
@@ -0,0 +1,34 @@
+---
+page_title: Imports
+sidebar_title: Imports
+sidebar_current: docs-concepts-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom import](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only
+local context for making policy decisions.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that the
+import calendar allows access to engineer calendars. This import only exists
+for the purpose of this example and at the time of writing is not a real
+available import.
+
+```sentinel
+import "calendar"
+
+// Get the calendar for Bob for today
+bob_calendar = calendar.for("bob").today
+
+// Allow this policy to pass if Bob is not on vacation.
+main = rule { not bob_calendar.has_event("vacation") }
+```
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/concepts/index.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/concepts/index.mdx
new file mode 100644
index 0000000000..ce7ac51454
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/concepts/index.mdx
@@ -0,0 +1,15 @@
+---
+page_title: Basic Concepts
+sidebar_current: docs-concepts
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Basic Concepts
+
+This section covers some high level basic concepts that are important
+to understand for day to day Sentinel usage. Every page in
+this section is recommended reading for anyone consuming
+or extending Sentinel.
+
+Please use the navigation to the left to learn more about a topic.
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/concepts/language.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/concepts/language.mdx
new file mode 100644
index 0000000000..af287f1da6
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/concepts/language.mdx
@@ -0,0 +1,92 @@
+---
+page_title: Policy Language
+sidebar_title: Policy Language
+sidebar_current: docs-concepts-language
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Policy Language
+
+Sentinel defines and uses its own [policy language](/sentinel/language).
+
+The language was designed to be approachable by non-programmers, since
+there are many use cases where the individual defining policy may not
+be a developer. However, the language includes constructs that
+are familiar to developers to enable powerful policies.
+
+To learn more about the language, please see the
+[writing policy section](/sentinel/writing)
+or the [language reference](/sentinel/language).
+
+An example of the policy language is shown below:
+
+```sentinel
+import "time"
+
+# Validate time is between 8 AM and 4 PM
+valid_time = rule { time.now.hour >= 8 and time.now.hour < 16 }
+
+# Validate day is M - Th
+valid_day = rule {
+ time.now.weekday_name in ["Monday", "Tuesday", "Wednesday", "Thursday"]
+}
+
+main = rule { valid_time and valid_day }
+```
+
+## Why?
+
+To explain why Sentinel defines and uses its own language, the question
+can be more easily split into roughly two types of languages: _configuration_ languages
+and _programming_ languages.
+
+### Configuration Languages
+
+Configuration languages are formats such as JSON, YAML, XML, etc. Many applications
+use configuration languages as their ACL format. Configuration languages
+are good for static, declarative information. ACL systems are a good fit
+for this. ACL systems are focused, providing the limiting options necessary
+to restrict access to a system.
+
+Configuration languages are not good for dynamic or logical rules. They
+typically only contain limited conditions, loops, or functions. Further
+configuration is often declarative, which can introduce complexities to
+logical statements that depend on ordering.
+
+The use cases that Sentinel was built for require the ability to perform
+complex behavior that didn't model well into a configuration format or a
+declarative form.
+
+### Programming Languages
+
+The Sentinel language was designed with the following goals:
+
+* **Non-programmer friendly.** Sentinel was built to be used by non-programmers.
+ For the use cases we discovered, non-programmers needed the ability to
+ enforce certain rules within a system. For example, a person responsible for
+ compliance may need to insert rules into a system.
+
+* **Programmer friendly.** At the same time, the language needed to support
+ programmer-friendly constructs such as conditionals, loops, and functions
+ for complex policies that a programmer may be writing.
+
+* **Embeddable.** Sentinel is embedded in existing software. The language
+ itself needs to be easily embeddedable. Further, Sentinel was designed
+ to be embedded in [HashiCorp](https://www.hashicorp.com) software
+ written in Go, so it needed to be easily embeddable in Go.
+
+* **Safe.** The language is used in highly security-sensitive environments.
+ It must not be able to crash its host system or access system resources
+ without explicit approval.
+
+Prior to building our own language, Sentinel evaluated other languages.
+Some languages worked quite well. As we continued to develop Sentinel, we
+found that the flexibility and control over designing our own language
+outweighed the costs.
+
+Because the language itself doesn't need to be general purpose, building
+a language focused on policy proved to be the best route for Sentinel.
+It allows us to build powerful tracing capabilities for debugging, make
+interesting performance choices, and extend the language as needed in the
+future.
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/concepts/policy-as-code.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/concepts/policy-as-code.mdx
new file mode 100644
index 0000000000..d9b51f04ad
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/concepts/policy-as-code.mdx
@@ -0,0 +1,73 @@
+---
+page_title: Policy as Code
+sidebar_title: Policy as Code
+sidebar_current: docs-concepts-pac
+description: >-
+ Policy as code is the idea of writing code in a high-level language to manage
+ and automate policies. By representing policies as code in text files, proven
+ software development best practices can be adopted such as version control,
+ automated testing, and automated deployment.
+layout: docs
+---
+
+# Policy as Code
+
+Policy as code is the idea of writing code in a high-level language to
+manage and automate policies. By representing policies as code in text files,
+proven software development best practices can be adopted such as version
+control, automated testing, and automated deployment.
+
+Many existing policy or ACL systems do not practice policy as code. Many
+policies are set by clicking in a GUI, which isn't easily repeatable nor
+versionable. They usually don't provide any system for testing policies
+other than testing an action that would violate the policy. This makes it
+difficult for automated testing. And the policy language itself varies by
+product.
+
+Sentinel is built around the idea and provides all the benefits of policy as code.
+
+## Benefits
+
+Policy as code provides a number of benefits:
+
+* **Sandboxing.** Policies provide the guardrails for other automated systems.
+ As the number of automated systems grow, there is also a growing need to
+ protect those automated systems from performing dangerous actions. Manual
+ verification is too slow; policies need to be represented as code to
+ keep up with other automated systems.
+
+* **Codification.** By representing policy logic as code, the information
+ and logic about a policy is directly represented in code and can be augmented
+ with comments rather than relying on oral tradition to learn about the
+ reason for policies.
+
+* **Version Control.** Policies are encouraged to be stored as simple text
+ files managed by a version control system. This lets you gain all the
+ benefits of a modern VCS such as history, diffs, pull requests, and more.
+
+* **Testing.** Policies are just code. Their syntax and behavior can be
+ [easily validated with Sentinel](/sentinel/commands/test). This also encourages
+ automated testing such as through a CI. Paired with a VCS system, this
+ allows a pull request workflow to verify that a policy keeps the system
+ behavior as expected before merging.
+
+* **Automation.** With all policies as code in simple text files, various
+ automation tools can be used. For example, it is trivial to create tools
+ to automatically deploy the policies into a system.
+
+## Sentinel and Policy as Code
+
+Sentinel fully embraces policy as code in a number of ways:
+
+* **Language.** All Sentinel policies are written using the
+ [Sentinel language](/sentinel/concepts/language). This language is
+ made to inputted directly to text files. As an additional benefit,
+ all Sentinel-enabled applications share the same policy language.
+
+* **Development.** Sentinel provides a [CLI](/sentinel/commands)
+ for development and testing. This local CLI can be used to verify policies
+ before deploying them to a system.
+
+* **Testing.** Sentinel provides a [test framework](/sentinel/commands/test)
+ designed specifically for automation. This allows developers and CI systems
+ to further verify policies.
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/consul/index.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/consul/index.mdx
new file mode 100644
index 0000000000..16b3f502a6
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/consul/index.mdx
@@ -0,0 +1,49 @@
+---
+page_title: Consul and Sentinel
+sidebar_title: Consul
+sidebar_current: docs-app-consul
+description: >-
+ Consul Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies are applied during writes to
+ the KV Store.
+layout: docs
+---
+
+# Consul
+
+[Consul Enterprise](https://www.hashicorp.com/products/consul/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies are applied during writes to the KV Store.
+
+Sentinel policies have access to the key/value being written. They can be used to
+allow or deny the modification. The information that Sentinel policies have access
+to will expand over time.
+
+The Consul integration with Sentinel is documented in depth in the
+[Consul Enterprise documentation](https://www.consul.io/docs/guides/sentinel.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Input validation depending on the name of the key.**
+
+```sentinel
+
+main = rule { valid_key() }
+
+required = [
+ ["port", "\\d+"], # ports must be integers
+ ["name", "\\w+"], # name must be a word
+]
+
+valid_key = func() {
+ for required as v {
+ if key is v[0] {
+ return value matches v[1]
+ }
+ }
+
+ return false
+}
+```
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/extending/index.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/extending/index.mdx
new file mode 100644
index 0000000000..60836644f4
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/extending/index.mdx
@@ -0,0 +1,43 @@
+---
+page_title: Extending Sentinel
+sidebar_current: docs-extending
+description: Learn how to create your own Sentinel imports to extend Sentinel's functionality.
+layout: docs
+---
+
+# Extending Sentinel
+
+-> **NOTE:** Sentinel's extensibility is currently undergoing large
+improvements - watch this space for future updates!
+
+Sentinel can be extended by writing additional
+[imports](/sentinel/language/imports). These imports can bring new functionality
+to Sentinel by allowing access to new data or by the addition of new functions.
+This section is designed to show you how to accomplish this extensibility and
+describe how it works.
+
+Currently, the only usable way to add additional imports is via
+[modules](#modules). While [plugins](#plugins) currently work under the Sentinel
+CLI, no Sentinel integration currently supports their use.
+
+-> You may also be interested in advanced details on [import
+internals](/sentinel/extending/internals).
+
+## Modules
+
+Modules allow you to re-use Sentinel code as an import. Modules are loaded
+external of policies and mapped to imports. This is usually configured via a
+file such as the [CLI configuration file](/sentinel/commands/config).
+
+See the [modules](/sentinel/extending/modules) section for more details.
+
+## Plugins
+
+-> **NOTE:** plugins are currently only supported on the CLI and not in any
+production Sentinel integration.
+
+Imports can also be implemented as plugins when Sentinel code itself is too
+restrictive to represent the import, or if you need access to features not
+normally available to the Sentinel runtime.
+
+See the [plugins](/sentinel/extending/plugins) section for more details.
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/extending/internals.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/extending/internals.mdx
new file mode 100644
index 0000000000..b7d49347f9
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/extending/internals.mdx
@@ -0,0 +1,253 @@
+---
+page_title: >-
+ Extending Sentinel: Internals
+sidebar_title: Internals
+sidebar_current: docs-extending-internals
+description: This Section covers some details about Sentinel's import internals.
+layout: docs
+---
+
+# Extending Sentinel: Internals
+
+-> **Advanced Topic!** This page covers low-level technical details of Sentinel.
+You don't need to understand these details to effectively use Sentinel. The
+details are documented here for those who wish to learn about them.
+
+This section covers some of the internal details of Sentinel's import system.
+The goal of this section is to help remove notion of "magic" from Sentinel, to
+allow you to trust and understand what Sentinel is doing with imports, and also
+to help practitioners and developers alike understand the differences between
+the ways that imports can be loaded into Sentinel.
+
+## Language and Runtime
+
+Understanding the import system in Sentinel generally requires understanding of
+two key concepts within Sentinel: the _language_, and the _runtime
+implementation_ of the language.
+
+The Sentinel language and the rules governing it are detailed in the [Sentinel
+Language Specification](/sentinel/language/spec). A runtime implementation must
+conform to the rules laid out in the specification at a minimum. However, to
+meet the design goals of a particular implementation, the runtime may implement
+features on top of the language. The subtle implementation details within the
+runtime may also vary while still being compliant with the specification.
+
+The core runtime included within the [Sentinel CLI](/sentinel/commands) is
+sometimes referred to as the _reference implementation_ of the Sentinel
+language. This runtime is also the main runtime embedded within each
+Sentinel-enabled HashiCorp product such as Terraform Cloud and Enterprise, Vault
+Enterprise, Nomad Enterprise, and Consul Enterprise. The runtime includes an API
+to allow these integrations implement Sentinel in as robust or as restrictive of
+a way as possible, so depending on the implementation, your experience may vary.
+
+## Sentinel's Import Model
+
+The concept of Imports within Sentinel is a _language_ feature. You can see the
+exact rules governing imports within the
+[Imports](/sentinel/language/spec#imports) section of the specification.
+
+Within the runtime, there are currently two major classifications of imports:
+
+- **Modules:** The mapping of Sentinel code to an import, essentially mapping a
+ scope of values to a particular package.
+- **Binary Imports:** A grouping of embedded and external plugins written using
+ the Sentinel SDK. These are comprised of internal or embedded imports (usually
+ the [standard imports](/sentinel/imports) and imports included by an
+ integration) and import plugins.
+
+Below is a diagram explaining in brief the relationship between the langauge
+and runtime features.
+
+
+
+This kind of topology ultimately minimizes the visibility of implementation
+internals to the actual policy language. This allows us to keep the Sentinel
+language itself simple and keep concerns such as module or plugin management,
+validation, and configuration out of the language. These kinds of things would
+impact the readability of a policy, possibly present security challenges, and
+would ultimately be of no use to more minimal embedded applications.
+
+### Implementation Differences Between Import Types
+
+As one would imagine, both modules and binary imports are loaded in much
+different ways, and the methods that they expose data to Sentinel differ as
+well. The effect is generally the same however across both, and we take care to
+ensure that both modules and binary imports behave as close as possible to each
+other, however there are some subtle differences:
+
+- Modules currently support the exporting of rules, but do not support function
+ calls with object receiver data (as seen in some standard imports such as
+ [`decimal`](/sentinel/imports/decimal), [`http`](/sentinel/imports/http), and
+ [`time`](/sentinel/imports/time)). This will be coming in a later release.
+- Binary imports cannot export rules. However, they do support object receiver
+ data (see [`framework.New`](/sentinel/extending/plugins#framework-new) in
+ [Extending Sentinel: Plugins](/sentinel/extending/plugins)).
+
+## Modules
+
+_Modules_ are essentially Sentinel code that is intended to be re-used in multiple
+policies as an import.
+
+The mechanism of module loading is fairly straightforward: during initialization
+of the Sentinel runtime, modules are parsed along with policies. The parsed
+_abstract syntax tree_ (AST) for each module is loaded into the runtime under
+the specified _import path_. These are then passed to the lower-level
+interpreter during the evaluation of each policy.
+
+As with policy code, during evaluation, a module's AST is walked to execute the
+code within that specific module. The module data is then stored within an
+specific scope mapped to the import name. The module data can then be accessed
+through the import via selector expressions and function calls (e.g.,
+`foo.some_map` or `foo.some_func()` for a module loaded via `import "foo"`).
+
+The AST for a module is only walked if a policy imports it, and it is _only
+walked once_. That means if a policy and a module import the same module, or a
+policy imports the same module twice (using [import
+aliases](/sentinel/language/imports#aliases)), those instances will share state.
+If you call a function that alters a singleton variable within the module, that
+singleton will be modified for all instances of the import.
+
+-> **Fun fact!** When you [mock an import with Sentinel
+code](/sentinel/commands/config#mocking-with-sentinel-code), you are actually
+using a module. The module is loaded with a flag that allows it to override an
+existing import, which would normally give a runtime error.
+
+### Map and List Cloning for Module Calls
+
+It's also worthwhile noting that map and list values are cloned for modules.
+
+Consider the case where you have a module with a map singleton.
+
+```sentinel
+// modules/foo.sentinel
+a_map = {"a": "b"}
+```
+
+Normally, assignment to the singleton, even when using an index expression, is
+blocked, so `foo.a_map["c"] = "d"` would not pass semantic checking.
+
+However, even when you try to do assignment via an intermediary, you will still
+be only modifying the copy, and the original will not be affected:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+a_map_copy = foo.a_map
+a_map_copy["c"] = "d" // Only modifies copy, does not modify module singleton
+print(foo.a_map) // Still only prints {"a": "b"}
+```
+
+This is to ensure that modules are consistent with binary imports in how return
+data is handled, and the expectation that most non-object data within an import
+is read-only, with the exception of modification of singleton data via
+functions. As return of complex object and collection data in a binary import is
+always via copy, it's not possible to modify any value elements within the
+import in a similar fashion.
+
+There are also some other implications of this cloning that are of note:
+
+- Rules are not cloned, either within a list or map, or by themselves. This is
+ to ensure that [memoization](/sentinel/language/rules#lazy-and-memoized)
+ functions correctly. As only a module can export rules at this time, there is no
+ parity for this in binary imports.
+- Functions are _not_ cloned. This mainly has implications for scope - for
+ example, if you assign a function to another value, or assign an object to a
+ value that has a method that utilizes a global module variable, those calls will
+ reference and/or modify those values.
+
+Functions, while represented differently in binary imports, generally follow the
+same logic here - as they would be invoked in the same package within the binary
+import, any singleton values they accessed there would be affected in a similar
+fashion. As rules are pseudo-functions, this generally makes sense here too.
+
+## Binary Imports
+
+_Binary imports_ is a grouping referring to all imports that are designed with
+the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). These can be
+either internally embedded, or executed via a plugin.
+
+All imports found in the [standard library](/sentinel/imports) are binary
+imports, embedded internally.
+
+The main difference between internally embedded imports and external plugins is
+whether or not the runtime needs to execute a plugin binary as part of
+[lifecycle management](#lifecycle), and whether or not it needs to
+[communicate](#communication) over RPC. Internal binary imports do not require
+these steps, so their execution model is somewhat simpler; however, technically,
+they still are the same as external plugins in terms of design model and value
+conversion. Most standard imports are even tested using the [SDK test
+suite](/sentinel/extending/plugins#testing), which builds the import as an
+external plugin.
+
+The remainder of this section discusses topics mostly relevant to external
+plugins. A large amount of technical detail regarding binary import development
+(also applicable to embedded binary imports) can be seen on the
+[Plugins](/sentinel/extending/plugins) page.
+
+-> **NOTE:** At this time, plugins can only be written for the Sentinel CLI, and
+cannot be used with any of Sentinel's integrations.
+
+### Plugin Basics
+
+Sentinel plugins are built on top of the HashiCorp [go-plugin
+system](https://github.com/hashicorp/go-plugin). This is the same plugin system
+powering all pluggable HashiCorp tools such as
+[Terraform](https://www.terraform.io), [Vault](https://www.vaultproject.io), and
+more. go-plugin is a system that has been used in production for millions of
+users for over 5 years.
+
+Plugins are executable binaries. Sentinel is responsible for launching and
+managing the lifecycle of plugins. Once a plugin is running, Sentinel
+communicates with the plugin via [gRPC](https://grpc.io/). The [protocol is open
+source](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/import.proto)
+to allow anyone to write a Sentinel plugin.
+
+### Lifecycle
+
+Sentinel launches plugins and is responsible for plugin lifecycle.
+
+The runtime optimizes for latency by launching the plugin as soon as it is
+configured, rather than when a policy requires it. This ensures that the plugin
+is ready to be used immediately. A plugin is only closed when Sentinel is
+reconfigured to no longer allow that plugin or if the Sentinel-enabled
+application is closing.
+
+When a plugin is removed from the configuration, Sentinel may wait to shut down
+the plugin until all currently executing policies that are using that plugin
+complete. New policy executions will not be allowed to use the old plugins.
+
+Plugins are automatically restarted if they shut down unexpectedly. Policies
+that were executing while this happens may fail. The Sentinel system is
+improving to more gracefully understand locations where it is safe to retry an
+execution.
+
+### Communication
+
+An [initial
+handshake](https://github.com/hashicorp/go-plugin/blob/master/docs/internals.md)
+is done via stdout to determine the main communication location. Following the
+handshake, communication will either occur on a local Unix domain socket or via
+a local-only TCP socket. This communication is done mostly over the low-level
+[`Get`](https://github.com/hashicorp/sentinel-sdk/blob/2b4db07dd12b55142f0c016f301af80aa4422520/proto/import.proto#L12)
+RPC call.
+
+#### Value Conversion
+
+As can generally be seen in [Developing an Import
+Plugin](/sentinel/extending/plugins#developing-an-import-plugin), the Sentinel
+type system is not exposed to binary imports. While explicit values for null and
+undefined exist, values are mostly converted over the wire from their Go values
+to their Sentinel equivalents.
+
+This has a few implications:
+
+- As discussed in [Map and List Cloning for Module
+ Calls](#map-and-list-cloning-for-module-calls), Map and list data returned from
+ binary import value lookups or function calls is always cloned. These can be
+ freely modified without affecting anything within the binary import.
+- It's currently impossible to represent certain Sentinel types such as
+ [rules](/sentinel/language/rules) within a binary import. This is not a major
+ issue at this point in time as practitioners generally do not look to binary
+ imports for rules; we may examine this as a later time if this situation
+ changes.
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/extending/modules.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/extending/modules.mdx
new file mode 100644
index 0000000000..3e768e3fe9
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/extending/modules.mdx
@@ -0,0 +1,137 @@
+---
+page_title: >-
+ Extending Sentinel: Modules
+sidebar_title: Modules
+sidebar_current: docs-extending-modules
+description: >-
+ Modules allow you to re-use Sentinel code as an import.
+layout: docs
+---
+
+# Extending Sentinel: Modules
+
+Modules allow you to re-use Sentinel code as an import. This allows for the
+export of any general construct that Sentinel supports, including values,
+functions, and even rules. As such, it's a great way to package code that is
+useful across multiple policies, reducing boilerplate and making final policy
+code simpler.
+
+You may want to use modules for:
+
+- **Writing a simple re-usable helper library.** If you mostly know Sentinel or
+ have helpers that you have written in Sentinel, moving these helpers to a module
+ will offer a low-friction way of facilitating their re-use.
+- **Writing re-usable rules.** If you have a set of
+ [rules](/sentinel/language/rules) you know you want to use across multiple
+ policies, bundling them into a module is a great way of abstracting the logic
+ away.
+- **Abstracting existing Sentinel imports.** Using a module is a great way to
+ extend existing Sentinel imports by abstracting the common functionality that
+ you use within an import to your own workflow.
+
+This page describes how you can use modules within the context of the Sentinel
+CLI. For instructions for a particular integration, see the documentation for
+that product.
+
+-> **Not all Sentinel-enabled applications support modules.** Please refer
+to the documentation of the specific Sentinel-enabled application. Some
+applications disable modules for security reasons.
+
+## Configuring a Module
+
+A module is configured within the Sentinel CLI by adding an entry for it in
+within the `modules` section of the [CLI configuration
+file](/sentinel/commands/config). The key for each entry specifies the path that
+the module is imported as. The `path` value within each entry specifies the path
+to the module file on the local filesystem.
+
+```json
+{
+ "modules": {
+ "foo": {
+ "path": "modules/foo.sentinel"
+ },
+ "bar": {
+ "path": "modules/bar.sentinel"
+ }
+ }
+}
+```
+
+In this example, we have two modules:
+
+- Import path `foo`, being loaded from the code within `modules/foo.sentinel`.
+- Import path `bar`, being loaded from the code within `modules/bar.sentinel`.
+
+See the [modules](/sentinel/commands/config#modules) section within the CLI
+configuration file syntax page for more details.
+
+### Testing Using Modules
+
+As with [mocks](/sentinel/commands/config#mocking-with-sentinel-code), modules
+are loaded relative to the configuration file location when using `sentinel test`. As such, you need to account for this in your test configuration files:
+
+```json
+{
+ "modules": {
+ "foo": {
+ "path": "../../modules/foo.sentinel"
+ },
+ "bar": {
+ "path": "../../modules/bar.sentinel"
+ }
+ }
+}
+```
+
+This could be a test file for a policy (example: `policy.sentinel`), residing
+under the correct test file directory for this policy (example:
+`test/policy/pass.json`).
+
+## Writing and Using a Module
+
+Writing a module is nearly the same as writing a Sentinel policy, except for the
+following two exceptions:
+
+- Modules do not require [`main`](/sentinel/writing/basic#the-simplest-policy)
+ to be present. It can be, but it will not carry any extra significance as it
+ does within a policy.
+- [`param`](/sentinel/language/parameters) declarations cannot be present in a module. If they are encountered,
+ a runtime error is given.
+
+Once you have a complete module ready for use and configured, using the module
+is as simple as importing it.
+
+Building on the above [configuration example](#configuring-a-module), we can
+export a simple test function in the module `foo` by adding this code to
+`modules/foo.sentinel`:
+
+```sentinel
+// modules/foo.sentinel
+hello = func() {
+ print("hello world!")
+ return undefined
+}
+```
+
+We can then import it within our policy and use it:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+// prints "hello world" in the trace
+foo.hello()
+
+main = true
+```
+
+Note that modules are considered [imports](/sentinel/language/spec#imports) by
+the Sentinel runtime and as such conform to the specification. This means that
+you cannot directly assign values to anything behind a module scope. You can,
+however, use functions to change state.
+
+-> **NOTE:** At this time, modules do not support object receiver data in the
+way that some imports do, like [`time`](/sentinel/imports/time),
+[`decimal`](/sentinel/imports/decimal), and [`http`](/sentinel/imports/http).
+Support for this will be coming in later versions of Sentinel.
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/extending/plugins.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/extending/plugins.mdx
new file mode 100644
index 0000000000..1ca89f3a23
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/extending/plugins.mdx
@@ -0,0 +1,521 @@
+---
+page_title: >-
+ Extending Sentinel: Plugins
+sidebar_title: Plugins
+sidebar_current: docs-extending-plugins
+description: >-
+ Plugins allow you to write imports as standalone executables, allowing you to
+ extend Sentinel in ways not normally possible with policy code alone.
+layout: docs
+---
+
+# Extending Sentinel: Plugins
+
+-> **NOTE:** Plugins are currently only supported on the CLI and not in any
+production Sentinel integration.
+
+Plugins allow you to write imports as standalone executables, allowing you to
+extend Sentinel in ways not normally possible with policy code alone.
+
+You might want to write or use a plugin when you need to:
+
+- **Use a provider or API integration for Sentinel.** In this case, you will
+ probably be working with a provider SDK, or other libraries that will be making
+ complex network requests to external services. Sentinel only provides limited
+ capabilities for these kinds of operations in the standard library, so writing
+ this kind of import as a module is not optimal.
+- **Introduce novel functionality not currently supported by Sentinel.**
+ Sentinel's policy language is limited by design to encourage simple policies,
+ reduce its runtime footprint, and to keep it efficient and safe. This will
+ mean that some functionality may be difficult or impossible to express as
+ Sentinel code, and would require a plugin to write.
+- **Extend a complex module.** Additionally, modules are restricted to a single
+ file of Sentinel code, so these will naturally become more and more unwieldy as
+ you continue to add to them. Eventually, there might be a time where a plugin is
+ better suited for the functionality, especially if it's a general-purpose
+ library.
+
+This section details how import plugins are currently configured in the Sentinel
+CLI, and some detail on how they are written. As we continue to build on the
+ability to use import plugins, we will extend this section with more details.
+
+-> **Not all Sentinel-enabled applications will support plugins.** Some
+applications will disable plugins for security reasons. For those that do enable
+plugins, the method to configure plugins will vary.
+
+## Installing Plugins
+
+Sentinel plugins are standalone executables. Sentinel-enabled applications such
+as the CLI launch these plugins and communicate with them via
+[RPC](https://en.wikipedia.org/wiki/Remote_procedure_call). To install a plugin,
+the first step is to download the plugin executable.
+
+After downloading the plugin, you must add it in the CLI configuration file,
+setting its name, path, and any arguments, environment variables, or
+configuration. An example is below:
+
+```json
+{
+ "imports": {
+ "time": {
+ "path": "/path/to/sentinel-import-time",
+ "config": { "fixed_time": 1504155600 }
+ }
+ }
+}
+```
+
+After configuring the plugin, it will be available on the next `sentinel apply`
+or `sentinel test`.
+
+For more details on configuration, see the
+[plugins](/sentinel/commands/config#plugins) section of the [CLI configuration
+file syntax](/sentinel/commands/config).
+
+## Debugging Plugins
+
+The Sentinel CLI logs when it launches, configures, and closes a plugin. The
+plugin may also log additional information when it is running.
+
+To debug issues with a plugin, you can usually refer to these logs. Depending on
+the plugin configuration, the logs you see may vary - refer to the plugin
+documentation on how to enable logs for a particular plugin.
+
+## Developing an Import Plugin
+
+Anyone can develop a Sentinel import plugin using the [Sentinel
+SDK](https://github.com/hashicorp/sentinel-sdk). The SDK contains a high-level
+framework for writing plugins in [Go](https://golang.org) including a test
+framework. Additionally, the SDK contains the low-level [gRPC protocol buffers
+definition](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/import.proto)
+for writing plugins in other languages.
+
+The rest of this page will document how to write a new Sentinel import in Go
+using the Sentinel SDK and the high-level framework provided. You are expected
+to already know the basics of Go and have a properly configured Go installation.
+
+Throughout this page, we'll be developing a simple import to access time values.
+
+-> **NOTE:** The example here is not reflective of the actual implementation of
+the [`time`](/sentinel/imports/time) standard import, so make sure to not
+get the two confused.
+
+### Import Framework
+
+The Sentinel SDK provides a high-level
+[framework](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework)
+for writing imports. We recommend using this framework.
+
+#### Basic Concepts
+
+This framework works by implementing the correct Go interfaces.
+
+As a general overview:
+[`framework.Import`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Import)
+implements the
+[`sdk.Import`](https://godoc.org/github.com/hashicorp/sentinel-sdk#Import)
+interface and therefore is a valid import plugin. You must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface to configure the import. The root may then delegate nested access to
+various
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+implementations. Other interfaces are implemented to provide functionality past
+what is provided by the basic `framework.Namespace` implementation - these are
+documented below.
+
+This all may sound like a lot of interfaces, but each interface typically only
+requires a single function implementation and you're only required to implement
+a single namespace (`framework.Namespace`).
+
+As we move on, we'll use real examples to make this clear.
+
+#### Implementing framework.Root
+
+To begin, you must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface. This is the interface representing the root of your import. The root
+itself must be implement
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace),
+which allows values to be accessed. Note that the root can optionally implement
+other interfaces, but they're more advanced and omitted here for simplicity.
+
+For our time example, our root may look like this:
+
+```sentinel
+type root struct {
+ time time.Time
+}
+
+// framework.Root impl.
+func (m *root) Configure(raw map[string]interface{}) error {
+ if _, ok := raw["timestamp"]; !ok {
+ raw["timestamp"] = time.Now().Unix()
+ }
+
+ v := raw["timestamp"]
+ timestamp, ok := v.(int)
+ if !ok {
+ return fmt.Errorf("invalid timestamp type %T", v)
+ }
+
+ m.time = time.Unix(timestamp, 0).UTC()
+ return nil
+}
+
+// framework.Namespace impl.
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "minute":
+ return m.time.Minute(), nil
+ }
+
+ return nil, nil
+}
+```
+
+This example implements `framework.Root` and `framework.Namespace` and would
+enable the following within a Sentinel policy once the completed import is
+installed.
+
+```sentinel
+import "time"
+
+print(time.minute)
+main = true
+```
+
+#### framework.Namespace
+
+The
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+interface implements a namespace of values. You saw above that
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+must itself be a namespace. However, a namespace `Get` implementation may
+further return namespaces to access nested values.
+
+This enables behavior such as `time.month.string` vs. `time.month.index`.
+Notice each of these examples accesses a nested value within `month`. This can
+be modeled as namespaces within the framework.
+
+In the example below, we return a new namespace for `month` to do just this:
+
+```sentinel
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "month":
+ return &namespaceMonth{Month: m.time.Month()}, nil
+ }
+
+ // ...
+}
+
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil nil
+}
+```
+
+#### Primitive Types and Structs
+
+A namespace may also return any primitive Go type as well as structs. The
+framework automatically exposes primitive values as you would expect. For
+structs, exported fields are lowercased and exposed to the policies. The
+`sentinel` struct tag can be used to control how Sentinel can access the field.
+
+In the example below, we expose a struct directly from the root namespace:
+
+```sentinel
+type Location struct {
+ Name string
+ TimeZone string `sentinel:"time_zone"`
+}
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "location":
+ return &Location{Name: "somewhere", TimeZone: "some zone"}, nil
+ }
+
+ // ...
+}
+```
+
+This makes the following values work within a Sentinel policy:
+`time.location.name`, `time.location.time_zone`.
+
+If a field has an empty `sentinel` struct tag (example: `sentinel:""`),
+then that field will not be accessible from a policy.
+
+#### Optional Interfaces
+
+The following are optional interfaces that can be implemented on top of
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace).
+All of these interfaces can be implemented at any level, except for
+[`framework.New`](#framework-new), which only works at the root level.
+
+##### `framework.Call`
+
+The
+[`framework.Call`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Call)
+interface can be implemented to support function calls.
+
+Implementing this interface is very similar to attribute access, except instead
+of returning the attribute value, you return a function. The framework uses Go
+reflection to determine the argument and result types and calls it. If the
+argument types do not match or the signature is otherwise invalid, an error is
+returned.
+
+For example, let's implement a function to add months to the current month:
+
+```sentinel
+func (m *root) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *root) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.time.AddDate(0, n, 0).Month()}
+}
+```
+
+You can now call the function. If today was in the month of September,
+the policy below would pass:
+
+```sentinel
+import "time"
+
+main = time.add_month(4).string == "January"
+```
+
+##### `framework.Map`
+
+The optional
+[`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+interface allows an alternative method to to present a namespace back to a
+Sentinel policy as a map.
+
+`framework.Map` is best paired with
+[`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys),
+which allows you to quickly fetch the necessary data via `Get` calls on the
+namespace:
+
+```
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil, nil
+}
+
+func (m *namespaceMonth) Map() (map[string]interface{}, error) {
+ return framework.MapFromKeys(m, []string{"string", "index"})
+}
+```
+
+##### `framework.New`
+
+The optional
+[`framework.New`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#New)
+interface can be implemented on a root namespace to add _methods_ to your
+namespaces.
+
+Data returned from a namespace is memoized and returned as a map, and is
+normally not callable - to call a function to operate on the data, you would
+need to create a new namespace from the top-level of the import, and then make a
+function call on the result within the same expression.
+
+Let's consider the [root example](#implementing-framework-root). Let's say,
+instead of hard-coding the time value at configuration, we wanted to load it
+on-demand using a `time.now` key, and allow `add_month` to be callable on that
+value. Our root and de-coupled time namespace would now look like:
+
+```
+type root struct{}
+
+func (m *root) Configure(raw map[string]interface{}) error { return nil }
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "now":
+ return &namespaceTime{Time: time.Now()}
+ }
+
+ return nil
+}
+
+func (m *root) New(data map[string]interface{}) (framework.Namespace, error) {
+ if v, ok := data["unix"]; ok {
+ if t, ok := v.(int64); !ok {
+ return &namespaceTime{Time: time.Unix(t, 0)}
+ }
+
+ return nil, fmt.Errorf("expected timestamp to be int64, got %T", v)
+ }
+
+ return nil, nil
+}
+
+type namespaceTime struct {
+ Time time.Time
+}
+
+func (m *namespaceTime) Get(key string) interface{} {
+ switch key {
+ case "unix":
+ return m.Time.Unix()
+
+ // case ...
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) Get(key string) (interface{}, error) {
+ return framework.MapFromKeys(m, []string{"unix", "..."})
+}
+
+func (m *namespaceTime) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.Time.AddDate(0, n, 0).Month()}
+}
+```
+
+Via this example, we can now create and assign a `namespaceTime` using `t = time.now`, and then call `t.add_month(months_to_add)` to return a
+`namespaceMonth` for the corresponding month.
+
+Methods on a namespace can also _modfiy_ the receiver data. So you can, for
+example, add a method named `increment_month` that takes no arguments, but
+increments the time stored in `namespaceTime.Time` by a month. Subsequent
+statements using the receiver would see the new value.
+
+Note that there are some restrictions and guidelines that you should take into
+account when using `framework.New`:
+
+- Only data that makes it back to a policy can be used as receiver data to
+ instantiate new namespaces. As such it's recommended to make use of
+ [`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+ and
+ [`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys)
+ to return all of the attribute data you need to construct new objects.
+- `framework.New` is only supported on the root namespace and has no effect on
+ namespaces below the root. Check all possible cases of receiver data within
+ the top-level `New` function and return the appropriate namespace based on the
+ input data.
+- Do not return general errors from your `New` method. When an unknown lookup is
+ made off of memoized data, it will hit your `New` method for possible
+ instantiation and key calls. This will ensure `undefined` is correctly
+ returned for these cases.
+- `framework.New` is designed to add utility to imports where calling methods on
+ a value is more intuitive than just making a function call, or just returning
+ all of a data set as a memoized map. Use it sparingly and avoid using it on
+ recursively complex data sets.
+
+### Testing
+
+The SDK exposes a testing framework to verify your plugin works as
+expected. This test framework integrates directly into `go test` so it
+is part of a familiar workflow.
+
+The test framework works by dynamically building your import and
+running it against the `sentinel` CLI to verify it behaves as expected.
+This ensures that your plugin builds, your plugin communicates via RPC
+correctly, and that the behavior within a policy is also correct.
+
+The example below shows a complete ready table-driven test for our time plugin.
+You can run this with a normal `go test`.
+
+```sentinel
+package main
+
+import (
+ "os"
+ "testing"
+
+ "github.com/hashicorp/sentinel-sdk"
+ plugintesting "github.com/hashicorp/sentinel-sdk/testing"
+)
+
+func TestMain(m *testing.M) {
+ exitCode := m.Run()
+ plugintesting.Clean()
+ os.Exit(exitCode)
+}
+
+func TestImport(t *testing.T) {
+ cases := []struct {
+ Name string
+ Data map[string]interface{}
+ Source string
+ }{
+ {
+ "month",
+ map[string]interface{}{"timestamp": 1495483674},
+ `main = subject.month.string == "September"`,
+ },
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.Name, func(t *testing.T) {
+ plugintesting.TestImport(t, plugintesting.TestImportCase{
+ Config: tc.Data,
+ Source: tc.Source,
+ })
+ })
+ }
+}
+```
+
+### Building Your Import
+
+To build your import, you must first implement the `main` function.
+The main function should just use the `rpc.Serve` method to serve the
+plugin over the Sentinel RPC layer:
+
+```sentinel
+package main
+
+import (
+ "github.com/hashicorp/sentinel-sdk"
+ "github.com/hashicorp/sentinel-sdk/rpc"
+)
+
+func main() {
+ rpc.Serve(&rpc.ServeOpts{
+ ImportFunc: func() sdk.Import {
+ return &framework.Import{Root: &root{}}
+ },
+ })
+}
+```
+
+You can then build this using `go build`. The output can be named
+anything. Once your plugin is built, [install it](#installing-plugins)
+and try it out!
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/functions/append.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/functions/append.mdx
new file mode 100644
index 0000000000..1b3e54b8e5
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/functions/append.mdx
@@ -0,0 +1,47 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Append'
+sidebar_title: 'append'
+sidebar_current: docs-funcs-append
+description: The built-in function `append` appends a value to the end of a list.
+layout: docs
+---
+
+# Builtin Function: append
+
+The built-in function `append` appends a value to the end of a list. The list
+is modified in-place. The return value will always be [undefined](/sentinel/language/undefined).
+
+`append` called on any value other than a list or undefined will result
+in an immediate error.
+
+Examples:
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+### Why does this function return undefined?
+
+This function returns `undefined` to avoid unintended side effects of the function in the context
+of a [rule](/sentinel/language/rules).
+
+For example, if `append` returned the list value as a result, the following policy would depend
+on the order in which rules are referenced:
+
+```sentinel
+list = []
+a = rule { append(list, 1) contains 1 }
+b = rule { append(list, 2) contains 2 }
+
+// Scenario 1: list becomes [1, 2]
+main = rule { a and b }
+
+// Scenario 2: list becomes [2, 1]
+main = rule { b and a }
+```
+
+This sort of side effect behavior introduces complex and difficult to track logical errors in programs.
+As a result, functions like this return `undefined` and modify values in-place.
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/functions/delete.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/functions/delete.mdx
new file mode 100644
index 0000000000..0a6340876d
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/functions/delete.mdx
@@ -0,0 +1,26 @@
+---
+page_title: 'Sentinel Language - Builtin Function: delete'
+sidebar_title: 'delete'
+sidebar_current: docs-funcs-delete
+description: The built-in function `delete` deletes an element from a map.
+layout: docs
+---
+
+# Builtin Function: delete
+
+The built-in function `delete` deletes an element from a map.
+
+If the element to delete does not exist, the map is left unchanged.
+Calling `delete` on a value that is not a map or
+[undefined](/sentinel/language/undefined)
+is an immediate error. The result of `delete` is always `undefined`.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/functions/error.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/functions/error.mdx
new file mode 100644
index 0000000000..1d06dfc432
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/functions/error.mdx
@@ -0,0 +1,16 @@
+---
+page_title: 'Sentinel Language - Builtin Function: error'
+sidebar_title: 'error'
+sidebar_current: docs-funcs-error
+description: >-
+ The built-in function `error` is used to exit immediately with an error
+ message.
+layout: docs
+---
+
+# Builtin Function: error
+
+The built-in function `error` is used to exit immediately with an error message.
+
+Please see the [page on errors](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/functions/index.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/functions/index.mdx
new file mode 100644
index 0000000000..0d900b33d0
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/functions/index.mdx
@@ -0,0 +1,14 @@
+---
+page_title: Sentinel Language - Builtin Functions
+sidebar_title: Builtin Functions
+sidebar_current: docs-funcs
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Language: Builtin Functions
+
+Builtin functions are
+[functions](/sentinel/language/functions)
+that are available in all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in function.
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/functions/keys.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/functions/keys.mdx
new file mode 100644
index 0000000000..36d99bc2b7
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/functions/keys.mdx
@@ -0,0 +1,23 @@
+---
+page_title: 'Sentinel Language - Builtin Function: keys'
+sidebar_title: 'keys'
+sidebar_current: docs-funcs-keys
+description: The built-in function `keys` returns the keys of a map.
+layout: docs
+---
+
+# Builtin Function: keys
+
+The built-in function `keys` returns the keys of a map.
+
+`keys` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned keys are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+```
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/functions/length.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/functions/length.mdx
new file mode 100644
index 0000000000..efe184c0f1
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/functions/length.mdx
@@ -0,0 +1,24 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Length'
+sidebar_title: 'length'
+sidebar_current: docs-funcs-length
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Builtin Function: length
+
+The built-in function `length` returns the length of a collection or string.
+
+The length of a string is the number of bytes in the string. It is not
+necessarilly the number of characters. The length of a collection is the
+number of elements in that collection. The length of
+[undefined](/sentinel/language/undefined) is `undefined`.
+
+Examples:
+
+```sentinel
+length("foo") // 3
+length([1, 2, 3, 4]) // 4
+length({ "key": "value" }) // 1
+```
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/functions/print.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/functions/print.mdx
new file mode 100644
index 0000000000..9d714195db
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/functions/print.mdx
@@ -0,0 +1,14 @@
+---
+page_title: 'Sentinel Language - Builtin Function: print'
+sidebar_title: 'print'
+sidebar_current: docs-funcs-print
+description: The built-in function `print` is used for logging and debugging.
+layout: docs
+---
+
+# Builtin Function: print
+
+The built-in function `print` is used for logging and debugging.
+
+Please see the [page on logging](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/functions/range.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/functions/range.mdx
new file mode 100644
index 0000000000..0a9a07aa6f
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/functions/range.mdx
@@ -0,0 +1,50 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Range'
+sidebar_title: 'range'
+sidebar_current: docs-funcs-range
+description: The built-in function `range` returns a list of numbers in a range.
+layout: docs
+---
+
+# Builtin Function: range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it
+defaults to `1`.
+
+Negative steps are permitted and count down from `start` towards `end`, instead
+of up.
+
+The range ends when the next value given by the sum of the last value and `step`
+would exceed `end`, regardless of if it has been reached.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4] - starts at 1 instead of 0
+range(1, 5, 2) // [1,3] - 3+2 does not satisfy r[-1] < end
+range(0, -3, -1) // [0,-1,-2] - example of negative range
+```
+
+Impossible ranges, or ranges where `start` will never reach `end` given `step`,
+yield an empty list.
+
+```
+range(1, 1) // [] - already starting at 1
+range(0, 1, -1) // [] - negative step will never reach 1
+```
+
+Supplying an undefined value to any argument of `range` yields an undefined
+value back.
+
+A range with a zero step is a runtime error.
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/functions/values.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/functions/values.mdx
new file mode 100644
index 0000000000..95b448f6c7
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/functions/values.mdx
@@ -0,0 +1,23 @@
+---
+page_title: 'Sentinel Language - Builtin Function: values'
+sidebar_title: 'values'
+sidebar_current: docs-funcs-values
+description: The built-in function `values` returns the values of a map.
+layout: docs
+---
+
+# Builtin Function: values
+
+The built-in function `values` returns the values of a map.
+
+`values` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned values are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+values(data) // [3, 2]
+```
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/guides/index.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/guides/index.mdx
new file mode 100644
index 0000000000..8577828033
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/guides/index.mdx
@@ -0,0 +1,12 @@
+---
+page_title: Documentation
+sidebar_current: guides-index
+description: TODO Guides
+layout: docs
+---
+
+# Sentinel Guides
+
+Welcome to the Sentinel Guides!
+
+TODO
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/imports/decimal.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/imports/decimal.mdx
new file mode 100644
index 0000000000..45c65b26c1
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/imports/decimal.mdx
@@ -0,0 +1,318 @@
+---
+page_title: 'Import: decimal'
+sidebar_title: 'decimal'
+sidebar_current: docs-imports-decimal
+description: |-
+ The decimal import provides functions for operating on numbers represented
+ as decimals.
+layout: docs
+---
+
+# Import: decimal
+
+The decimal import provides functions for operating on numbers represented
+as decimals.
+
+Binary floating-point numbers provided by the `float` type are unable to
+fully represent numbers like `1.1` and `2.2`. The decimal import can
+represent these numbers exactly, and so should be used when exactness is
+required.
+
+Decimals are created through the `new` function, which takes any type that
+can represent a number. Arguments to the decimal operators can also take
+a value of any of these types. The full list is:
+
+* float
+* int
+* string
+* existing decimal value
+
+### decimal.infinity(sign)
+
+Creates an infinite-value decimal. Infinite-value decimals are always larger
+or smaller, depending on sign, than any non-infinite decimals.
+
+```sentinel
+decimal.infinity(1) // +Infinity
+decimal.infinity(-1) // -Infinity
+```
+
+Also available as `decimal.inf`.
+
+### decimal.nan
+
+A decimal representing a "not-a-number" value. NaNs are not equal to any
+other number, even themselves.
+
+```sentinel
+decimal.new(-1).sqrt() // NaN
+decimal.new(0).mul(decimal.inf(1)) // NaN
+decimal.nan.is(decimal.nan) // false
+```
+
+### decimal.is_infinite(d)
+
+Evaluates to true if the decimal is infinite.
+
+```sentinel
+decimal.is_infinite(100) // false
+decimal.is_infinite("Infinity") // true
+```
+
+Also available as `decimal.is_inf`, `decimal.isinf` and `decimal.isinfinite`.
+
+### decimal.is_nan(d)
+
+Evaluates to true if the decimal is not-a-number.
+
+```sentinel
+decimal.is_nan("NaN") // true
+```
+
+Also available as `decimal.isnan`.
+
+### decimal.new(v)
+
+Constructs a decimal from another value.
+
+```sentinel
+decimal.new("1.1") // Constructs a decimal from a string.
+decimal.new(2.2) // Constructs a decimal from a float.
+decimal.new(3) // Constructs a decimal from an integer.
+decimal.new("1.1234E+400") // Constructs a decimal from a string using E-notation.
+```
+
+## Type: number
+Numbers are represented internally by a sign, coefficient, and exponent.
+The value is calculated with the formula `sign * coefficient * 10^exponent`.
+Exponent is limited to 32 bits, and the coefficient is limited by the total
+precision.
+
+The maximum precision a number can represent is 100 digits. This includes
+both before and after the decimal place.
+
+### number.string
+
+A string representation of the decimal.
+
+```sentinel
+decimal.new(1.5).string // 1.5
+```
+
+### number.sign
+
+A number between `-1` and `+1` representing the sign of the decimal.
+
+```sentinel
+decimal.new(1.5).sign // 1
+```
+
+### number.coefficient
+
+The coefficient component of the decimal.
+
+```sentinel
+decimal.new(1.5).coefficient // 15
+```
+
+### number.exponent
+
+The exponent component of the decimal.
+
+```sentinel
+decimal.new(1.5).exponent // -1
+```
+
+### number.float
+
+A floating-point representation of the decimal.
+
+```sentinel
+decimal.new(1.5).float // 1.500000
+```
+
+### number.int
+
+An integer representation of the decimal. This always rounds down to the nearest integer.
+
+```sentinel
+decimal.new(1.5).int // 1
+```
+
+### number.is(v)
+
+Test for equality with another value.
+
+```sentinel
+decimal.new(1.5).is(1) // false
+```
+
+### number.is_not(v)
+
+Test for inequality with another value.
+
+```sentinel
+decimal.new(1.5).is_not(1) // true
+```
+
+### number.less_than(v)
+
+Test that the decimal is less than another value.
+Can also be called as `number.lt(v)`
+
+```sentinel
+decimal.new(1.5).less_than(1) // false
+```
+
+### number.less_than_or_equals(v)
+
+Test that the decimal is less than or equals to another value.
+Can also be called as `number.lte(v)`
+
+```sentinel
+decimal.new(1.5).less_than_or_equals(1) // false
+```
+
+### number.greater_than(v)
+
+Test that the decimal is greater than another value.
+Can also be called as `number.gt(v)`
+
+```sentinel
+decimal.new(1.5).greater_than(1) // true
+```
+
+### number.greater_than_or_equals(v)
+
+Test that the decimal is greater than or equals to another value.
+Can also be called as `number.gte(v)`
+
+```sentinel
+decimal.new(1.5).greater_than_or_equals(1) // true
+```
+
+### number.add(v)
+
+Add another number to the decimal.
+
+```sentinel
+decimal.new(1.5).add(1) // 2.5
+```
+
+### number.subtract(v)
+
+Subtract another number from the decimal.
+Can also be called as `number.sub(v)`
+
+```sentinel
+decimal.new(1.5).subtract(1) // 0.5
+```
+
+### number.multiply(v)
+
+Multiply the decimal by a number.
+Can also be called as `number.mul(v)`
+
+```sentinel
+decimal.new(1.5).multiply(2) // 3.0
+```
+
+### number.divide(v)
+
+Divide the decimal by a number.
+Can also be called as `number.div(v)`
+
+```sentinel
+decimal.new(3.0).divide(1.5) // 2
+```
+
+### number.modulo(v)
+
+Find the remainder after dividing the decimal by a number.
+Can also be called as `mod`, `remainder`, or `rem`.
+
+```sentinel
+decimal.new(1.5).modulo(1) // 0.5
+```
+
+### number.power(v)
+
+Raise the decimal to a power.
+Can also be called as `number.pow(v)`
+
+```sentinel
+decimal.new(1.5).power(2) // 2.25
+```
+
+### number.exp()
+
+The natural exponent of the decimal. This calculates `e^number`.
+
+```sentinel
+decimal.new(1.5).exp() // 4.4816...
+```
+
+### number.loge()
+
+The natural logarithm of the decimal.
+Can also be called as `number.ln()`
+
+```sentinel
+decimal.new(1.5).loge() // 0.4054...
+```
+
+### number.log10()
+
+The base-10 logarithm of the decimal.
+Can also be called as `number.log()`
+
+```sentinel
+decimal.new(1.5).log10() // 0.1760...
+```
+
+### number.square_root()
+
+The square root of the decimal.
+Can also be called as `number.sqrt()`
+
+```sentinel
+decimal.new(1.5).square_root() // 1.2247...
+```
+
+### number.ceiling()
+
+Round the decimal to the smallest integer greater or equal to itself.
+Can also be called as `number.ceil()`
+
+```sentinel
+decimal.new(1.5).ceiling() // 2
+```
+
+### number.floor()
+
+Round the decimal to the largest integer less than or equal to itself.
+
+```sentinel
+decimal.new(1.5).floor() // 1
+```
+
+### number.absolute()
+
+The absolute value of the decimal.
+Can also be called as `number.abs()`
+
+```sentinel
+decimal.new(1.5).absolute() // 1.5
+decimal.new(-1.5).absolute() // 1.5
+```
+
+### number.negate()
+
+The negated value of the decimal. A positive decimal will become negative,
+and a negative decimal will become positive.
+Can also be called as `number.neg()`
+
+```sentinel
+decimal.new(1.5).negate() // -1.5
+decimal.new(-1.5).negate() // 1.5
+```
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/imports/http.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/imports/http.mdx
new file mode 100644
index 0000000000..ecfc5d2b42
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/imports/http.mdx
@@ -0,0 +1,305 @@
+---
+page_title: 'Import: http'
+sidebar_title: 'http'
+sidebar_current: docs-imports-http
+description: The http import enables the use of HTTP-accessible data from outside the runtime in Sentinel policy rules.
+layout: docs
+---
+
+# Import: http
+
+The http import enables the use of HTTP-accessible data from outside
+the runtime in Sentinel policy rules.
+
+At the center of the import is the [`get()`](#clientgeturl_or_request) function, which issues a GET
+request and returns a `response` type:
+
+```sentinel
+import "http"
+
+resp = http.get("https://example.hashicorp.com")
+main = rule { resp.body contains "something" }
+```
+
+By default, any request response that is not a successful "200 OK" HTTP
+response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information and
+how to customize this behavior.
+
+For more advanced requests which require setting request headers, use a
+[`request` type](#type-request):
+
+```sentinel
+import "http"
+import "json"
+
+param token
+
+req = http.request("https://example.hashicorp.com").with_header("Authorization", "token "+token)
+resp = json.unmarshal(http.get(req).body)
+main = rule { resp["some_key"] is true }
+```
+
+The `with_header` function above returns the `request` object with the
+`Authorization` HTTP header set to value of the variable `some_value`. Many
+of the methods within the http import API are designed around this concept
+of method chaining, allowing for complex building of HTTP requests
+encapsulated in functions. The following is an idiomatic example further
+demonstrating this pattern:
+
+```sentinel
+import "http"
+import "json"
+
+param github_user
+param github_token
+
+client = func(req) {
+ return http.with_timeout(5).with_retries(3)
+}
+
+github_request = func(path) {
+ full_url = "https://api.github.com" + path
+ return http.request(full_url).
+ with_basic_auth(github_user, github_token).
+ with_header("Accept", "application/vnd.github.v3+json").
+ with_header("Custom", "foo"),
+}
+
+default_response = client.get(github_request("/example"))
+
+# Override the Custom header for this single request
+custom_header_response = json.unmarshal(
+ client.get(
+ github_request("/example").with_header("Custom", "bar"),
+ ),
+)
+
+# Override the timeout value for a known slower request
+slow_response = json.unmarshal(
+ client.with_timeout(15).get(github_request("/slow-endpoint")),
+)
+
+some_key_is_true = rule { json.unmarshal(default_response.body)["some_key"] is true }
+body_contains_text = rule { custom_header_response.body contains "something" }
+response_is_ok = rule { slow_response.status_code is 200 }
+
+main = rule { some_key_is_true and body_contains_text and response_is_ok }
+```
+
+### http.client
+
+Retrieve the base HTTP client with default settings. The returned client may be
+configured by calling configuration functions on it; all of these configuration
+functions on are also aliased as top-level functions in this namespace. See
+[`client`](#type-client) below.
+
+### http.request(url)
+
+Create a new [`request`](#type-request) to the specified `url` (string). A
+`request` type enables customization of headers and other concerns before then
+providing the constructed `request` to [`get()`](#clientgeturl_or_request).
+
+```sentinel
+req = http.request("example.hashicorp.com/foo").with_header("Foo", "bar")
+resp = http.get(req)
+```
+
+## Type: client
+
+All of the following configuration functions on the default client are also
+aliased as top-level functions in the import. This allows a short-hand (and
+idiomatic) way of configuring a new client without having to directly access
+`http.client` each time:
+
+```sentinel
+# The following two lines are semantically the same:
+resp = http.client.with_timeout(5).get(url)
+resp = http.with_timeout(5).get(url)
+```
+
+### client.get(url_or_request)
+
+Issue a GET request with a URL string or a `request` type. Returns a `response`
+type.
+
+When `url_or_request` is a string, a GET request is made with the URL
+specified by the string. To customize HTTP headers and other settings, pass
+a request. By default, a request has a timeout value of 10 seconds and 1
+retry. These settings can be adjusted with [`with_timeout()`](#clientwith_timeoutinteger) and
+[`with_retries()`](#clientwith_retriesinteger), respectively.
+
+If the response is one of the following redirect codes, `get()` follows the
+redirect, up to a maximum of 10 redirects:
+
+301 (Moved Permanently)
+302 (Found)
+303 (See Other)
+307 (Temporary Redirect)
+308 (Permanent Redirect)
+
+If the redirect limit is exceeded, the result is a policy error.
+
+By default, after any redirects are followed (up to the maximum), any request
+response that is not a successful "200 OK" response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information
+and how to customize this behavior.
+
+### client.accept_status_codes(list)
+
+Configures a client to not automatically cause a policy failure if
+the resulting response's status code is in `list`. Any status code received
+that is not present in this list will trigger a runtime error.
+
+By default, any request response that is not a successful "200 OK" response
+causes a policy error. This is for convenience, as the most common scenario
+by far is to receive a response and immediately signal a policy error if the
+status code is not 200. Use this scoping to specify a list of non-redirect
+HTTP codes that you wish to accept without error and evaluate the response
+yourself.
+
+Redirects are not considered final status codes in this context. That is,
+redirects are always followed up to the maximum allowed value (see [`get()`](#clientgeturl_or_request))
+and the final response code in the redirect chain is validated against
+`list`.
+
+```sentinel
+resp = http.accept_status_codes([200, 404]).get(url)
+main = rule { resp.status_code is 404 }
+```
+
+### client.accepted_status_codes
+
+Returns a list of the client's accepted status codes.
+
+```sentinel
+client = http.accept_status_codes([200, 404])
+main = rule { client.accepted_status_codes contains 404 }
+```
+
+### client.with_retries(integer)
+
+Sets the maximum number of retries, specified by `integer`, that should be
+attempted on an unsuccessful request. An unsuccessful request is considered
+to be any 5xx response except `501 (Not Implemented)` or a request timeout.
+By default, one retry is attempted.
+
+The maximum number of retries is 10, for a total of 11 request attempts.
+When a request exceeds the maximum number of retries without a response, the
+result is a policy error. Specifying a number larger than this will result
+in a runtime error.
+
+Between each retry, an exponentially increasing delay is observed, allowing
+time for the server to recover. This delay starts at 1 second for the first
+retry and increases each attempt thereafter. The maximum delay for a single
+attempt is 30 seconds.
+
+Retries can be disabled by specifying 0.
+
+### client.retries
+
+Returns the client's configured number of retries as an integer.
+
+### client.with_timeout(integer)
+
+Sets the request timeout to the number of seconds indicated by `integer`.
+
+Each individual request performed by the HTTP client will be limited by the
+given request timeout. This timeout includes connection time, any redirects,
+and reading the response body.
+
+A maximum of 30 seconds is allowed. Specifying a number larger than this
+will result in a runtime error.
+
+By default, requests will time out after 10 seconds.
+
+### client.timeout
+
+Returns the client's configured timeout in whole seconds as an integer.
+
+### client.without_certificate_verification()
+
+Skips verification of TLS certificates during secure HTTP operations,
+allowing for requests to `https://` endpoints which are secured with a TLS
+certificate signed by an untrusted certificate authority. Takes no arguments.
+
+By default, the HTTP client will trigger a runtime error if a TLS certificate
+presented by a server is from an untrusted certificate authority.
+
+Do not change this setting unless you are aware of the risks involved.
+Without verification, TLS accepts any certificate presented by the server
+and any host name in that certificate. In this mode, TLS is susceptible to
+man-in-the-middle attacks. This option should only be used for testing or in
+trusted networks with self-signed certificates.
+
+```sentinel
+http.without_certificate_verification().get(url)
+```
+
+### client.certificate_verification
+
+Returns true if the client requires TLS certificates to be verifiable.
+
+## Type: request
+
+### request.url
+
+Returns the request URL as a string.
+
+### request.headers
+
+Returns the configured request headers as a string-keyed map of strings.
+
+### request.with_header(key, value)
+
+Returns a `request` with the header `key` (string) set to `value` (string).
+
+Values are overridden on subsequent calls to the same `key`. To specify
+multiple values, use the existing value when setting the new one.
+
+```sentinel
+original = http.request("http://example.hashicorp.com").with_header("Foo", "bar")
+overridden = original.with_header("Foo", "baz")
+multi_value = original.with_header("Foo", original.headers["Foo"] + ",baz")
+
+main = rule {
+ original.headers["Foo"] is "bar" and
+ overridden.headers["Foo"] is "baz" and
+ multi_value.headers["Foo"] is "bar,baz"
+}
+```
+
+### request.with_basic_auth(username, password)
+
+Returns a `request` with the `Authorization` header set to use HTTP Basic
+Authentication with the provided username and password.
+
+Note that with HTTP Basic Authentication the provided username and password
+are encoded, but not encrypted.
+
+## Type: response
+
+### response.status_code
+
+The response status code as an integer, e.g. `200`.
+
+### response.headers
+
+The response headers as a map.
+
+### response.body
+
+The response body as a string. Callers should unmarshal the content to a useful
+representation as necessary based on the content type. For example, if the
+response is in JSON:
+
+```sentinel
+import "http"
+import "json"
+
+# This example endpoint returns {"some_key": true}
+req = http.request("https://example.hashicorp.com/some-json")
+
+resp = json.unmarshal(http.get(req).body)
+main = rule { keys(resp) contains "some_key" and resp["some_key"] is true }
+```
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/imports/index.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/imports/index.mdx
new file mode 100644
index 0000000000..e7d16b2ccb
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/imports/index.mdx
@@ -0,0 +1,19 @@
+---
+page_title: Sentinel Language - Standard Imports
+sidebar_title: Standard Imports
+sidebar_current: docs-imports
+description: >-
+ Standard Imports are the built-in imports that are available to all Sentinel
+ policies.
+layout: docs
+---
+
+# Standard Imports
+
+Standard Imports are the built-in [imports](/sentinel/language/imports)
+that are available to all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in import.
+
+Sentinel-embedded applications can choose to whitelist or blacklist certain
+standard imports. Please reference the documentation for the Sentinel-enabled
+application you're using to determine if all standard imports are available.
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/imports/json.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/imports/json.mdx
new file mode 100644
index 0000000000..45a75d30d5
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/imports/json.mdx
@@ -0,0 +1,32 @@
+---
+page_title: 'Import: json'
+sidebar_title: 'json'
+sidebar_current: docs-imports-json
+description: The json import enables a Sentinel policy to parse and access a JSON document.
+layout: docs
+---
+
+# Import: json
+
+The json import enables a Sentinel policy to parse and access a JSON
+document.
+
+### json.unmarshal(obj)
+
+Unmarshals the JSON object `obj` into a native Sentinel structure.
+
+All native JSON types can be represented perfectly as Sentinel native types.
+
+```sentinel
+// Typically the input for this would come from an external source.
+config = json.unmarshal("{ \"foo\": 42 }")
+config.foo // 42
+config.bar // undefined (as usual for accessing a non-existent map key)
+```
+
+### json.marshal(obj)
+
+Marshals the Sentinel object `obj` into a JSON object encoded as a string.
+
+Functions and `undefined` cannot be natively encoded as JSON. If values of
+either type are found in the structure, this will return undefined.
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/imports/runtime.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/imports/runtime.mdx
new file mode 100644
index 0000000000..5bfa156825
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/imports/runtime.mdx
@@ -0,0 +1,17 @@
+---
+page_title: 'Import: runtime'
+sidebar_title: 'runtime'
+sidebar_current: docs-imports-runtime
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: runtime
+
+The runtime import contains various information about the Sentinel runtime. It
+can be used to get information about the runtime as it's embedded in the CLI or
+a specific integration, such as validating a specific runtime version.
+
+### runtime.version
+
+Returns the version of Sentinel within this implementation.
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/imports/sockaddr.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/imports/sockaddr.mdx
new file mode 100644
index 0000000000..c200e69796
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/imports/sockaddr.mdx
@@ -0,0 +1,42 @@
+---
+page_title: 'Import: sockaddr'
+sidebar_title: 'sockaddr'
+sidebar_current: docs-imports-sockaddr
+description: The sockaddr import makes working with IP addresses easy.
+layout: docs
+---
+
+# Import: sockaddr
+
+The sockaddr import makes working with IP addresses easy.
+
+### sockaddr.is_contained(outer, inner)
+
+The is_contained function returns true if `inner` is an address that
+is contained within `outer`.
+
+```sentinel
+sockaddr.is_contained("192.168.0.0/24", "192.168.0.32") // true
+sockaddr.is_contained("192.168.0.0/24", "192.174.1.1") // false
+```
+
+### sockaddr.is_equal(addr1, addr2)
+
+The is_equal function returns true if addr1 is equal to addr2.
+
+```sentinel
+sockaddr.is_equal("192.168.12.24", "192.168.12.24") // true
+```
+
+### sockaddr.is_ipv4(addr)
+
+The is_ipv4 function returns true if addr is an IPv4 address.
+
+```sentinel
+sockaddr.is_ipv4("192.168.12.24") // true
+sockaddr.is_ipv4("2001:0db8:85a3:0000:0000:8a2e:0370:7334") // false
+```
+
+### sockaddr.is_ipv6(addr)
+
+The is_ipv6 function returns true if addr is an IPv6 address.
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/imports/strings.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/imports/strings.mdx
new file mode 100644
index 0000000000..bdacfc68a1
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/imports/strings.mdx
@@ -0,0 +1,92 @@
+---
+page_title: 'Import: strings'
+sidebar_title: 'strings'
+sidebar_current: docs-imports-strings
+description: The strings import exposes common string operations.
+layout: docs
+---
+
+# Import: strings
+
+The strings import exposes common string operations.
+
+### strings.has_prefix(s, prefix)
+
+Returns true if `s` starts with `prefix`.
+
+```sentinel
+strings.has_prefix("billing-id", "billing-") // true
+strings.has_prefix("bill-id", "billing-") // false
+```
+
+### strings.has_suffix(s, suffix)
+
+Returns true if `s` ends with `suffix`.
+
+```sentinel
+strings.has_suffix("billing-id", "id") // true
+strings.has_suffix("billing-name", "id") // false
+```
+
+### strings.join(a, sep)
+
+Joins a list with the string supplied by `sep`.
+
+Multi-dimensional lists are supported, and non-string primitive
+types will be converted to their string equivalents. Maps and
+other complex non-list types are not supported.
+
+```sentinel
+strings.join(["foo", "bar", "baz"], ".") // "foo.bar.baz"
+strings.join([["foo", "bar"], "baz"], ".") // "foo.bar.baz"
+strings.join(["a", 1, 1.01, true], ",") // "a,1,1.01,true"
+```
+
+### strings.trim_prefix(s, prefix)
+
+Trim the prefix from the string `s`. If the string doesn't have the
+prefix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_prefix("billing-id", "billing-") // "id"
+strings.trim_prefix("bill-id", "billing-") // "bill-id"
+```
+
+### strings.trim_suffix(s, suffix)
+
+Trim the suffix from the string `s`. If the string doesn't have the
+suffix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_suffix("billing-id", "-id") // "billing"
+strings.trim_suffix("bill-id", "-foo") // "bill-id"
+```
+
+### strings.to_lower(s)
+
+Lowercase the string s.
+
+```sentinel
+strings.to_lower("FoO") // "foo"
+```
+
+### strings.to_upper(s)
+
+Uppercase the string s.
+
+```sentinel
+strings.to_upper("FoO") // "FOO"
+```
+
+### strings.split(s, sep)
+
+Split the string 's' via a substring 'sep'. If 'sep' is not contained in
+'s', the return value will be a single-element list containing only 's'.
+As a special-case, if the input is already a list, it will be returned
+as-is. This allows calling the split function when not knowing if the input
+is already a list or not.
+
+```sentinel
+strings.split("foo,bar,baz", ",") // ["foo", "bar", "baz"]
+strings.split(["foo", "bar", "baz"], ",") // ["foo", "bar", "baz"]
+```
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/imports/time.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/imports/time.mdx
new file mode 100644
index 0000000000..bad99ce9e0
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/imports/time.mdx
@@ -0,0 +1,258 @@
+---
+page_title: 'Import: time'
+sidebar_title: 'time'
+sidebar_current: docs-imports-time
+description: The time import provides access to the execution time and time functions.
+layout: docs
+---
+
+# Import: time
+
+The time import provides access to the execution time and time functions.
+
+During the execution of a policy the time is fixed to the moment of
+execution. This gives the illusion that a policy instantly executes at a
+single moment of time.
+
+The import uses Coordinated Universal Time (UTC).
+
+As an example of this, if a policy accessed `time.now.second` multiple times,
+for each access the same value would be returned even if they are several seconds apart.
+This is the execution `timespace`, which is mapped to `time.now`.
+
+It is also possible to run functions on a custom time by using the
+`time.load()` function to create a new timespace from the specified value.
+See the documentation for available actions on timespaces.
+
+Times specified as the reference time or via the `time.load()` function can
+be specified in a `timeish` fashion. This is either one of:
+
+* The number of seconds since the Unix epoch (Thursday, 1 January 1970,
+ 00:00:00 UTC),
+* A timestamp matching the format outlined in RFC3339, either in the
+ format `2006-01-02T15:04:05+07:00`, which includes a timezone offset, or
+ `2006-01-02T15:04:05Z`, which indicates UTC.
+
+### time.now
+
+Create a `timespace` locked either to execution time or, if set, the
+reference time. In a given execution, this will always produce the same
+value.
+
+```sentinel
+time.now // {"day": 17, "hour": 23, "minute": 19, "month": 11 ... }
+```
+
+### time.load(timeish)
+
+Create a timespace using the given value. Please see the import
+documentation for valid values for "timeish".
+
+```sentinel
+time.load("2019-11-16T01:20:30Z") // {"day": 16, "hour": 1, "minute": 20, "month": 11 ... }
+```
+
+### time.nanosecond
+
+A nanosecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.nanosecond * 1) // 2019-11-16T01:20:30.000000001Z
+```
+
+### time.microsecond
+
+A microsecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.microsecond * 1) // 2019-11-16T01:20:30.000001Z
+```
+
+### time.millisecond
+
+A millisecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.millisecond * 1) // 2019-11-16T01:20:30.001Z
+```
+
+### time.second
+
+A second duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.second * 1) // 2019-11-16T01:20:31Z
+```
+
+### time.minute
+
+A minute for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.minute * 1) // 2019-11-16T01:21:30Z
+```
+
+### time.hour
+
+An hour duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+## Type: timespace
+
+### timespace.hour
+
+The hour from 0 to 23.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").hour // 1
+```
+
+### timespace.minute
+
+The minute from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").minute // 20
+```
+
+### timespace.second
+
+The second from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").second // 30
+```
+
+### timespace.day
+
+The day of the month, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").day // 16
+```
+
+### timespace.month
+
+The month of the year as an integer, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month // 11
+```
+
+### timespace.month_name
+
+The name of the month of the year, in English. Example: `January`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month_name // November
+```
+
+### timespace.weekday
+
+The weekday as an integer, where 0 is Sunday and 6 is Saturday.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday // 6
+```
+
+### timespace.weekday_name
+
+The name of the day of the week, in English. Example: `Sunday`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday_name // Saturday
+```
+
+### timespace.year
+
+The year as an integer.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").year // 2019
+```
+
+### timespace.unix
+
+The number of seconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix // 1573867230
+```
+
+### timespace.unix_nano
+
+The number of nanoseconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix_nano // 1573867230000000000
+```
+
+### timespace.zone
+
+The timezone offset, in seconds. This can be a negative number to indicate
+time west of UTC.
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone // -28800
+```
+
+### timespace.zone_string
+
+The timezone offset, in the format `+HH:MM` (example: `-08:00` for PST).
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone_string // -08:00
+```
+
+### timespace.before(timeish_or_timespace)
+
+Check if the time in the timespace is before the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").before("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.after(timeish_or_timespace)
+
+Check if the time in the timespace is after the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").after("2019-11-15T01:20:30Z") // true
+```
+
+### timespace.equal(timeish_or_timespace)
+
+Check if two times are equivalent
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").equal("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.add(duration)
+
+Add a duration in nanoseconds to the time in the timespace and return a new timespace. The
+duration can be negative. The duration should be specified as an integer
+multiplied with the correct unit.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+### timespace.sub(timeish_or_timespace)
+
+Subtract a time from the time in the timespace and return a duration in nanoseconds.
+
+```sentinel
+// Gives a duration of 3 hours.
+duration = time.load(
+ "2019-11-16T01:20:30Z",
+).sub("2019-11-16T04:20:30Z")
+
+// Returns 2019-11-16T01:20:30Z
+time.load(
+ "2019-11-16T04:20:30Z",
+).add(duration)
+```
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/imports/types.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/imports/types.mdx
new file mode 100644
index 0000000000..a25e9ef4c1
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/imports/types.mdx
@@ -0,0 +1,36 @@
+---
+page_title: 'Import: types'
+sidebar_title: 'types'
+sidebar_current: docs-imports-types
+description: The types import enables a Sentinel policy to parse an object's type.
+layout: docs
+---
+
+# Import: types
+
+The types import enables a Sentinel policy to parse an object's type.
+
+### types.type_of(obj)
+
+Returns the object's type as a string
+
+```sentinel
+import "types"
+
+// Typically the inputs would come from an external source.
+is_boolean = rule { types.type_of(true) is "bool" }
+is_string = rule { types.type_of("Hello!") is "string" }
+is_integer = rule { types.type_of(42) is "int" }
+is_float = rule { types.type_of(42.123) is "float" }
+is_null = rule { types.type_of(null) is "null" }
+is_undefined = rule { types.type_of(undefined) is "undefined" }
+
+main = rule {
+ is_boolean and
+ is_string and
+ is_integer and
+ is_float and
+ is_null and
+ is_undefined
+}
+```
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/imports/units.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/imports/units.mdx
new file mode 100644
index 0000000000..d1bff53736
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/imports/units.mdx
@@ -0,0 +1,40 @@
+---
+page_title: 'Import: units'
+sidebar_title: 'units'
+sidebar_current: docs-imports-units
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: units
+
+The units import provides access to quick calculations for various byte
+units.
+
+Note that this import uses base-2 terminology:
+
+* One kilobyte is 1024 bytes
+* One megabyte is 1024^2 = 1048576 bytes
+* One gigabyte is 1024^3 = 1073741824 bytes
+* One terabyte is 1024^4 = 1099511627776 bytes
+* One petabyte is 1024^5 = 1125899906842624 bytes
+
+### units.kilobyte
+
+The base-2 representation of a kilobyte (1024 bytes).
+
+### units.megabyte
+
+The base-2 representation of a megabyte (1048576 bytes).
+
+### units.gigabyte
+
+The base-2 representation of a gigabyte (1073741824 bytes).
+
+### units.terabyte
+
+The base-2 representation of a terabyte (1099511627776 bytes).
+
+### units.petabyte
+
+The base-2 representation of a petabyte (1125899906842624 bytes).
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/index.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/index.mdx
new file mode 100644
index 0000000000..4497b40b83
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/index.mdx
@@ -0,0 +1,31 @@
+---
+layout: 'docs'
+page_title: 'Documentation'
+sidebar_current: 'docs-home'
+description: |-
+ Sentinel is a language and framework for policy built to be embedded in existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+[introduction](/sentinel/intro) to understand what Sentinel is, how it
+compares to other software, and more.
+
+
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/language/arithmetic.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/language/arithmetic.mdx
new file mode 100644
index 0000000000..3a341db72e
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/language/arithmetic.mdx
@@ -0,0 +1,68 @@
+---
+page_title: Sentinel Language - Arithmetic
+sidebar_title: Arithmetic
+sidebar_current: docs-language-arith
+description: >-
+ Sentinel supports arithmetic operators for integers and floats. Sentinel
+ supports sum, difference, product, quotient, and remainder.
+layout: docs
+---
+
+# Language: Arithmetic
+
+Sentinel supports arithmetic operators for integers and floats. Sentinel
+supports sum, difference, product, quotient, and remainder.
+
+```plaintext
++ sum
+* difference
+* product
+/ quotient
+% remainder
+```
+
+These operators work in a typical infix style:
+
+```sentinel
+4 + 8 // 12
+8 * 2 // 16
+8 / 4 // 2
+8 / 5 // 1
+8 % 5 // 3
+```
+
+## Order of Operations
+
+Arithmetic follows a standard mathematical order of operations.
+Grouping with parentheses can be used to affect ordering.
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+(4 + 5) * 2 // 18
+```
+
+A full table of operator precendence can be found on the
+[boolean expressions page](/sentinel/language/boolexpr). This
+shows how arithmetic operators relate to other operators.
+
+## Integer Division
+
+Integer division with a remainder rounds down to the nearest integer.
+Example: `8 / 3 is 2`.
+
+If the divisor is zero, an error occurs.
+
+## Mixed Numeric Operations
+
+Mixed numeric operations between integer and floating-point values are
+permitted. The result is a floating-point operation with the integer converted
+to a floating-point value for purposes of calculation.
+
+```sentinel
+import "types"
+
+a = 1.1 + 1 // 2.1
+types.type_of(a) // "float"
+```
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/language/boolexpr.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/language/boolexpr.mdx
new file mode 100644
index 0000000000..86432c4153
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/language/boolexpr.mdx
@@ -0,0 +1,193 @@
+---
+page_title: Sentinel Language - Boolean Expressions
+sidebar_title: Boolean Expressions
+sidebar_current: docs-language-boolexpr
+description: >-
+ Boolean expressions are expressions that evaluate to a boolean value from the
+ result of a combination of one or more comparisons and logical operators.
+layout: docs
+---
+
+# Language: Boolean Expressions
+
+Boolean expressions are expressions that evaluate to a boolean value
+from the result of a combination of one or more comparisons and logical
+operators. Boolean expressions form the basis of policy since policy can be broken
+down to a set of logical decisions that turn into true or false.
+
+A single boolean expression is the body of a [rule](/sentinel/language/rules).
+Additionally, boolean expressions are used with [conditionals](/sentinel/language/conditionals),
+and more.
+
+## Order of Operations
+
+The precedence of operators is shown below. Operators with higher
+precedence values (larger numbers) are evaluated first. Operators with
+the same precedence value are evaluated left-to-right.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Examples of this precedence are shown below:
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+```
+
+## Logical Operators
+
+Logical operators are used to change or combine logical expressions.
+There are three binary operators `and`, `or`, and `xor`. There is
+a single unary operator `not` (which can also be specified as `!`).
+
+The binary operators require two boolean operands and the unary
+operator requires a single boolean operand. In both case, the operands
+are values or expressions that are boolean types.
+
+Below is a basic explanation of the logical operators directly from
+the specification:
+
+```sentinel
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+Using parentheses to group boolean expressions, you can combine
+boolean expressions to become more complex or affect ordering:
+
+```sentinel
+(p and q) or r
+p and (q or r)
+```
+
+## Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+For any comparison, the two operands must be equivalent types. The one
+exception are integers and floats. When an integer is compared with
+a float, the integer is promoted to a floating point value.
+
+The available comparison operators are:
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+The behavior of `is` with `==` and `is not` with `!=` is identical.
+
+Example comparisons:
+
+```sentinel
+name is "Mitchell"
+idnumber > 42
+idnumber <= 12
+```
+
+Using the logical operators, these can be combined:
+
+```sentinel
+name is "Mitchell" and idnumber > 42
+```
+
+The language specification provides more detail on exactly
+[how comparison behaves](/sentinel/language/spec#comparison-operators).
+
+## Set Operators
+
+The set operators `contains` and `in` test for inclusion in a collection
+(a list or map).
+
+Set operators may 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.
+
+`contains` tests if the left-hand collection contains the right-hand value.
+`in` tests if the right-hand collection contains the left-hand value. For
+maps, "contains" looks at the keys, not the values.
+
+Examples:
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+```
+
+## Matches Operator
+
+The `matches` operator tests if a string matches a regular expression.
+The `matches` operator can be negated by prefixing the operator with
+`not`, such as `not matches`.
+
+The left-hand value is the string to test. The right-hand value is
+a string value representing a regular expression. The syntax of the regular
+expression is the same general syntax used by Python, Ruby, and other languages.
+The precise syntax accepted is the syntax accepted by RE2.
+
+Regular expressions are not anchored by default; any anchoring must be
+explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+## Any, All Expressions
+
+`any` and `all` expressions allow testing that any or all elements of a
+collection match some boolean expression. The result of an `any` and `all`
+expression is a boolean.
+
+`any` returns the boolean value `true` if any value in the collection expression
+results in the body expression evaluating to `true`. If the body expression
+evalutes to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression
+result in the body expression evaluating to `true`. If any value in the
+collection expression result in the body expression evaluating to `false`,
+the `all` expression returns `false`.
+
+For empty collections, `any` returns `false` and `all` returns `true`.
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" }
+any group.tasks as t { t.driver is "vmware" }
+```
+
+Since `any` and `all` expressions are themselves boolean expressions, they
+can be combined with other operators:
+
+```sentinel
+any ["a", "b"] as char { char is "a" } or
+other_value is "another"
+```
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/language/collection-operations.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/language/collection-operations.mdx
new file mode 100644
index 0000000000..32d10c5c5e
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/language/collection-operations.mdx
@@ -0,0 +1,44 @@
+---
+page_title: Sentinel Language - Collection Operations
+sidebar_title: Collection Operations
+sidebar_current: docs-language-collection-operations
+description: |-
+ Operations for interacting with collections (list, map).
+layout: docs
+---
+
+# Language: Collection Operations
+
+Collection operations are expressions that are performed on a list or map to
+return a variation of the initial data.
+
+At the moment, `filter` is the only collection operation available.
+
+## Filter Expression
+
+`filter` is a [quantifier
+expression](/sentinel/language/spec#quantifier-expressions-any-all-filter) that
+returns a subset of the provided collection. Only elements whose filter body
+returns true will be returned. If any of the elements filter body returns
+undefined, the final result will be undefined.
+
+Filter uses the same syntax as the `any` and `all` [boolean
+expressions](/sentinel/language/boolexpr#any-all-expressions):
+
+```sentinel
+filter list as value { condition } // Single-iterator, list
+filter list as idx, value { condition } // Double-iterator, list
+
+filter map as key { condition } // Single-iterator, map
+filter map as key, value { condition } // Double-iterator, map
+```
+
+Examples:
+
+```sentinel
+l = [1, 1, 2, 3, 5, 8]
+evens = filter l as v { v % 2 is 0 } // [2, 8]
+
+m = { "a": "foo", "b": "bar" }
+matched_foo = filter m as _, v { v is "foo" } // { "a": "foo" }
+```
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/language/conditionals.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/language/conditionals.mdx
new file mode 100644
index 0000000000..af85dafcc6
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/language/conditionals.mdx
@@ -0,0 +1,162 @@
+---
+page_title: Sentinel Language - Conditionals
+sidebar_title: Conditionals
+sidebar_current: docs-language-conditional
+description: |-
+ Conditional statements allow your policy to behave differently depending on a condition.
+layout: docs
+---
+
+# Language: Conditionals
+
+Conditional statements allow your policy to behave differently depending
+on a condition.
+
+Conditional statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## If Statements
+
+`if` statements only execute their bodies if a condition is met.
+The syntax of an `if` statement is:
+
+```sentinel
+if condition {
+ // ... this is executed if condition is true
+}
+```
+
+The `condition` must result in a boolean, such as by calling a function
+or evaluating a [boolean expression](/sentinel/language/boolexpr). If
+the `condition` is `true`, the body (within the `{}`) is executed. Otherwise,
+the body is skipped.
+
+Examples:
+
+```sentinel
+// This would execute the body
+value = 12
+if value is 18 {
+ print("condition met")
+}
+
+// Direct boolean values can be used
+value = true
+if value {
+ print("condition met")
+}
+
+// This would not execute the body since the boolean expression will
+// result in undefined.
+value = {}
+if value["key"] > 12 {
+ print("condition met")
+}
+```
+
+### Else, Else If
+
+An `else` clause can be given to an `if` statement to execute a body
+in the case the condition is _not met_. By putting another `if` statement
+directly after the `else`, multiple conditions can be tested for.
+The syntax is:
+
+```sentinel
+if condition {
+ // ...
+} else {
+ // ...
+}
+
+if condition {
+ // ...
+} else if other_condition {
+ // ...
+} else {
+ // ...
+}
+```
+
+### Scoping
+
+The body of an `if` statement does not create a new
+[scope](/sentinel/language/scope). Any variables assigned within the body
+of an if statement will modify the scope that the `if` statement itself
+is in.
+
+Example:
+
+```sentinel
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+```sentinel
+a = 18
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+## Case Statements
+
+`case` statements are a selection control mechanism that execute a clause based
+on matching expressions. It is worth noting that the expression for `case` is
+optional. When no expression is provided, it defaults the expression to `true`.
+Additionally, the order of clauses is important, as they are evaluated from top
+to bottom, executing the first match.
+The syntax of a case statement is:
+
+```sentinel
+case expression {
+ when clause_expression:
+ // executed when clause_expression and expression are equal
+ else:
+ // executed if no clause matches expression
+}
+```
+
+### When Clause
+
+Any clause that has an expression for comparison must use the `when` keyword.
+It accepts a list of expressions, seperated by a `,`.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+}
+```
+
+```sentinel
+case {
+ when x > 40:
+ return true
+}
+```
+
+### Else Clause
+
+The `else` keyword allows for capturing any expressions that have no matching
+`when` clause.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+ else:
+ return false
+}
+```
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/language/functions.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/language/functions.mdx
new file mode 100644
index 0000000000..c55dfe4d81
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/language/functions.mdx
@@ -0,0 +1,174 @@
+---
+page_title: Sentinel Language - Functions
+sidebar_title: Functions
+sidebar_current: docs-language-functions
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Functions
+
+Functions allow you to create reusable code to perform computations.
+
+Below is a trivial example function that doubles a number and shows
+the main rule using the function:
+
+```sentinel
+double = func(x) {
+ return x * 2
+}
+
+main = rule { double(12) is 24 }
+```
+
+Functions may contain any expressions,
+[conditionals](/sentinel/language/conditionals),
+and [loops](/sentinel/language/loops). They must return a value
+at the end of their execution. If no reasonable return value exists,
+the function should return [undefined](/sentinel/language/undefined).
+
+## Creating a Function
+
+A function is created by assigning a variable to a `func`.
+
+A function can have zero or more parameters. These parameters are specified
+within parentheses `()` separated by commas. If a function has no parameters,
+the parentheses must still be present.
+
+A function must terminate with a `return` statement to return a value back to
+the caller. Only a single value can be returned. The type of a return can
+vary between return statements.
+
+Example:
+
+```sentinel
+add1 = func(x) { return x + 1 }
+```
+
+This example creates a function that adds 1 to the parameter `x`. It is
+assigned to the variable `add1`.
+
+## Calling a Function
+
+Functions are called by accessing their name followed by parentheses with
+a list of comma-separated values for the parameters. If the function takes no
+parameters, an empty set of parentheses must still be used to denote a
+function call.
+
+To call the function in the example above:
+
+```sentinel
+x = 1
+y = add1(x)
+```
+
+## Scoping
+
+The body of a `func` creates a new [scope](/sentinel/language/scope).
+
+The parent scope is the scope in which the function is created. This allows
+for the creation of functions within functions that can access their outer
+scopes.
+
+Example:
+
+```sentinel
+f = func() {
+ a = 42
+ print(a) // 42
+ return undefined
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+f = func() {
+ a = 42
+ return undefined
+}
+
+print(a) // 18
+```
+
+And below is an example of creating a function in a function which uses
+outer values:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double()
+}
+
+print(f()) // 84
+```
+
+A more complex example below shows how scoping works when passing
+functions around as arguments or results:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double
+}
+
+double = f()
+print(double()) // 84
+```
+
+## Pass By Value
+
+The parameters to a function are passed by value, but not deep copied.
+This means that elements of collections can still be modified but the
+root value cannot be changed.
+
+Example:
+
+```sentinel
+f = func(x) {
+ x = "value"
+ return x
+}
+
+x = "outside"
+f(x)
+print(x) // "outside"
+```
+
+```sentinel
+f = func(x) {
+ append(x, "value")
+ return x
+}
+
+x = []
+f(x)
+print(x) // ["value"]
+```
+
+## Recursion
+
+A function is allowed to call other functions, including itself.
+This can be used to implement recursion. For example, the fibonacci sequence
+is implemented below:
+
+```sentinel
+fib = func(x) {
+ if x <= 0 {
+ return undefined
+ }
+
+ if x == 1 {
+ return 1
+ } else {
+ return x + fib(x - 1)
+ }
+}
+```
+
+Note that this example also shows using
+[undefined](/sentinel/language/undefined)
+as a return value in cases where a function has undefined behavior.
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/language/imports.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/language/imports.mdx
new file mode 100644
index 0000000000..945bb95930
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/language/imports.mdx
@@ -0,0 +1,117 @@
+---
+page_title: Sentinel Language - Imports
+sidebar_title: Imports
+sidebar_current: docs-language-imports
+description: >-
+ Imports enable a Sentinel policy to access reusable libraries and external
+ data and functions.
+layout: docs
+---
+
+# Language: Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external
+data and functions. The exact list of available imports is dependent on the
+host system. Host systems may also make the available imports configurable
+via plugins.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that
+the import `calendar` allows access to engineer calendars. This import
+only exists for the purpose of this example and at the time of writing is
+not a real available import.
+
+```sentinel
+import "calendar"
+
+// Get the calendar for Bob for today
+bob_calendar = calendar.for("bob").today
+
+// Allow this policy to pass if Bob is not on vacation.
+main = rule { not bob_calendar.has_event("vacation") }
+```
+
+This example shows an import being used to deny a behavior if Bob is on
+vacation. Hopefully real policies do not depend on a single person being
+in the office, but the example shows the power of imports.
+
+In addition to consuming imports, you can also
+[extend Sentinel by writing custom imports](/sentinel/extending)
+This allows you to add any arbitrary behavior to your Sentinel-protected
+systems.
+
+## Importing
+
+Whether accessing a built-in library or an external plugin, the syntax
+for an import is the same:
+
+```sentinel
+import "name"
+```
+
+This adds the import with the name `name`. It can be referenced using
+the identifier `name`. For example, if the import above exposed first
+and last name data, you could access it via `name.first` or `name.last`.
+
+You can shadow imports by assigning a variable. In the example below,
+the import `name` becomes unavailable within the function because it is
+shadowed by a variable of the same name.
+
+Shadowing an import is not the same as overwriting a variable. Imports
+are not part of the [scope](/sentinel/language/scope), meaning that
+the shadow only exists for the scope it is created within. For everything
+else, the import works even after it is shadowed. The example below shows that
+as well.
+
+```sentinel
+import "name"
+
+f = func() {
+ name = "jane"
+ return name
+}
+
+print(f()) // "jane"
+print(name.first) // "bob"
+```
+
+## Aliases
+
+An import can be aliased to another name using the syntax below:
+
+```sentinel
+import "name" as other
+```
+
+This would make the import "name" available as `other`.
+
+An import may only be imported once, regardless of aliases. The following
+would be **invalid** and would result in an error:
+
+```sentinel
+import "name" as one
+import "name" as two
+```
+
+## Accessing Import Data
+
+Imports are accessed with dot-separated identifiers, such as `name.first` or
+`name.stage.first`. These are called _selector expressions_. An import may
+return nested data that further can be accessed via selector expressions.
+
+In the first example on this page with the "calendar" import, we see this:
+
+```sentinel
+import "calendar"
+
+a = calendar.for("bob") // selector expression calendar.for
+b = a.today // selector expression on the result
+c = b.vacation // accessing further nested data
+```
+
+The exact structure of an import is dependent on the import. Please reference
+the documentation for an import to learn more.
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/language/index.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/language/index.mdx
new file mode 100644
index 0000000000..fe23ee1144
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/language/index.mdx
@@ -0,0 +1,100 @@
+---
+page_title: Sentinel Language
+sidebar_title: Language
+sidebar_current: docs-language-basics
+description: |-
+ Sentinel policies are written using the Sentinel language. This language
+ is easy to learn and easy to write. You can learn the Sentinel language
+ and be productive within an hour. Learning Sentinel doesn't require any
+ formal programming experience.
+layout: docs
+---
+
+# Sentinel Language
+
+Sentinel policies are written using the Sentinel language. This language
+is easy to learn and easy to write. You can learn the Sentinel language
+and be productive within an hour. Learning Sentinel doesn't require any
+formal programming experience.
+
+This language guide will contain many details that are not necessary to
+be productive with Sentinel or are targeted to those who are looking for
+exact information about the language (such as what types of line endings are
+allowed). You can safely ignore these sentences.
+
+You may also view the
+[official language specification](/sentinel/language/spec)
+This is a specific and detailed document on the syntax and behavior of
+the language primarily intended for implementation creators and to disambiguate
+the language.
+
+## Simplest Example
+
+The example below is about the simplest practical example of Sentinel.
+It is reasonable to imagine this as a realistic policy. This shows that
+in most cases, Sentinel will be extremely simple:
+
+```sentinel
+main = rule { request.method is "GET" and request.headers contains "X-Key" }
+```
+
+## Files
+
+Sentinel policies are single files that end in the `.sentinel` file extension.
+There is currently no built-in mechanism to Sentinel for merging multiple
+files. This is purposefully done to make Sentinel policies easy to submit
+to systems that support Sentinel policies.
+
+Sentinel policy files must be UTF-8 encoded and can end in both
+Unix (LF) or Windows (CRLF) line breaks. When running the
+[auto-formatter](#),
+line endings will always use Unix line breaks.
+
+## Ordering
+
+Sentinel policies are executed top-down. For example:
+
+```sentinel
+a = 1 // a = 1 here
+b = a + 1 // b = 2 here
+a = 3 // a = 3, b = 2
+```
+
+In this example, the value of `a` and `b` is shown at each line. Since Sentinel
+executes values top-down, the final value of `a` is 3 and `b` is 2. `b` _does
+not_ become 4.
+
+## Main
+
+Sentinel expects there to be a `main` [rule](/sentinel/language/rules).
+The value of this rule is the result of the entire policy.
+
+If the result of `main` is true, the policy passes. If the value is anything
+else (false or a non-boolean value), the policy fails. The exact meaning
+of what happens a policy passes or fails is dependent on the host system.
+
+## More Complex Example
+
+The simple example above is a full working example. In our experience with
+Sentinel, many policies can be representing using this simple form. However,
+to show more features of the language, a more complex example is shown below.
+This example is also a realistic example of what Sentinel may be used for.
+
+```sentinel
+import "units"
+
+memory = func(job) {
+ result = 0
+ for job.groups as g {
+ for g.tasks as t {
+ result += t.resources.memory else 0
+ }
+ }
+
+ return result
+}
+
+main = rule {
+ memory(job) < 1 * units.gigabyte
+}
+```
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/language/lists.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/language/lists.mdx
new file mode 100644
index 0000000000..0b901e0fef
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/language/lists.mdx
@@ -0,0 +1,131 @@
+---
+page_title: Sentinel Language - Lists
+sidebar_title: Lists
+sidebar_current: docs-language-lists
+description: Lists are a collection of zero or more values.
+layout: docs
+---
+
+# Language: Lists
+
+Lists are a collection of zero or more values.
+
+Lists can be created using by wrapping values in `[]` and separating them
+by commas. An optional trailing comma is allowed. List elements can be
+differing types. Examples:
+
+```sentinel
+[] // An empty list
+["foo"] // Single element list
+["foo", 1, 2, true] // Multi element list with different types
+["foo", [1, 2]] // List containing another list
+```
+
+A list can be [sliced](/sentinel/language/slices) to create sublists.
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for value inclusion in a list.
+
+## Accessing Elements
+
+List elements can be accessed with the syntax `name[index]` where
+`index` is zero-indexed.
+
+A negative index accesses the list in reverse. It is the same as
+reversing a list and then using a positive index. Similar to a positive
+index, it is bounded by the length of the list as a negative value.
+
+Accessing beyond the length of the list results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+list = ["foo", 1, true, [1, 2]]
+
+list[0] // "foo"
+list[2] // true
+list[4] // undefined
+list[-2] // true
+list[-4] // "foo"
+list[-5] // undefined
+list[3][1] // 2
+```
+
+## List Append
+
+Values can be appended to a list using the built-in
+[append](/sentinel/functions/append) function.
+
+This modifies the list in-place and returns
+[undefined](/sentinel/language/undefined). For more information on
+why `append` behaves this way, please read the full documentation for the
+[append](/sentinel/functions/append) function.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append([1,2], "foo") // [1, 2, "foo"]
+append([1,2], [3]) // [1, 2, [3]]
+append(1, 3) // error()
+```
+
+## List Concatenation
+
+Two lists can be concatenated using the `+` operator or the shorthand
+`+=` assignment operator. For the `+` operator, a new list is returned.
+For `+=`, the left-hand list is modified in place.
+
+Examples:
+
+```sentinel
+[1] + [2] // [1, 2]
+[1] + [[1]] // [1, [1]]
+[1] + 1 // error
+
+a = [1]
+a += [2] // a = [1, 2]
+a += 3 // error
+```
+
+## List Length
+
+The length of a list can be retrieved using the
+[length](/sentinel/functions/length) function.
+
+Examples:
+
+```sentinel
+length([]) // 0
+length(["foo"]) // 1
+```
+
+## Removing Items From a List
+
+You can use a combination of [list concatenation](#list-concatenation) and
+[slicing](/sentinel/language/slices) to remove elements from a list:
+
+```sentinel
+a = [0, 1, 2]
+a = foo[:1] + foo[2:] // [0, 2]
+```
+
+## List Comparison
+
+Lists may be [compared](/sentinel/language/boolexpr#comparison-operators)
+for equality. Lists are equal if they are of equal length and their
+corresponding elements are comparable and equal. Lists with the same elements
+but in a different order are not equal.
+
+```sentinel
+[1, 2] is [1, 2] // true
+[1, 2] is [2, 1] // false
+["a"] is ["a", "b"] // false
+["a", ["b", "c"]] is ["a", ["b", "c"]] // true
+```
+
+List comparison speed is O(N), meaning that the speed of the comparison is
+_linearly_ proportional to the number of elements in the list. The more
+elements, the more iterations that are necessary to verify equality.
+
+-> The N-value quoted above should account for the sum of the elements of _all_
+lists in the subjects of comparison, as list comparison will recurse into these
+lists to check for equality.
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/language/logging-errors.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/language/logging-errors.mdx
new file mode 100644
index 0000000000..32427697d9
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/language/logging-errors.mdx
@@ -0,0 +1,53 @@
+---
+page_title: Sentinel Language - Logging and Errors
+sidebar_title: Logging and Errors
+sidebar_current: docs-language-log
+description: The built-in functions `print` and `error` can be used for logging and errors.
+layout: docs
+---
+
+# Language: Logging and Errors
+
+The built-in functions `print` and `error` can be used for logging
+and errors. Logging is helpful to understand how a policy is executing
+in development. And errors are useful to halting execution immediately in
+certain scenarios.
+
+## Print
+
+The `print` function is used to output debug information. The exact location
+where this print statements go is dependent on the host application and
+the Sentinel runtime itself.
+
+The `print` function takes one or more Sentinel values as arguments.
+Each value is formatted for human-friendly output and space-separated.
+Example:
+
+```sentinel
+value = 42
+print("the value is", value) // the value is 42
+
+map = { "foo": false }
+print(map) // { "foo": false }
+
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // false
+```
+
+## Errors
+
+Certain actions in Sentinel, such as accessing an undefined variable,
+attempting to add two incompatible types, etc. result in _runtime errors_.
+These errors halt the execution of a policy immediately. The pass/fail result
+of a policy is considered fail.
+
+Runtime errors can be manually triggered using the `error` function.
+The `error` function behaves exactly like the `print` function except that
+execution is halted immediately.
+
+This should only be used in scenarios where the policy _must fail_ due to
+some unexpected or invalid condition. The line between `error` and
+[undefined](/sentinel/language/undefined) can sometimes be unclear. Undefined
+is recommended when a policy can conceivably recover or safely ignore the
+undefined behavior. An error should be used when the policy should fail and
+notify someone regardless.
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/language/loops.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/language/loops.mdx
new file mode 100644
index 0000000000..2a04c6957e
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/language/loops.mdx
@@ -0,0 +1,93 @@
+---
+page_title: Sentinel Language - Loops
+sidebar_title: Loops
+sidebar_current: docs-language-loops
+description: >-
+ Loop statements allow you to execute a body of code for each element in a
+ collection or for some fixed number of times.
+layout: docs
+---
+
+# Language: Loops
+
+Loop statements allow you to execute a body of code for each element in
+a collection or for some fixed number of times.
+
+Loop statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## For Statements
+
+`for` statements allow repeated execution of a block for each
+element in a collection.
+
+Example:
+
+```sentinel
+// A basic sum
+count = 0
+for [1, 2, 3] as num {
+ count += num
+}
+```
+
+The syntax is `for COLLECTION as value`. This will iterate over the
+collection, assigning each element to `value`. In the example above,
+each element is assigned to `num`. The body is executed for each element.
+In the example above, the body adds `num` to the `count` variable. This
+creates a basic sum of all values in the collection.
+
+For a map, the assigned element is the key in the map. In the example
+below, `name` would be assigned map keys.
+
+```sentinel
+list = []
+for { "a": 1, "b": 2 } as name {
+ append(list, name)
+}
+
+print(list) // ["a" "b"]
+```
+
+An alternate syntax is `for COLLECTION as key, value`. This will assign
+both the key and value to a variable. For a list, the key is the element
+index. For a map, it is the key and value is assigned the element value.
+Example:
+
+```sentinel
+count = 0
+for { "a": 1, "b": 2 } as name, num {
+ count += num
+}
+
+print(count) // 3
+```
+
+## Scoping
+
+The body of a `for` statement creates a new
+[scope](/sentinel/language/scope). If a variable is assigned within
+the body of a for statement that isn't assigned in a parent scope,
+that variable will only exist for the duration of the body execution.
+
+Example:
+
+```sentinel
+for list as value {
+ a = 42
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+for list as value {
+ a = 42
+}
+
+print(a) // 18
+```
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/language/maps.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/language/maps.mdx
new file mode 100644
index 0000000000..8b6c71abeb
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/language/maps.mdx
@@ -0,0 +1,100 @@
+---
+page_title: Sentinel Language - Maps
+sidebar_title: Maps
+sidebar_current: docs-language-maps
+description: >-
+ Maps are a collection of zero or more key/value pairs. These are useful for
+ storing values that are looked up by a unique key.
+layout: docs
+---
+
+# Language: Maps
+
+Maps are a collection of zero or more key/value pairs. These are useful
+for storing values that are looked up by a unique key.
+
+Maps can be created using `{}` and specifying key/value pairs. Keys and
+values can be differing types. An optional trailing comma is allowed.
+Examples:
+
+```sentinel
+// Empty map
+{}
+
+// Map with a single value on one line
+{ "key": "value" }
+
+// Map with multiple values with differing types on multiple lines
+{
+ "key": "value",
+ 42: true,
+}
+```
+
+Maps are **unordered**. When
+[looping](/sentinel/language/loops)
+over a map, the key/value pairs can be returned in any order.
+
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for key inclusion in a map.
+
+## Accessing Elements
+
+Map elements can be accessed with the syntax `name[key]`. This looks up
+a value by a key.
+
+Accessing a key that doesn't exist results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+map = { "key": "value", 42: true, }
+
+map["key"] // "value"
+map[42] // true
+map[0] // undefined
+```
+
+## Modifying or Adding Elements
+
+Elements can be added or modified in a map by assigning to `name[key]`.
+If the key doesn't exist, the value is added. If the key already exists,
+the value is overridden.
+
+```sentinel
+map = { "key": "value" }
+
+map[42] = true // Add a new key/value
+map["key"] = 12 // Modify the value of "key"
+```
+
+## Deleting Elements
+
+An element can be deleted from a map using the
+[delete](/sentinel/functions/delete) function.
+
+Examples:
+
+```sentinel
+map = { "key": "value" }
+delete(map, "key") // map is now empty
+delete(map, "other") // no effect for non-existent key
+```
+
+## Keys and Values
+
+The keys and values of a map can be retrieved as
+[lists](/sentinel/language/lists) using the
+[keys](/sentinel/functions/keys) and
+[values](/sentinel/functions/values) functions.
+
+Because maps are unordered, the keys and values are returned in an
+unspecified order. It should not be assumed that keys and values will be
+returned in a consistent order.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/language/parameters.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/language/parameters.mdx
new file mode 100644
index 0000000000..b5b47d6bf8
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/language/parameters.mdx
@@ -0,0 +1,136 @@
+---
+page_title: Sentinel Language - Parameters
+sidebar_title: Parameters
+sidebar_current: docs-language-parameters
+description: >-
+ Parameteres allow a Sentinel policy to describe variables that are expected
+ to be supplied by the calling application, in addition to any default value.
+layout: docs
+---
+
+# Language: Parameters
+
+Sentinel allows a policy author to supply parameters to help facilitate policy
+reuse and ensure sensitive values do not need to be hard-coded in a policy.
+
+Parameters are supplied by using the `param` keyword, followed by an identifier.
+A default value can also be supplied by using the `default` keyword.
+
+```sentinel
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+```
+
+Once declared, parameters can be used like any other variable, including being
+re-assigned.
+
+```sentinel
+param foo default 1 // 1 (default)
+
+foo = foo + 1 // 2
+```
+
+## Variable Descriptions
+
+You can supply a description to a parameter by adding a comment at the top of
+it. This value can be communicated to a specific implementation of Sentinel to
+provide information about what the parameter is for during configuration.
+
+```sentinel
+// An example parameter. Must be supplied or the policy will fail.
+param foo
+```
+
+## Supplying Parameter Values Using the Sentinel CLI
+
+In a production implementation, supplying parameters to a policy is an
+implementation-specific detail - see the documentation for your particular
+implementation for details.
+
+Using the [Sentinel CLI](/sentinel/commands), you can supply parameters one of
+four ways.
+
+### Supplying Parameter Values Using the Configuration File
+
+You can supply parameters using the
+[`param`](/sentinel/commands/config#parameters) section of the
+[configuration file](/sentinel/commands/config).
+
+```json
+{
+ "param": {
+ "foo": "bar"
+ }
+}
+```
+
+This method works for both `sentinel apply` and `sentinel test`.
+
+### Supplying Parameter Values Using the Environment
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can supply a value using environment variables - prefix the parameter with
+`SENTINEL_PARAM_`, using the name of the parameter to supply.
+
+```plaintext
+SENTINEL_PARAM_foo=bar sentinel apply policy.sentinel
+```
+
+### Supplying Parameter Values Using the CLI
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can also use the `-param` CLI argument to supply parameter in a `key=value`
+pair.
+
+```plaintext
+sentinel apply -param foo=bar policy.sentinel
+```
+
+### Interactive CLI Prompting
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+If a required value has not been supplied when a policy is run with `sentinel apply`, it will be prompted for, along with its description:
+
+
+
+ $ sentinel apply policy.sentinel
+
+ policy.sentinel:2:7: requires value for parameter foo
+
+ An example parameter. Must be supplied or the policy will fail.
+
+
+ Values can be strings, floats, or JSON array or object values. To
+ force
+
+ strings, use quotes.
+
+
+ Enter a value: bar
+
+
+
+ Pass
+
+
+
+
+### CLI Value Format
+
+-> **NOTE:** This section contains details for the parameter features supported by `sentinel apply`.
+
+The CLI takes either strings, or JSON numbers, arrays, or maps. If you need a
+literal string value, quote the value.
+
+```sentinel
+foo // string
+42 // number (float)
+"42" // string ("42", without quotes)
+[1, 2] // array (list)
+{"a": "b"} // object (map)
+```
+
+-> **NOTE:** Boolean values are not supported by this method.
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/language/rules.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/language/rules.mdx
new file mode 100644
index 0000000000..52d5eeac35
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/language/rules.mdx
@@ -0,0 +1,165 @@
+---
+page_title: Sentinel Language - Rules
+sidebar_title: Rules
+sidebar_current: docs-language-rules
+description: >-
+ Rules form the basis of a policy by representing behavior that is either
+ passing or failing (true or false).
+layout: docs
+---
+
+# Language: Rules
+
+Rules form the basis of a policy by representing behavior that is either
+passing or failing (true or false). A policy can be broken down into a
+set of rules. Breaking down a policy into a set of rules can make it more
+understandable and aids with testing.
+
+An example is shown below:
+
+```sentinel
+is_sunny = rule { weather is "sunny" }
+is_wednesday = rule { day is "wednesday" }
+
+main = rule {
+ is_sunny and
+ is_wednesday
+}
+```
+
+A rule contains a single [boolean expression](/sentinel/language/boolexpr).
+This boolean expression can be split into multiple lines for readability
+as shown in the example above. Boolean expressions should only be split
+into multiple lines after the operator ("and", "or", etc.).
+
+A rule is also _lazy_ and _memoized_. _Lazy_ means that the rule is only
+evaluated when it is actually required, not at the point that it is
+created. This is covered in more detail in the [lazy](#lazy-and-memoized) section.
+_Memoized_ means that the value is only computed once and then saved. Once
+a rule is evaluated, the result of it is reused anytime the rule is referenced
+again.
+
+## Easing Understandability
+
+Rules are an abstraction that make policies much more understandable
+by making it easier for humans to see what the policy is trying to do.
+
+For example, consider the policy before which doesn't abstract into rules:
+
+```sentinel
+main = rule {
+ ((day is "saturday" or day is "sunday") and homework is "") or
+ (day in ["monday", "tuesday", "wednesday", "thursday", "friday"] and
+ not school_today)
+}
+```
+
+Assume that `day`, `homework`, and `school_day` are available.
+
+In plain english, this policy is trying to say: you can play with your friends
+only on the weekend as long as there is no homework, or during the week if
+there is no school and no homework.
+
+Despite the relatively simple nature of this policy (a few lines, about 5
+logical expressions), it is difficult to read and undertand, especially if
+you've not seen it before.
+
+The same policy using rules as an abstraction:
+
+```sentinel
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+is_valid_weekend = rule { is_weekend and homework is "" }
+is_valid_weekday = rule { not is_weekend and not school_today }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+By reading the names of the rules, its much clearer to see what each individual
+part of the policy is trying to achieve. The readability is improved even
+further when adding comments to the rules to explain them further, which is
+a recommended practice:
+
+```sentinel
+// A weekend is Sat or Sun
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+// A valid weekend is a weekend without homework
+is_valid_weekend = rule { is_weekend and homework is "" }
+
+// A valid weekday is a weekday without school
+is_valid_weekday = rule { not is_weekend and not school_today }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+## "When" Predicates
+
+A rule may have an optional `when` predicate attached to it. When this is
+present, the rule is only evaluated when the predicate results in `true`.
+Otherwise, the rule is not evaluated and the result of the rule is always
+`true`.
+
+"When" predicates should be used to define the context in which the rule
+is semantically meaningful. It is common when translating human language
+policy into Sentinel to have scenarios such as "when the key has a prefix
+of `/account/`, the remainder must be numeric." In that example, when the
+key _does not_ have the specified prefix, the policy doesn't apply.
+
+Assume you have rules `is_prefix` and `is_numeric` to check both the
+conditions in the example above. An example is shown with and without
+the "when" predicate:
+
+```sentinel
+example_no_when = rule { (is_prefix and is_numeric) or not is_prefix }
+
+example_when = rule when is_prefix { is_numeric }
+```
+
+The rules are equivalent in behavior for all values of `is_prefix` and
+`is_numeric`, but the second rule is more succint and easier to understand.
+
+## Testing
+
+To verify a policy works correct, the
+[built-in Sentinel test framework](#)
+uses rules as the point where you can assert behavior. You say that
+you expect certain rules to be true or false. By doing so, you ensure that
+the policy results in the value you expect using the rule flow that you
+expect.
+
+Therefore, in addition to readability, we recommend splitting policies into
+rules to aid testability.
+
+## Lazy and Memoized
+
+A rule is _lazy_ and _memoized_.
+
+_Lazy_ means that the rule is only evaluated when it is actually required,
+not at the point that it is created. And _memoized_ means that the value is
+only computed once and then saved. Once a rule is evaluated, the result of it
+is reused anytime the rule is referenced again.
+
+Both of these properties have important implications for Sentinel
+policies:
+
+**Performance:** Rules are a way to improve the performance of a Sentinel
+policy. If you have a boolean expression that is reused a lot, a rule will
+only be evaluated once. In the example shown above, `is_weekend` is used
+multiple times, but will only have to be evaluated once.
+
+**Behavior:** Rules accessing variables see the value of those variables
+at _the time they are evaluated_. This can lead to surprising behavior
+in some cases. For example:
+
+```sentinel
+a = 1
+b = rule { a == 1 }
+a = 2
+main = b
+```
+
+In this example, `main` will actually result to `false`. It is evaluated
+when it is needed, which is when the policy executes `main`. At this point,
+`a` is now 2 and `b` has not been evaluated yet. Therefore, `b` becomes `false`.
+All future references to `b` will return `false` since a rule is memoized.
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/language/scope.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/language/scope.mdx
new file mode 100644
index 0000000000..2fb562a75d
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/language/scope.mdx
@@ -0,0 +1,42 @@
+---
+page_title: Sentinel Language - Scope
+sidebar_title: Scope
+sidebar_current: docs-language-scope
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Scope
+
+Scope is the context in which variables are created and accessed. If a
+variable exists in a parent scope, it is accessed and can be modified.
+Otherwise, the variable is created in your current scope.
+
+Scopes are created with _blocks_. A block is a possibly empty sequence
+of statements within matching brace brackets `{}`. You may nest blocks
+to create nested scopes.
+
+In addition to explicitly created blocks, there are implicit blocks:
+
+1. The policy block encapsulates an entire policy.
+2. Each `any`, `all`, and `for` statement is considered to be in
+ its own block. Note that `if` statements _do not_ create their
+ own block.
+
+The example policy below shows various effects of scopes:
+
+```sentinel
+a = 1
+print(a) // 1
+
+f = func() {
+ print(a) // 1
+ a = 12
+ b = 1
+ return undefined
+}
+f()
+
+print(a) // 12
+print(b) // undefined
+```
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/language/slices.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/language/slices.mdx
new file mode 100644
index 0000000000..d8faa5cece
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/language/slices.mdx
@@ -0,0 +1,33 @@
+---
+page_title: Sentinel Language - Slices
+sidebar_title: Slices
+sidebar_current: docs-language-slices
+description: >-
+ Slices are an efficient way to create a substring or sublist from an existing
+ string or list, respectively.
+layout: docs
+---
+
+# Language: Slices
+
+Slices are an efficient way to create a substring or sublist from an
+existing string or list, respectively.
+
+The syntax for creating a slice is:
+
+```sentinel
+a[low : high]
+```
+
+`low` is the lowest index to slice from. The resulting slice includes
+the value at `low`. `high` is the index to slice to. The resulting slice
+will not include the value at `high`. The length of the resulting list
+or string is always `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+
+a = "hello"
+b = a[1:4] // "ell"
+```
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/language/spec.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/language/spec.mdx
new file mode 100644
index 0000000000..526c6b39b2
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/language/spec.mdx
@@ -0,0 +1,1297 @@
+---
+page_title: Sentinel Language Specification
+sidebar_title: Specification
+sidebar_current: docs-language-spec
+description: This is the specification for the Sentinel policy language.
+layout: docs
+---
+
+# Sentinel Language Specification
+
+This is the specification for the Sentinel policy language.
+
+The Sentinel language is designed with policy enforcement in mind. It is dynamically typed and garbage collected and has explicit support for _rule_ construction representing boolean logic.
+
+The language is designed to be easy to learn and use by non-programmers. It is expected to be embedded within applications.
+
+## Table of Contents
+
+
+
+
+* [Source code representation](#source-code-representation)
+* [Declarations and Scope](#declarations-and-scope)
+* [Blocks](#blocks)
+* [Lexical Elements](#lexical-elements)
+ * [Comments](#comments)
+ * [Identifiers](#identifiers)
+ * [Keywords](#keywords)
+ * [Operators and Delimiters](#operators-and-delimiters)
+ * [Integer Literals](#integer-literals)
+ * [Floating-point Literals](#floating-point-literals)
+ * [String Literals](#string-literals)
+ * [Implicit Line Joining](#implicit-line-joining)
+ * [Whitespace](#whitespace)
+ * [Semicolons](#semicolons)
+* [Variables](#variables)
+* [Undefined](#undefined)
+* [Expressions](#expressions)
+ * [Operand](#operand)
+ * [Primary Expressions](#primary-expressions)
+ * [Null](#null)
+ * [Booleans](#booleans)
+ * [Boolean Literals](#boolean-literals)
+ * [Boolean Expressions](#boolean-expressions)
+ * [List Literals](#list-literals)
+ * [Map Literals](#map-literals)
+ * [Function Literals](#function-literals)
+ * [Rule Expressions](#rule-expressions)
+ * [Index Expressions](#index-expressions)
+ * [Selectors](#selectors)
+ * [Slice Expressions](#slice-expressions)
+ * [Calls](#calls)
+ * [Operators](#operators)
+ * [Operator Precedence](#operator-precedence)
+ * [Arithmetic Operators](#arithmetic-operators)
+ * [Integer operators](#integer-operators)
+ * [Integer overflow](#integer-overflow)
+ * [Floating-point operators](#floating-point-operators)
+ * [String Concatenation](#string-concatenation)
+ * [List Concatenation](#list-concatenation)
+ * [Comparison Operators](#comparison-operators)
+ * [Logical Operators](#logical-operators)
+ * [Set Operators](#set-operators)
+ * [Matches Operator](#matches-operator)
+ * [Else Operator](#else-operator)
+ * [Quantifier Expressions (any, all, filter)](#quantifier-expressions-any-all-filter)
+* [Statements](#statements)
+ * [Expression Statements](#expression-statements)
+ * [Assignments](#assignments)
+ * [If Statements](#if-statements)
+ * [Case Statements](#case-statements)
+ * [For Statements](#for-statements)
+ * [Break Statements](#break-statements)
+ * [Continue Statements](#continue-statements)
+ * [Return Statements](#return-statements)
+* [Imports](#imports)
+* [Parameters](#parameters)
+* [Built-in Functions](#built-in-functions)
+ * [Length](#length)
+ * [Collections](#collections)
+ * [List Append](#list-append)
+ * [Map Delete](#map-delete)
+ * [Keys and Values](#keys-and-values)
+ * [Range](#range)
+ * [Type Conversion](#type-conversion)
+ * [Printing](#printing)
+ * [Errors](#errors)
+* [Program Execution](#program-execution)
+
+
+
+## Source code representation
+
+Source code is Unicode text encoded in UTF-8. A "character" referenced in this document refers to a Unicode code point. Each code point is distinct: upper and lower case letters are different characters.
+
+The underscore character \_ (U+005F) is considered a "letter".
+
+```ebnf
+letter = unicode_letter | "_" .
+decimal_digit = "0" … "9" .
+octal_digit = "0" … "7" .
+hex_digit = "0" … "9" | "A" … "F" | "a" … "f" .
+```
+
+## Declarations and Scope
+
+A declaration binds an identifier to a value. An identifier is declared at the point it is first assigned. An assignment is considered a first assignment if the identifier hasn't already been previously declared in the current scope or any parent scopes.
+
+The scope of an identifier is the extent of source text in which the identifier denotes the specified value. Sentinel is lexically scoped using blocks.
+
+An identifier declared in a block may be redeclared in an inner block. While the identifier of the inner declaration is in scope, it denotes the value assigned in the inner declaration.
+
+## Blocks
+
+A block is a possibly empty sequence of statements within matching brace brackets.
+
+```ebnf
+Block = "{" StatementList "}" .
+StatementList = { Statement ";" } .
+```
+
+Blocks nest and affect scoping.
+
+In addition to explicit blocks in the source code, there are implicit blocks:
+
+1. The universe block encompasses all source text.
+2. Each program has a program block containing all source text for that program.
+3. Each "any", "all", and "for" statements is considered to be in its own implicit block. "if" does not create an implicit block.
+
+## Lexical Elements
+
+### Comments
+
+Comments are sections of source text used for documentation.
+
+```plaintext
+# Single line comment
+// Single line comment
+/* multi
+line
+ comment */
+```
+
+Three forms of comments are supported: two single-line forms and one multi-line form. A single-line comment begins with the token `//` or `#`. Everything between the starting token and the end of the line is ignored.
+
+A multi-line comment begins with the token `/*` and ends with the token `*/`. Everything between `/*` and `*/` is ignored.
+
+A comment may not start inside a string literal or inside another comment.
+
+A multi-line comment containing no newlines acts like a space.
+
+### Identifiers
+
+Identifiers name program entities such as rules, variables, and functions. An identifier is a sequence of one or more letters and digits. The first character in an identifier must be a letter.
+
+```ebnf
+identifier = letter { letter | unicode_digit } .
+```
+
+```sentinel
+a
+_a
+_A
+αβ
+```
+
+#### Pre-Declared Identifiers
+
+The following identifiers are implicitly declared in the [universe block](#blocks):
+
+```plaintext
+Constants:
+ false null true undefined
+
+Functions:
+ append bool delete error float int keys length print range string values
+```
+
+### Keywords
+
+The following keywords are reserved and may not be used as identifiers:
+
+```plaintext
+all
+any
+as
+break
+case
+continue
+default
+else
+for
+func
+if
+import
+param
+return
+rule
+when
+```
+
+### Operators and Delimiters
+
+The following character sequences represent operators, delimiters, and other special tokens:
+
+```plaintext
++
+-
+*
+/
+%
++=
+-=
+*=
+/=
+%=
+==
+<
+>
+=
+!
+!=
+<=
+>=
+( )
+[ ]
+{ }
+,
+.
+:
+;
+and
+contains
+else
+in
+is
+matches
+not
+or
+xor
+```
+
+As a special case, `else` is both a keyword and operator, depending on the context. See [Else Operator](#else-operator) for more details.
+
+### Integer Literals
+
+An integer literal is a sequence of digits representing an integer constant. An optional prefix sets a non-decimal base: `0` for octal, `0x` or `0X` for hexadecimal. In hexadecimal literals, letters `a-f` and `A-F` represents values 10 through 15.
+
+Integers are signed 64-bit values (-9223372036854775808 to 9223372036854775807).
+
+```ebnf
+int_lit = decimal_lit | octal_lit | hex_lit .
+decimal_lit = ( "1" … "9" ) { decimal_digit } .
+octal_lit = "0" { octal_digit } .
+hex_lit = "0" ( "x" | "X" ) hex_digit { hex_digit } .
+```
+
+```
+42
+0600
+0xBadFace
+170141183460469231731687303715884105727
+```
+
+### Floating-point Literals
+
+A floating-point literal is a decimal representation of a floating-point constant. It has an integer part, a decimal point, a fractional part, and an exponent part. The integer and fractional part comprise decimal digits; the exponent part is an e or E followed by an optionally signed decimal exponent. One of the integer part or the fractional part may be elided; one of the decimal point or the exponent may be elided.
+
+Floating-point numbers are IEEE-754 64-bit floating numbers.
+
+```ebnf
+float_lit = decimals "." [ decimals ] [ exponent ] |
+ decimals exponent |
+ "." decimals [ exponent ] .
+decimals = decimal_digit { decimal_digit } .
+exponent = ( "e" | "E" ) [ "+" | "-" ] decimals .
+```
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+### String Literals
+
+String literals are character sequences between double quotes, as in "bar". Within the quotes, any character may appear except newline and unescaped double quote. The text between the quotes forms the value of the literal.
+
+A multi-character sequence beginning with a backslash encode values in various formats.
+
+Backslash escapes allow arbitrary values to be encoded as ASCII text. There are four ways to represent the integer value as a numeric constant: `\x` followed by exactly two hexadecimal digits; `\u` followed by exactly four hexadecimal digits; `\U` followed by exactly eight hexadecimal digits, and a plain backslash `\` followed by exactly three octal digits. In each case the value of the literal is the value represented by the digits in the corresponding base.
+
+After a backslash, certain single-character escapes represent special values:
+
+```plaintext
+\a U+0007 alert or bell
+\b U+0008 backspace
+\f U+000C form feed
+\n U+000A line feed or newline
+\r U+000D carriage return
+\t U+0009 horizontal tab
+\v U+000b vertical tab
+\\ U+005c backslash
+\" U+0022 double quote
+```
+
+The three-digit octal (\nnn) and two-digit hexadecimal (\xnn) escapes represent individual bytes of the resulting string; all other escapes represent the (possibly multi-byte) UTF-8 encoding of individual characters. Thus inside a string literal \377 and \xFF represent a single byte of value 0xFF=255, while ÿ, \u00FF, \U000000FF and \xc3\xbf represent the two bytes 0xc3 0xbf of the UTF-8 encoding of character U+00FF.
+
+A string value is a (possibly empty) sequence of bytes. Strings are immutable: once created, it is impossible to change the contents of a string.
+
+The length of a string s (its size in bytes) can be discovered using the built-in function `length`. An individual character (of type string) can be accessed by integer indices 0 through `length(s)-1`.
+
+```ebnf
+string_lit = `"` { unicode_value | byte_value } `"` .
+unicode_value = unicode_char | little_u_value | big_u_value | escaped_char .
+byte_value = octal_byte_value | hex_byte_value .
+octal_byte_value = `\` octal_digit octal_digit octal_digit .
+hex_byte_value = `\` "x" hex_digit hex_digit .
+little_u_value = `\` "u" hex_digit hex_digit hex_digit hex_digit .
+big_u_value = `\` "U" hex_digit hex_digit hex_digit hex_digit
+ hex_digit hex_digit hex_digit hex_digit .
+escaped_char = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | `"` ) .
+```
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+### Implicit Line Joining
+
+Expressions can be split over more than one line. For example:
+
+```sentinel
+a or
+b or # This is a comment
+c
+```
+
+Implicitly continued lines can have trailing comments. Blank continued lines are allowed.
+
+### Whitespace
+
+Whitespace is needed to separate tokens, but no distinction is made between the number and combination of whitespace characters. Whitespace characters can be space (U+0020), horizontal tabs (U+0009), carriage returns (U+000D), and newlines (U+000A).
+
+```sentinel
+a or b
+
+a or b
+
+a or
+ b
+
+rule { a or b }
+
+rule {
+ a or b }
+
+rule {
+ a or
+ b
+}
+```
+
+### Semicolons
+
+The formal grammar uses semicolons ";" as terminators in a number of productions.
+It is idiomatic Sentinel source to omit semicolons in most cases.
+
+A semicolon is automatically inserted into the token stream immediately after
+a line's final token if that token is:
+
+* An identifier
+* An integer, float, or string literal
+* The keyword `break`, `continue`, or `return`
+* The delimiter `)`, `]`, or `}`
+
+## Variables
+
+A variable is a storage location for holding a value.
+
+A variable has a dynamic type, which is the concrete type of the value assigned to the variable at run time. The dynamic type may vary during execution and change as a result of further assignments.
+
+A variable's value is retrieved by referring to the variable in an expression; it is the most recent value assigned to the variable. If a variable has not yet been declared (initially assigned), it is an error.
+
+```sentinel
+x = 7 // x is type int
+x = "foo" // x is now type string
+
+x = y // error if y is not previously assigned
+```
+
+## Undefined
+
+The value denoted by the keyword `undefined` represents undefined behavior or values. It can be created directly using the keyword `undefined`. It is also returned as a result of expressions in specified cases.
+
+`undefined` is a valid operand for any operations. Only `undefined or true` will result in true. All other operations result in `undefined`. An exception is if `undefined` is not reached as a result of short-circuit operations.
+
+```sentinel
+undefined OR true = true
+undefined OR false = undefined
+undefined OR undefined = undefined
+undefined AND true = undefined
+undefined AND false = undefined
+undefined AND undefined = undefined
+undefined XOR true = undefined
+undefined XOR false = undefined
+undefined XOR undefined = undefined
+
+// Short-circuit examples
+false OR true OR undefined = true
+false OR undefined OR true = true
+true AND false AND undefined = false
+true AND undefined AND false = undefined
+
+// Non-logical operators
+undefined + 5 = undefined
+-undefined = undefined
+!undefined = undefined
+```
+
+If the result of the main rule is `undefined`, it is treated as `false` but is indicative of erroneous logic.
+
+## Expressions
+
+### Operand
+
+Operands denote the elementary values in an expression. An operand may be a literal, an identifier denoting a variable, rule, or function, or a parenthesized expression.
+
+```ebnf
+Operand = Literal | OperandName | MethodExpr | "(" Expression ")" .
+Literal = BasicLit | CompositeLit | FunctionLit | RuleLit .
+BasicLit = int_lit | float_lit | string_lit .
+OperandName = identifier | QualifiedIdent.
+```
+
+### Primary Expressions
+
+Primary expressions are the operands for unary and binary expressions.
+
+```ebnf
+PrimaryExpr =
+ Operand |
+ PrimaryExpr Selector |
+ PrimaryExpr Index |
+ PrimaryExpr Slice |
+ PrimaryExpr Arguments .
+
+Selector = "." identifier .
+Index = "[" Expression "]" .
+Slice = "[" [ Expression ] ":" [ Expression ] "]" |
+ "[" [ Expression ] ":" Expression ":" Expression "]" .
+Arguments = "(" [ ( ExpressionList | Type [ "," ExpressionList ] ) [ "..." ] [ "," ] ] ")" .
+```
+
+```sentinel
+x
+2
+(s + ".txt")
+f(3.1415, true)
+m["foo"]
+s[i : j + 1]
+obj.color
+f.p[i].x()
+```
+
+### Null
+
+The reserved word `null` denote the singleton value `null`. Null represents the explicit absense of a value. Behavior of `null` within expressions is specified explicitly for each expression.
+
+### Booleans
+
+### Boolean Literals
+
+The reserved words `true` and `false` denote objects that represent the boolean values `true` and `false`, respectively. These are boolean literals.
+
+```ebnf
+BoolLit = "true" | "false" .
+```
+
+#### Boolean Expressions
+
+Boolean expressions are expressions that must result in a boolean value. Any other value type becomes the `undefined` value.
+
+```ebnf
+BoolExpr = Expression .
+```
+
+### List Literals
+
+A list literal denotes a list, which is an integer indexed collection of values.
+
+```ebnf
+ListLit = "[" [ ElementList [ "," ] ] "]" .
+ElementList = Element { "," Element } .
+Element = Expression | LiteralValue .
+```
+
+A list may contain zero or more values. The number of values in a list is its length.
+
+A list has a set of indices. An empty list has an empty set of indices. A non-empty list has the index set `{0...n - 1}` where `n` is the length of the list. Attempting to access a list using an index that is not a member of its set of indices results in the `undefined` value.
+
+### Map Literals
+
+A map literal denotes a map, which is an unordered group of elements indexed by a set of unique keys.
+
+```ebnf
+MapLit = "{" [ KeyedElementList [ "," ] ] "}" .
+KeyedElementList = KeyedElement { "," KeyedElement } .
+KeyedElement = Element ":" Element .
+```
+
+Keys can only be a boolean, numeric, or string type.
+
+The value of a non-existent key is the `undefined` value.
+
+### Function Literals
+
+A function literal represents a function.
+
+```ebnf
+FunctionLit = "func" Function .
+Function = Parameters FunctionBody .
+FunctionBody = Block .
+Parameters = "(" [ IdentifierList [ "," ] ] ")" .
+IdentifierList = identifier { "," identifier } .
+```
+
+```sentinel
+func(a, b) { return b }
+```
+
+Function literals are only allowed in the file scope. They may refer to variables defined in surrounding blocks.
+
+A function must terminate with a return statement. If a function has no meaningful return value, it should return `undefined`.
+
+### Rule Expressions
+
+A rule is a boolean expression that is evaluated lazily and the result is memoized.
+
+If the optional "when" predicate is present, the rule is evaluated only when
+the "when" boolean expression results in true. If the predicate is false,
+the rule is not evaluated and returns true. The predicate is evaluated when
+the rule would be evaluated; it is also lazy and memoized in the same way.
+
+```ebnf
+RuleExpr = "rule" [ "when" BoolExpr ] "{" BoolExpr "}" .
+```
+
+```sentinel
+rule { x is y }
+
+rule {
+ x == "value" or
+ y == "other"
+}
+
+rule when x is y { y > 42 }
+```
+
+### Index Expressions
+
+A primary expression of the form `a[x]` denotes the element of a list, map, or string indexed by x. The value x is called the index or key.
+
+For `a` of type map:
+
+* If `a` contains a key `x`, `a[x]` is the map value with key `x`.
+* If `a` does not contain key `x`, `a[x]` is the `undefined` value.
+
+For `a` of type list:
+
+* `x` must be an integer
+* `x` must be in the range `[-1 * length(a), length(a)-1]`
+* If `x` is contained in the set of indices of `a`, `a[x]` is the list element at index `x`. If `x` is negative, `a[x]` is equivalent to `a[length(a)+x]`.
+* If `x` is not contained in the set of indices of `a`, `a[x]` is the `undefined` value.
+
+For `a` of type string:
+
+* `x` must be an integer
+* `x` must be in the range `[-1 * length(a), length(a)-1]`
+* If `x` is in range, `a[x]` is the byte value at index `x` as a string. If `x` is negative, `a[x]` is equivalent to `a[length(a)+x]`.
+* If `x` is out of range, `a[x]` is the `undefined` value.
+
+For `a` of value `null`:
+
+* `a[x]` is the `undefined` value.
+
+Otherwise `a[x]` is an error.
+
+### Selectors
+
+For a primary expression x, the selector expression `x.f` denotes the field `f` of the value `x`. The identifier `f` is called the selector. The type of the selector expression is the type of the selector.
+
+As a special case, selectors can be [reserved words](#keywords) and keyword [operators](#operators-and-delimiters), but cannot be any other non-identifier element.
+
+Selectors are used to access data from [imports](#imports). The first primary expression `x` in `x.f` denotes the import name. The field `f` is the selector to access data from the import.
+
+Selectors may also be used to access map data with static keys. They are syntactic sugar over index expressions. `math.pi` is equivalent to `math["pi"]` and exhibit the same limitations and behavior as an index expression. Selectors cannot, however, be used to assign values to map data.
+
+Selectors on undefined result in undefined.
+
+```sentinel
+math.pi
+time.pst.hour
+```
+
+### Slice Expressions
+
+Slice expressions construct a substring or list from a string or list.
+
+The primary expression
+
+```sentinel
+a[low : high]
+```
+
+constructs a substring or list. The indices `low` and `high` select which elements of operand `a` appear in the result. The result has indices starting at 0 and length equal to `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+```
+
+For convenience, any of the indices may be omitted. A missing `low` index defaults to zero; a missing `high` index defaults to the length of the sliced operand:
+
+```sentinel
+a[2:] // same as a[2 : length(a)]
+a[:3] // same as a[0 : 3]
+a[:] // same as a[0 : length(a)]
+```
+
+The indices are in range if `0 <= low <= high <= length(a)`, otherwise they are out of range. If the indices are out of range at run time, the result is the `undefined` value.
+
+If `a` is the value `null`, the result is the `undefined` value.
+
+If `a` is any other value type, it is an error.
+
+### Calls
+
+Given an expression `f` where `f` is a function value:
+
+```sentinel
+f(a1, a2, … an)
+```
+
+calls `f` with arguments `a1, a2, ... an`. The type of the expression is the result of `f`. The arguments are evaluated left to right. Arguments are passed by value.
+
+### Operators
+
+```ebnf
+Expression = UnaryExpr | Expression binary_op Expression .
+UnaryExpr = PrimaryExpr | unary_op UnaryExpr .
+
+binary_op = logical_op | set_op | rel_op | add_op | mul_op | else_op .
+logical_op = "and" | "or" | "xor" .
+set_op = ["not"] ( "contains" | "in" ).
+rel_op = "==" | "!=" | "<" | "<=" | ">" | ">=" |
+ "is" | "is not" | "matches" | "not matches" .
+add_op = "+" | "-" .
+mul_op = "*" | "/" | "%" .
+else_op = "else" .
+unary_op = "+" | "-" | "!" | "not" .
+```
+
+#### Operator Precedence
+
+Unary operators have the highest precedence.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Binary operators of the same precedence associate from left to right. For instance, x / y _ z is the same as (x / y) _ z.
+
+### Arithmetic Operators
+
+Arithmetic operators apply to numeric values and yield a result of the same type when both operands are the same.
+
+Arithmetic operations between integer and floating-point types result in a floating-point value with the integer operand treated as a floating-point value for purposes of calculation.
+
+Arithmetic operations between numeric values (integer, floating-point) and non-numeric values (strings, lists) are not permitted.
+
+All five supported arithmetic operators (+, -, \*, /, %) apply to both integer and floating-point types; + also applies to strings.
+
+```plaintext
++ sum integers, floats, strings, lists
+* difference integers, floats
+* product integers, floats
+/ quotient integers, floats
+% remainder integers, floats
+```
+
+#### Integer operators
+
+For two integer values x and y, the integer quotient `q = x / y` and remainder `r = x % y` satisfy the following relationships:
+
+```sentinel
+x = q*y + r and |r| < |y|
+```
+
+with x / y truncated towards zero.
+
+```plaintext
+ x y x / y x % y
+ 5 3 1 2
+-5 3 -1 -2
+ 5 -3 -1 2
+-5 -3 1 -2
+```
+
+As an exception to this rule, if the dividend x is the most negative value for the int type of x (-9223372036854775808), the quotient `q = x / -1` is equal to x (and r = 0).
+
+If the divisor is a constant, it must not be zero. If the divisor is zero at run time, an error occurs.
+
+#### Integer overflow
+
+For signed integers, the operations `+`, `-`, and `*` may legally overflow and the resulting value exists and is deterministically defined by the signed integer representation, the operation, and its operands. No exception is raised as a result of overflow.
+
+#### Floating-point operators
+
+For floating-point numbers, +x is the same as x, while -x is the negation of x. The result of a floating-point division by zero is not specified beyond the IEEE-754 standard.
+
+#### String Concatenation
+
+Strings can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = "hi"
+y = "hello"
+x = x + ", " + y // "hi, hello"
+x += " and good bye" // "hi, hello and good bye"
+```
+
+String addition creates a new string by concatenating the operands.
+
+#### List Concatenation
+
+Lists can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = [1, 2]
+x = x + [2, 3] // [1, 2, 2, 3]
+x += [4] // [1, 2, 2, 3, 4]
+```
+
+List addition creates a new list by concatenating the operands.
+
+### Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+In any comparison, the two operands must be equivalent types. The only exception are integers and floats, which are considered numeric and may be compared. Any comparison between other non-matching types results in the `undefined` value.
+
+The equality operators `==`, `!=`, `is`, and `is not` apply to operands that are comparable. The ordering operators `<`, `<=`, `>`, and `>=` apply to operands that are ordered. The behavior of `is` with `==` and `is not` with `!=` is identical. These terms and the result of the comparisons are defined as follows:
+
+* Boolean values are comparable. Two boolean values are equal if they are either both true or both false.
+* Integer values are comparable and ordered, in the usual way.
+* Floating point values are comparable and ordered, as defined by the IEEE-754 standard.
+* An integer compared with floating point value treats the integer as the converted floating point value.
+* String values are comparable and ordered, lexically byte-wise.
+* Lists are comparable. Lists are equal if they are of equal length and their
+ corresponding elements are comparable and equal.
+
+Maps are not comparable. As a special case, maps may be compared to the `null` value for equality or inequality.
+
+If either operand is the `undefined` value, the result of the expression is the `undefined` value.
+
+### Logical Operators
+
+Logical operators apply to boolean values and yield a boolean result.
+
+Logical operators are evaluated left-to-right and perform short-circuit logic. The right-hand side is not guaranteed to be evaluated if a short-circuit can be performed.
+
+```plaintext
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+### Set Operators
+
+The set operators `contains` and `in` test for set inclusion for lists
+and maps, and substring inclusion for strings.
+
+Set operators may be negated by prefixing the operator with `not`: `not contains` and `not in`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+`contains` tests if the left-hand collection contains the right-hand value. For lists, this tests equality of any value. For maps, this tests equality of any key. For strings, this tests for substring existence.
+
+`in` tests if the right-hand collection contains the left-hand value. The behavior is equivalent to `contains`.
+
+The collection must be a list, map, or string. If it is the `undefined` value, the result is the `undefined` value. For any other value, the result is an error.
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+
+"test" contains "est" // true
+"test" contains "best" // false
+"test" in "testing" // true
+"best" in "testing" // false
+```
+
+### Matches Operator
+
+The matches operator tests if a string matches a regular expression.
+
+The matches operators may be negated by prefixing the operator with `not`: `not matches`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+The left-hand value is the string to test. The right-hand value is a string value representing a regular expression.
+
+The syntax of the regular expression is the same general syntax used by Python, Ruby, and other languages. More precisely, it is the syntax accepted by RE2. The regular expression is not anchored by default; any anchoring must be explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+If either operand is `undefined`, the result is the `undefined` value. For any other non-string value, the result is an error.
+
+### Else Operator
+
+Else operators can be used to provide a default value for an expression that may evaluate to the `undefined` value. Else operators return their left-hand value unless it is `undefined`, otherwise they return their right-hand value.
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else null
+```
+
+### Quantifier Expressions (any, all, filter)
+
+Quantifiers are expressions that apply a predicate to a collection (list or map). The purpose of the quantifier is to produce a result based on its particular type.
+
+`any` and `all` expressions are existential and universal quantifiers, respectively. `any` expressions are equivalent to a chain of `or` and `all` expressions are equivalent to a chain of `and`. Both expressions implement short-circuiting equivalent to logical operators.
+
+The `filter` expression is a subset quantifier and returns the subset of all values in the input collection for which the supplied predicate applies. This will be zero or more elements, up to the length of the input.
+
+The body of a quantifier expression is a boolean expression.
+
+`any` returns the boolean value `true` if any value in the collection expression results in the body expression evaluating to `true`. If the body expression evaluates to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression result in the body expression evaluating to `true`. If any value in the collection expression result in the body expression evaluating to `false`, the all expression returns `false`.
+
+For empty collections, `any` returns false and `all` returns `true`.
+
+`filter` returns a list or map, the same type as its input collection. Only the elements for which the expression body evaluated to `true` will be returned. If an iteration of the expression body results in `undefined`, the entire result set is `undefined`.
+
+When the collection is a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When the collection is a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+QuantExpr = QuantOp Expression "as" [ identifier "," ] identifier "{" BoolExpr "}" .
+QuantOp = "all" | "any" | "filter" .
+```
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" } // true if every iterations is true
+any group.tasks as t { t.driver is "vmware" } // true if any iteration is true
+filter group.tasks as t { t.driver is "vmware" } // subset of tasks that is true
+```
+
+## Statements
+
+### Expression Statements
+
+Function call expressions may exist in the statement context.
+
+```ebnf
+ExpressionStmt = Expression .
+```
+
+### Assignments
+
+Assignments set the value of an expression to the value of another expression.
+
+```ebnf
+Assignment = AssignExpr assign_op Expression .
+AssignExpr = identifier | IndexExpr .
+assign_op = [ add_op | mul_op ] "=" .
+```
+
+The assignment `x op= y` is equivalent to `x = x op (y)` for supported values of `op`.
+
+```sentinel
+a = 1
+b[12] = 42
+c["key"] = "value"
+
+a *= 12
+b[12] += 8
+```
+
+Assignments to lists and maps can be carried out using [index expressions](#index-expressions), with the operands of the index expression being evaluated alongside the right hand side expression in a right-to-left (RHS, LHS) order.
+
+In addition to the rules detailed in the section describing their use, the following rules apply when assigning to maps or lists using index expressions:
+
+* The [variable](#variables) must exist and be a list or map. Attempts to use an index assignment on an unknown variable, or a variable that not a list or map, results in a runtime error.
+* Assigning to a list or map index that already exists overwrites that index's data with evaluated right hand side's value.
+* Assigning to an unknown key in a map creates a key for that map with the evaluated right hand side's value assigned to that key.
+* Attempting to assign a value to a list index that is out of range results in a runtime error.
+
+### If Statements
+
+If statements specify the conditional execution of two branches according to the value of a boolean expression. If the expression evaluates to true, the "if" branch is executed, otherwise, if present, the "else" branch is executed.
+
+```ebnf
+IfStmt = "if" BoolExpr Block [ "else" ( IfStmt | Block ) ] .
+```
+
+```sentinel
+if x < y {
+ return x
+} else if x > z {
+ return z
+} else {
+ return y
+}
+```
+
+### Case Statements
+
+Case statements specify a selection control mechanism to conditionally execute a branch based on expression equality.
+
+Each clause that wishes to provide an expression must use the "when" keyword. Multiple expressions can be provided, separated with a comma (",").
+
+There can be one, optional, "else" clause within a `case` statement. The "else" clause is executed if all other clauses failed to run.
+
+The clauses are evaluated in the order they are listed, with the first successful evaluation being executed.
+
+A `case` statement can optionally provide an expression. If no expression is provided, it is the equivalent of writing `case true {`.
+
+```ebnf
+CaseStmt = "case" [ Expression ] "{" { CaseWhenClause } "}" .
+CaseWhenClause = CaseWhenCase ":" StatementList .
+CaseWhenCase = "when" Expression { "," Expression } | "else" .
+```
+
+```sentinel
+case x {
+when y, z:
+ return true
+else:
+ return false
+}
+
+case {
+when x > 42:
+ return true
+else:
+ return false
+}
+```
+
+### For Statements
+
+A `for` statement specifies repeated execution of a block.
+
+The expression must evaluate to a list or a map.
+
+When iterating over a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When iterating over a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+ForStmt = "for" Expressions "as" [ identifier "," ] identifier Block .
+```
+
+```sentinel
+for [1, 2, 3] as v { count += v } // basic sum
+
+for [1, 2, 3] as idx, v {
+ if idx > 1 { count += v }
+}
+
+data = { "a": 12, "b": 32 }
+for data as k { count += data[k] } // sum map values
+for data as k, v { count += v }
+```
+
+### Break Statements
+
+A `break` statement terminates execution of the innermost `for` statement
+within the same function.
+
+```ebnf
+BreakStmt = "break" .
+```
+
+```sentinel
+// Will only print "1"
+for [1, 2, 3] as v {
+ print(v)
+ break
+}
+```
+
+### Continue Statements
+
+A `continue` statement begins the next iteration of the innermost `for` loop.
+The `for` loop must be within the same function.
+
+```ebnf
+ContinueStmt = "continue" .
+```
+
+```sentinel
+// Will print 1, 3
+for [1, 2, 3] as v {
+ if v == 2 {
+ continue
+ }
+
+ print(v)
+ break
+}
+```
+
+### Return Statements
+
+A return statement in a function F terminates the execution of F and provides the result value.
+
+```ebnf
+ReturnStmt = "return" Expression .
+```
+
+A function must terminate with a return statement.
+
+The return statement can be invoked at any time during a function to terminate execution.
+
+## Imports
+
+An import adds an assignment to the global scope to external definitions.
+
+The import declarations must only appear at the top of the source file, before any other statements. Comments may appear before imports.
+
+```ebnf
+ImportDecl = "import" Name ["as" identifier] .
+Name = string_lit .
+```
+
+An import name and identifier must be distinct. Two names may not repeated even if they're declared with different identifiers.
+
+If an identifier is not specified, the identifier is the name of the import itself.
+
+An import is not a valid value. The identifier representing the import may not be used as an expression, such as in assignment statements or call expressions.
+
+Values in imports are not assignable directly via their selectors. Function calls may be used to alter the state of the external data represented by the import. Whether or not this is supported is specific to the import implementation. The exception to this assignment restriction is the memoized data returned by a call to an import (see below).
+
+Data returned by imports can be memoized, meaning that data is returned by the import in the form of a map which is then used as much as possible without having to return to the import for more data. For example, `t = time.now` returns a map so that `t.hour` will be able to be looked up without having to go back to the import for that data.
+
+Data returned by imports may be callable, meaning that methods may be called on the memoized data returned by an import. For example, given `t = time.now`, `t.after(some_previous_time)` is valid, with the receiver data being set to the local memoized data stored in `t`. It is a valid case to accept modifications to the receiver data (example: assignment to the memoized data via index expressions), as long as all data in in the receiver continues to be string-keyed. It is an invalid case to accept receiver data with non-string-typed keys. Methods may also alter the contents of receiver data so that it is different after the call is finished.
+
+As with methods, imports may also have callable keys where the data may not be memoized. Example: in the event of `v = foo.bar`, if `v.baz` is not included in the memoized data, a call will be made to the import to fetch it. The same rules and restrictions to receiver data apply to callable keys as do to method calls.
+
+The support for callable methods and non-memoized keys on data returned by imports is specific to the import implementation.
+
+The handling of import loading is specific to the runtime implementation.
+
+Implicit imports may be added by the runtime, for example for standard library definitions. An explicit import of the same name should override a matching implicit import. For example, if "time" is an implicit import and the program specifies `import "time" as cal`, then only `cal` is defined.
+
+```sentinel
+import "time"
+import "math" as m
+```
+
+## Parameters
+
+```ebnf
+ParamDecl = "param" identifier [ "default" Expression ]
+```
+
+A parameter is a way for a policy to describe variables that are expected to be supplied by the calling application, in addition to any default value.
+
+Parameter declarations must appear at the top of the source file, following imports.
+
+Like imports, comments may appear before parameters; this is mainly pertinent when the policy is not importing anything, which would make parameters the first declarations that appear in a policy.
+
+A parameter declaration describes a valid variable that is added to the scope of a policy at runtime. This variable has the same properties and rules as other variables assigned during the course of policy evaluation as defined by the specification. This includes having a dynamic type and being able to be re-assigned during execution.
+
+Parameters may only be strings, integers, floating point numbers, booleans, lists, or maps. More complex types such as rules or functions are not allowed.
+
+A parameter can specify a default value by adding one in the declaration via use of the "default" keyword, and supplying a literal for the default value. The literal for a default is a very limited expression, comprising of:
+
+* For strings, a simple string literal;
+* For integers and floating-point numbers, either a literal denoting the value, or a unary expression with the literal, and the operators - or +;
+* For booleans, the pre-declared identifiers true or false;
+* For lists and maps, their respective literals composed of values and keys (for maps) of the above values only.
+
+Any other type of expression or identifier is not allowed.
+
+A parameter that does not have a default value defined is required by the policy. Evaluating a policy with a required parameter that has not been supplied at execution results in a runtime error.
+
+Parameters must not conflict with imports or any other identifiers currently defined in the global scope, including any reserved or pre-declared identifiers. Attempting to assign a parameter to an existing value results in a runtime error.
+
+```sentinel
+import "time"
+
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+param time default "11:00" // error, conflicts with "time" import
+param undefined // error, reserved identifier
+```
+
+## Built-in Functions
+
+Built-in functions are predeclared. They are called like any other function.
+
+### Length
+
+The built-in function `length` returns the length of a collection of string.
+
+The length of a string is the number of bytes in the string. It is not necessarilly the number of characters.
+
+The length of a collection is the number of elements in that collection.
+
+The length of `undefined` is `undefined`.
+
+### Collections
+
+#### List Append
+
+The built-in function `append` appends a value to the end of a list in-place.
+
+Appending to a non-list value results in an immediate `fail`.
+
+The return value of `append` is always the `undefined` value.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+#### Map Delete
+
+The built-in function `delete` deletes elements from a map by key. The map is modified in-place.
+
+Deleting a key that does not exist does nothing.
+
+Calling delete for a non-map value results in an immediate `fail`.
+
+The return value of `delete` is always the `undefined` value.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
+
+#### Keys and Values
+
+The built-in function `keys` and `values` return the keys and values of a map, respectively. The return value is a list. Both functions return values in an unspecified order.
+
+The `keys` or `values` of `undefined` is `undefined`.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+### Range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it defaults to `1`.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4]
+range(1, 5, 2) // [1,3]
+range(0, -3, -1) // [0,-1,-2]
+```
+
+### Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a value of that type according to the rules below.
+
+For `int`:
+
+* Integer values are unchanged
+* String values are converted according to the syntax of integer literals
+* Float values are rounded down to their nearest integer value
+* Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+* Float values are unchanged
+* Integer values are converted to the nearest equivalent floating point value
+* String values are converted according to the syntax of float literals
+* Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+* String values are unchanged
+* Integer values are converted to the base 10 string representation
+* Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+* Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+* The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+* The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+* Any non-zero integer or float value converts to `true`
+* Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
+
+### Printing
+
+The built-in function `print` can be used to output formatted debug information. The runtime may omit print function calls depending on its execution mode. The runtime may choose where the output of a print function goes.
+
+`print` is a variadic function, accepting one or more values to be printed; values are separated by a single whitespace character (`" "`).
+
+The return value of `print` is always `true` to allow it to be used in boolean expressions.
+
+```sentinel
+print("hello") // "hello"
+print("hello", "world") // "hello world"
+print("The", "number", "is", 42) // "The number is 42"
+print([1, 2, 3]) // "[1, 2, 3]"
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // "false"
+```
+
+### Errors
+
+The built-in function `error` is equivalent to `print` but immediately causes execution to halt and the main rule to evaluate to `false`. The program execution is considered to have failed.
+
+## Program Execution
+
+Program execution begins by evaluating the source top to bottom. If evaluation is successful, the `main` rule is evaluated and its result is returned. Execution can be interrupted at any time by an error, which can be called explicitly or implicitly through illegal or undefined behavior.
+
+---
+
+> The content of this page is licensed under the [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
+>
+> Based on [The Go Programming Language Specification](https://golang.org/ref/spec) licensed under [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/language/undefined.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/language/undefined.mdx
new file mode 100644
index 0000000000..fa8dd50cc4
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/language/undefined.mdx
@@ -0,0 +1,98 @@
+---
+page_title: Sentinel Language - Undefined
+sidebar_title: Undefined
+sidebar_current: docs-language-undefined
+description: >-
+ The value `undefined` is a special value representing undefined behavior or an
+ unknown value. Any operation on an `undefined` value results in `undefined`.
+layout: docs
+---
+
+# Language: Undefined
+
+The value `undefined` is a special value representing undefined behavior
+or an unknown value.
+
+Undefined is not an error because there are situations where the undefined
+value can be safely ignored and the language also provides construts for
+recovering from an undefined value.
+
+The undefined value can be created manually with `undefined`. In most cases,
+undefined is created by accessing a non-existent list element or value.
+The undefine value is created in a number of other circumstances. The documentation
+will note when an undefined value may be created.
+
+Almost any operation with `undefined` results in `undefined`. For example,
+performing math on undefined, attempting to call a function that is undefined,
+etc.
+
+## Main and Undefined
+
+If the value `undefined` is returned from the top-level `main` rule, then
+the policy is considered `false`. However, the runtime provides mechanisms
+for the host application to determine the policy failure was caused by
+an undefined value and report that to the user.
+
+The `main` rule returning the undefined value should be considered a logical
+error in most cases and should probably be corrected by the policy writer.
+
+## Debugging Undefined
+
+When an `undefined` value is first created (either explicitly or implicitly),
+the runtime will record the position where the undefined was created. This
+location can be used to find logic that could be erroneous.
+
+## Boolean Logic and Undefined
+
+Boolean logic is one way to safely recover from `undefined`. If boolean
+logic can safely determine a result despite an undefined value, that result
+will be returned.
+
+For example: `undefined or true` will result in `true`, because no matter
+what actual value `undefined` could've been if the policy behaved properly,
+the boolean expression would still be true.
+
+Another scenario where `undefined` can be safely ignored is short-circuit
+logic with `and`. `false and undefined` will result in `false`.
+
+The full table of logical operations on `undefined` is below:
+
+```sentinel
+undefined or true = true
+undefined or false = undefined
+undefined or undefined = undefined
+undefined and true = undefined
+undefined and false = undefined
+undefined and undefined = undefined
+undefined xor true = undefined
+undefined xor false = undefined
+undefined xor undefined = undefined
+
+// Short-circuit examples
+false or true or undefined = true
+false or undefined or true = true
+true and false and undefined = false
+true and undefined and false = undefined
+```
+
+## Else Expressions
+
+The `else` expression allows explicit recovery from undefined and is useful
+for setting default values.
+
+`else` is an operator where the left and right hand side are values. If the
+left-hand value is `undefined`, the right-hand value is returned. Otherwise,
+the left-hand value is returned.
+
+Examples:
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else "default"
+
+// In more complex scenarios
+
+foo() else 42 == 12
+a = config.value else "default"
+```
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/language/values.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/language/values.mdx
new file mode 100644
index 0000000000..d2969549b9
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/language/values.mdx
@@ -0,0 +1,195 @@
+---
+page_title: Sentinel Language - Values
+sidebar_title: Values
+sidebar_current: docs-language-values
+description: Values are the data that Sentinel policies operate on.
+layout: docs
+---
+
+# Language: Values
+
+Values are the _data_ that Sentinel policies operate on. You can create this
+data yourself, for example by just typing the number `42`, or you may access
+this data from an external source to make policy decisions.
+
+Values have a _type_, which determines what kind of operatins can be performed
+on the data. Example types are booleans (true and false), numbers,
+and strings (text).
+
+This page documents all the available value types. You can also
+[convert between types](#type-conversion).
+
+## Boolean
+
+A boolean is a value that is either true or false.
+
+A boolean value is created literally with `true` or `false`.
+
+As a policy language, booleans are central to the behavior of Sentinel.
+Booleans are used as conditions in [if statements](/sentinel/language/conditionals),
+are the result of [rules](/sentinel/language/rules), and more. The ultimate
+result of a Sentinel policy is true or false.
+
+## Integer
+
+An integer is a whole number.
+
+An integer is a 64-bit value. This means it can represent numbers from
+-9,223,372,036,854,775,808 to 9,223,372,036,854,775,808.
+
+Integers are created by typing them out literally with no
+separators (such as a comma). An optional prefix can set a non-decimal base:
+`0` for octal, and `0x` for hexadecimal. In hexadecimal literals, letters
+`a-f` and `A-F` represent values 10 to 15.
+
+Example integers are shown below:
+
+```sentinel
+42
+0600
+0xBadFace
+170141183460469
+```
+
+Integers are used for math and numerical comparison.
+
+## Float
+
+A float is a number with a decimal point. It has an integer part, a decimal
+point, a fractional part, and optionally an exponent part. Example
+floats are shown below:
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+Floats are IEEE-754 64-bit floating point numbers.
+
+## String
+
+Strings are text values.
+
+Strings are created by wrapping text in double quotes, such as `"hello"`.
+Within the quotes, any character may appear except newline and an unescaped
+double quote. The text between the quotes is the value.
+
+Because Sentinel policies must be UTF-8 encoded text, strings themselves are
+UTF-8 encoded text. This means you can put any value UTF-8 character into
+a string, such as `"日本語"`.
+
+You can have a string with a literal double-quote by _escaping_ it with
+a backslash. For example: `"they said \"hello\""` turns into the value
+`they said "hello"`.
+
+Backslash escapes can be used for much more than only escaping a double quote.
+Newlines are `\n`, a literal backslash is `\\`, and many more. The full list
+of available backslash escapes can be found
+[in the language specification](/sentinel/language/spec#string-literals).
+
+Example strings:
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+Strings support indexing. Indexing a string will access that _byte_
+in the string as if the string were a byte array. This is an important
+distinction: it _will not_ access the character at that position if you're
+using multi-byte characters.
+
+Strings support [slicing](/sentinel/language/slices) to efficiently create
+substrings.
+
+## List
+
+See [Lists](/sentinel/language/lists).
+
+## Map
+
+See [Maps](/sentinel/language/maps).
+
+## Rule
+
+See [Rules](/sentinel/language/rules).
+
+## Function
+
+See [Functions](/sentinel/language/functions).
+
+## Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a
+value of that type. Some examples are shown below followed by a list of exact
+rules for type conversion.
+
+```sentinel
+int(42) // 42
+int("42") // 42
+int(42.8) // 42
+int(true) // 1
+
+float(1.2) // 1.2
+float(1) // 1.0
+float("4.2") // 4.2
+float(true) // 1.0
+
+string("foo") // "foo"
+string(88) // "88"
+string(0xF) // "15"
+string(true) // "true"
+
+bool("true") // true
+bool(1) // true
+bool(-1) // true
+bool(0.1) // true
+bool("false") // false
+bool(0) // false
+```
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/language/variables.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/language/variables.mdx
new file mode 100644
index 0000000000..69655b0c63
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/language/variables.mdx
@@ -0,0 +1,100 @@
+---
+page_title: Sentinel Language - Variables
+sidebar_title: Variables
+sidebar_current: docs-language-vars
+description: Variables store values that can be used later.
+layout: docs
+---
+
+# Language: Variables
+
+Variables store values that can be used later.
+
+Most notably, a variable assignment of `main` is required for all
+Sentinel policies. This is the main [rule](/sentinel/language/rules) that
+is executed to determine the result of a policy. The `main` rule may
+use other variables that are assigned in the policy.
+
+Example:
+
+```sentinel
+a = 1
+b = a + 1
+```
+
+In this example, two variables are assigned: `a` and `b`. `a` is given
+the value of "1" and `b` uses the `a` to calculate its own value.
+
+## Syntax
+
+The syntax for assigning a variable is:
+
+```sentinel
+IDENTIFIER = VALUE
+```
+
+On the left of the equal sign is an _identifier_. This is a name for your
+variable and will be how you reference it later. An identifier is any
+combination of letters and digits, but must start with a letter. An
+underscore `_` is also a valid letter.
+
+On the right is any valid Sentinel value or expression. A value is a
+literal value such as a number or string. An expression is some computed
+value such as doing math, calling a function, etc.
+
+## Assignment and Reassignment
+
+A variable is _assigned_ when it is given a value. You can also _reassign_
+variables at any time by setting it to a new value. The new value takes
+effect for any subsequent use of that variable. A variable can be reassigned
+to a different type.
+
+For example:
+
+```sentinel
+a = 1 // a = 1
+b = a // b = 1
+a = "value" // a = "value", b = 1
+c = a // c = "value", b = 1
+```
+
+In the above example you can see that the variables are set and reassigned.
+Notice that the value of a variable is the _current_ value, and that reassigning
+a variable only affects _future_ uses of that variable. You can see this with
+`c` and `b` being different values.
+
+## Unassigned Variables
+
+Using a variable that is _unassigned_ is an error.
+
+In the example below, the first line would result in the policy erroring.
+Sentinel is executed top-down and the value of `c` is not available yet
+on the first line.
+
+```sentinel
+a = c // Error!
+c = 1
+```
+
+## Type Conversion
+
+While a topic more related to [values][lang-values], variables can be assigned
+(or-reassigned) a different value type via type conversion.
+
+[lang-values]: /sentinel/language/values
+
+```sentinel
+s = "1.1"
+a = int(s) // a = 1
+a = float(s) // a = 1.1
+
+a = 1
+s = string(a) // s = "1"
+```
+
+For more details, see the type conversion sections in the
+[values][lang-values-type-conversion] page, or the [Sentinel Language
+Specification][lang-spec-type-conversion].
+
+[lang-values-type-conversion]: /sentinel/language/values#type-conversion
+[lang-spec-type-conversion]: /sentinel/language/spec#type-conversion
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/nomad/index.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/nomad/index.mdx
new file mode 100644
index 0000000000..b519c1bf29
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/nomad/index.mdx
@@ -0,0 +1,50 @@
+---
+page_title: Nomad and Sentinel
+sidebar_title: Nomad
+sidebar_current: docs-app-nomad
+description: >-
+ Nomad Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies can currently execute on job
+ submission (creation, update).
+layout: docs
+---
+
+# Nomad
+
+[Nomad Enterprise](https://www.hashicorp.com/products/nomad/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies can currently execute on job submission (creation, update).
+
+Sentinel policies have full access to the job structure. This allows the
+Sentinel policy to control behavior based on any attribute within a job,
+such as the driver, resource requests, network configuration, volume
+configuration, and more. The information that Sentinel policies have access
+to will expand over time.
+
+Nomad fully supports all [enforcement levels](/sentinel/concepts/enforcement-levels).
+For soft mandatory policies, the `sentinel-override` capability must be
+available on the user's ACL policy to allow override. Overrides are always
+logged.
+
+The Nomad integration with Sentinel is documented in depth in the
+[Nomad Enterprise documentation](https://www.nomadproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Only allow Docker-based jobs.**
+
+```sentinel
+# Test policy only allows Docker based tasks
+main = rule { all_drivers_docker }
+
+# all_drivers_docker checks that all the drivers in use are Docker
+all_drivers_docker = rule {
+ all job.task_groups as tg {
+ all tg.tasks as task {
+ task.driver is "docker"
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/terraform/index.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/terraform/index.mdx
new file mode 100644
index 0000000000..4825ad24fe
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/terraform/index.mdx
@@ -0,0 +1,60 @@
+---
+page_title: Terraform and Sentinel
+sidebar_title: Terraform
+sidebar_current: docs-app-terraform
+description: >-
+ Sentinel can be used with Terraform and Terraform Enterprise to enforce policy
+ on Terraform configurations, states, and plans.
+layout: docs
+---
+
+# Terraform
+
+[Terraform Enterprise](https://www.hashicorp.com/products/terraform/) uses Sentinel to enforce
+policy on [Terraform](https://www.terraform.io) configurations, states, and plans.
+
+The Sentinel integration with Terraform runs within
+[Terraform Enterprise](https://www.hashicorp.com/products/terraform/)
+after a `terraform plan` and before a `terraform apply`. The policies
+have access to the created plan, the state at the time of the plan,
+and the configuration at the time of the plan.
+
+The Terraform integration with Sentinel is documented in depth in the [Terraform Enterprise documentation](https://www.terraform.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show basic examples.
+
+### Examples
+
+**Example: All AWS instances must have a tag**
+
+```sentinel
+import "tfplan"
+
+main = rule {
+ all tfplan.resources.aws_instance as _, instances {
+ all instances as _, r {
+ (length(r.applied.tags) else 0) > 0
+ }
+ }
+}
+```
+
+**Example: Only allow GCP instance sizes smaller than `n1-standard-16`**
+
+```sentinel
+import "tfplan"
+
+allowed_machine_types = [
+ "n1-standard-1",
+ "n1-standard-2",
+ "n1-standard-4",
+ "n1-standard-8",
+]
+
+main = rule {
+ all tfplan.resources.google_compute_instance as _, instances {
+ all instances as _, r {
+ r.applied.machine_type in allowed_machine_types
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/vault/index.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/vault/index.mdx
new file mode 100644
index 0000000000..9c37e0eedb
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/vault/index.mdx
@@ -0,0 +1,65 @@
+---
+page_title: Vault and Sentinel
+sidebar_title: Vault
+sidebar_current: docs-app-vault
+description: >-
+ Vault Enterprise uses Sentinel to augment the built-in policy system to
+ provide Role Governing Policies (RGPs) and Endpoint Governing Policies (EGPs)
+ to enable complex, flexible policies across identities and endpoints.
+layout: docs
+---
+
+# Vault
+
+[Vault Enterprise](https://www.hashicorp.com/products/vault/) uses Sentinel
+to augment the built-in policy system to provide Role Governing Policies (RGPs)
+and Endpoint Governing Policies (EGPs) to enable complex, flexible policies
+across identities and endpoints.
+
+**Role Governing Policies (RGPs)** are Sentinel policies that are tied to
+particular tokens, Identity entities, or Identity groups. They have access to
+a rich set of controls across various aspects of Vault. These are evaluated
+whenever a token they're attached to is used.
+
+**Endpoint Governing Policies (EGPs)** are Sentinel policies that are tied to
+particular paths instead of tokens. They have access to as much request
+information as possible, but they can take effect even on unauthenticated
+paths, such as login paths.
+
+The Vault integration with Sentinel is documented in depth in the
+[Vault Enterprise documentation](https://www.vaultproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Endpoint policy that requires MFA authentication from a corporate network.**
+
+```sentinel
+import "sockaddr"
+
+# We expect logins to come only from our private IP range
+cidrcheck = rule {
+ sockaddr.is_contained(request.connection.remote_addr, "10.20.0.0/16")
+}
+
+# Require Ping MFA validation to succeed
+ping_valid = rule {
+ mfa.methods.ping.valid
+}
+
+main = rule when request.path is "auth/ldap/login" {
+ ping_valid and cidrcheck
+}
+```
+
+**Example: Endpoint policy that disallows tokens created before a certain time.**
+
+```sentinel
+import "time"
+import "strings"
+
+main = rule when not strings.has_prefix(request.path, "auth/ldap/login") {
+ time.load(token.creation_time).unix > time.load("2017-09-17T13:25:29Z").unix
+}
+```
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/writing/basic.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/writing/basic.mdx
new file mode 100644
index 0000000000..1a881476a4
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/writing/basic.mdx
@@ -0,0 +1,107 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Basics
+sidebar_current: docs-writing-basic
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Policy Basics
+
+Sentinel policies are easy to write while still supporting advanced constructs
+for creating complex policies. This page will explain the basics of writing
+Sentinel policies to get started. You don't need any prior Sentinel knowledge,
+but we do recommend reading the
+[getting started guide](/sentinel/intro/getting-started/first-policy) and
+[language guide](/sentinel/language) after this.
+
+Sentinel policies are text files written using the [Sentinel language](/sentinel/language).
+The policies are evaluated top-to-bottom. The value of `main` after execution
+determines whether a policy passes or fails.
+
+## The Simplest Policy
+
+Sentinel only requires that a policy have a `main` variable that evaluates to
+a boolean value.
+
+A valid example is shown below:
+
+```sentinel
+main = 10 > 5
+```
+
+This type of minimal policy is not purely academic. In practice, simple
+policies can often be reduced to a single line logical statement resulting
+in `true` or `false`. However, the expression is usually wrapped in a
+[rule](/sentinel/language/rules) for testing reasons.
+
+You can verify Sentinel will execute this minimal policy using the CLI:
+
+```
+$ sentinel apply minimal.sentinel
+Pass
+```
+
+## Logical Expressions
+
+Policy is at its core a set of logic: you can or can not perform some action
+under a certain set of circumstances. Those circumstances are logical
+expressions. Therefore, Sentinel policies primarily translate into logical
+expressions.
+
+Detailed documentation on boolean expressions is
+[available in the language guide](/sentinel/language/boolexpr).
+
+A simple numerical comparison was seen in the first example on this page.
+Sentinel also provides inclusion operators such as `contains`, `any`, `all`, and
+more. Sentinel allows some operators to have aliases to promote readability
+while remaining programmer-familiar, such as `==` which can equivalently be `is`.
+
+The example below verifies that all numbers in a list are even:
+
+```sentinel
+numbers = [6, 22, 8, 4, 12]
+main = all numbers as n { n % 2 is 0 }
+```
+
+## Variables
+
+A policy will very often use variables. Applications such as
+[Nomad](https://www.nomadproject.io) inject variables into the global
+scope of a policy for making policy decisions. For example, Nomad injects
+the job that is being run into the policy scope. Knowing how to use
+variables is critical to effectively using Sentinel.
+
+Detailed documentation on how to define and access variables is
+[available in the language guide](/sentinel/language/variables).
+
+Variables can be defined and used explicitly. For example:
+
+```sentinel
+value = 10
+main = value > 5
+```
+
+But they may also be introduced implictly by the host system. Nomad
+injects `job` into policies to describe the job that is being run. The
+policy below is a valid policy that requires a job have two task groups.
+Notice that `job` is not defined anywhere. It is implicitly inserted by
+the host application (in this case Nomad). Refer to the application you're
+writing policy for to determine if it implicitly inserts values.
+
+```sentinel
+main = length(job.task_groups) is 2
+```
+
+## And more!
+
+The Sentinel language supports many more features such as functions,
+loops, and more. You can learn about all of this in the
+[complete language guide](/sentinel/language).
+
+The other pages in the [writing policy](/sentinel/writing)
+will cover other information you need to know about writing Sentinel
+policies that isn't simply a language reference.
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/writing/debugging.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/writing/debugging.mdx
new file mode 100644
index 0000000000..7311de7be3
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/writing/debugging.mdx
@@ -0,0 +1,46 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Debugging
+sidebar_current: docs-writing-debugging
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Debugging
+
+As policies become more complex, it becomes harder to understand exactly why
+a policy may be behaving differently than you expect.
+
+Prior to actively debugging, there are multiple best practices we recommend
+following. We recommend breaking your policy down into a set of
+[rules](/sentinel/writing/rules). This will make it easier to understand
+[traces](/sentinel/writing/tracing) and make it easier to
+[test](/sentinel/writing/testing) your policies.
+This should help to debug policies up to a significant complexity.
+
+## Print Logging
+
+The built-in [print function](/sentinel/language/logging-errors#print)
+can be used to log data. The print output is only shown during failures
+or when tracing is explicitly enabled.
+
+The print function is [variadic](https://en.wikipedia.org/wiki/Variadic_function).
+Each argument specifies a value to print together, concatenated with a space
+in between each. You can put any type as an argument and Sentinel will
+convert that to a human-friendly string format.
+
+Example:
+
+```sentinel
+value = 42
+print("the value is", value) // the value is 42
+
+map = { "foo": false }
+print(map) // { "foo": false }
+```
+
+The `print` function returns the value `true` so that it can also be
+used within rule expressions. Note that if short-circuiting is occuring
+within the boolean logic, the `print` function may never be reached.
+The runtime contains a special case where `print` will not short-circuit
+its own logic, so `print() or expr` will always evaluate `expr`.
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/writing/imports.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/writing/imports.mdx
new file mode 100644
index 0000000000..8e6280e5b1
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/writing/imports.mdx
@@ -0,0 +1,124 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Imports
+sidebar_current: docs-writing-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom import](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only local
+context for making policy decisions.
+
+Sentinel also comes with a set of [standard imports](/sentinel/imports).
+Standard imports are available to every Sentinel policy to help policy writers
+with common tasks such as working with the time, network addresses, and more.
+
+-> **This page is about writing policies that _use_ imports.** If you're
+interested in _creating_ a new import, please see the section on [extending
+Sentinel](/sentinel/extending) for information on how to write modules and
+import plugins.
+
+## Using Imports
+
+To use an import, you use the `import` keyword at the top of your policy. This
+specifies the name of the import you want to use. The application you're writing
+the policy for must already be
+[configured](/sentinel/extending/plugins#installing-plugins) to provide that
+import.
+
+Details on imports can be found in the
+[import section in the language reference](/sentinel/language/imports).
+
+In the example below, we use the [time](/sentinel/imports/time) import:
+
+```sentinel
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+There are two options to develop a policy locally when using imports:
+configure Sentinel to launch the import on `apply`, or mock the import
+using `mock`. The former requires access to the import plugin while the
+latter is faster (doesn't have to launch a process) and doesn't require
+the plugin.
+
+## Mocking Imports
+
+The first option to developing policies locally is to mock the import values.
+When mocking an import, you don't need the import plugin available. This can be
+useful since some imports may not be available as a plugin and may only be
+available to the application the policy runs in.
+
+Mocks are specified via the [configuration
+file](/sentinel/commands/config). Mocks can also be used for
+[testing](/sentinel/writing/testing).
+
+You can supply mock configuration one of two ways, depending on your use case:
+
+- **[Using static data](/sentinel/commands/config#mocking-static-data):**
+ Use this method when you can accurately represent your mock data in JSON and
+ do not need to mock complex Sentinel features such as functions.
+- **[Using Sentinel code](/sentinel/commands/config#mocking-with-sentinel-code):**
+ Use this method when using a static JSON object is insufficient, such as when
+ you need to mock functions or other complex Sentinel features.
+
+Our example does not require complex data to be mocked, so a static JSON object
+is sufficient:
+
+```json
+{
+ "mock": {
+ "time": {
+ "now": {
+ "hour": 12,
+ "weekday_name": "Tuesday"
+ }
+ }
+ }
+}
+```
+
+This can be used via the CLI:
+
+```
+$ sentinel apply -config=config.json policy.sentinel
+Pass
+```
+
+## Launching Imports
+
+If you have access to the plugin binary, you can launch the import. The
+benefit of this is that it is really using the import to test your
+policy. If the import changes, your policies may start failing. If you
+only use mock data and the import changes, your policies will still
+appear to work.
+
+Imports are configured in the configuration file:
+
+```json
+{
+ "imports": {
+ "time": {
+ "path": "/path/to/sentinel-time-import"
+ }
+ }
+}
+```
+
+This would require the `sentinel-time-import` binary. For this example
+this doesn't currently exist. We plan on writing one to provide for this
+section of the documentation.
+
+## Custom Imports
+
+You can also [create your own imports](/sentinel/extending).
+
+If your policy decisions could benefit from accessing external information,
+then you can use custom imports as a way to do this.
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/writing/index.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/writing/index.mdx
new file mode 100644
index 0000000000..e7743c0011
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/writing/index.mdx
@@ -0,0 +1,36 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Writing Policy
+sidebar_current: docs-writing
+description: >-
+ Sentinel provides a language and workflow for building policy across any
+ system that embeds Sentinel. By learning Sentinel once, you are able to
+ effectively control access to many systems using Sentinel's powerful features.
+ Basic concepts that are important to understand for Vault usage.
+layout: docs
+---
+
+# Writing Sentinel Policy
+
+This section covers how to write Sentinel policies.
+
+It is recommended that you complete the
+[getting started guide](/sentinel/intro/getting-started/first-policy) prior to
+reading this section of the documentation. The getting started guide will
+explain all the basics of writing and testing policies. This section of the
+documentation will serve as more of a reference guide to all available
+features of Sentinel.
+
+Sentinel provides a language and workflow for building policy across any system
+that embeds Sentinel. By learning Sentinel once, you are able to effectively
+control access to many systems using Sentinel's powerful features.
+
+Sentinel also provides a [local CLI](/sentinel/commands) for developing and testing
+Sentinel policies. This CLI can be integrated into a daily developer's workflow
+along with CI to treat [policy as code](/sentinel/concepts/policy-as-code).
+
+Sentinel uses its own [language](/sentinel/language) for writing
+policies. You can view a [language reference](/sentinel/language)
+as well as the [specification](/sentinel/language/spec) for details. You
+don't have to read those documents immediately, since the language should
+be easy enough to pick up throughout this section.
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/writing/rules.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/writing/rules.mdx
new file mode 100644
index 0000000000..a99b58a04e
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/writing/rules.mdx
@@ -0,0 +1,49 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Rules
+sidebar_current: docs-writing-rules
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Rules
+
+Rules form the basis of a policy by representing behavior that is either
+passing or failing (true or false). Rules are a first class language construct
+in Sentinel. A policy can and should be broken down into rules to aid with
+readability, testability, and performance.
+
+Rules provide:
+
+* **Readability:** A policy that is broken down into rules can be read
+ more easily. The logic becomes clearer to see for policy writers when
+ they come back to it.
+
+* **Debuggability:** When [tracing](/sentinel/writing/tracing) is enabled,
+ the trace is formatted by rule names. A policy that is broken down into
+ more rules is more easily debugging by noticing unexpected rule values.
+
+* **Testability:** [Policy testing](/sentinel/writing/testing) is
+ built on asserting the values of rules. By breaking logic down into
+ rules, you're able to more effectively test your policies.
+
+* **Performance:** As explained in the next section, rules are only
+ evaluated once on demand. This means that a rule referenced multiple
+ time only has a one-time performance cost. For complex logic, this
+ could result in improved performance.
+
+An example usage of rules is shown below:
+
+```sentinel
+is_sunny = rule { weather is "sunny" }
+is_wednesday = rule { day is "wednesday" }
+main = rule { is_sunny and is_wednesday }
+```
+
+Details about rules and their behavior can be found in
+[the language reference](/sentinel/language/rules). Rules have important
+behavior so we recommend reading the language reference on rules.
+Rules will be used throughout the remainder of the documentation.
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/writing/testing.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/writing/testing.mdx
new file mode 100644
index 0000000000..cb639c76d1
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/writing/testing.mdx
@@ -0,0 +1,340 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Testing
+sidebar_current: docs-writing-testing
+description: >-
+ Sentinel has a built-in test framework to validate a policy behaves as
+ expected.
+layout: docs
+---
+
+# Testing
+
+Sentinel has a built-in test framework to validate a policy behaves as expected.
+
+With an ever-increasing amount of automation surrounding technology,
+the guardrails provided by policies are a critical piece towards ensuring
+expected behavior. As a reliance on correct policy increases, it is important
+to test and verify policies.
+
+Testing is a necessary step to fully realize
+[policy as code](/sentinel/concepts/policy-as-code). Just as good software
+is well tested, a good set of policies should be equally well tested.
+
+## Test Folder Structure
+
+Policies are tested by asserting that [rules](/sentinel/writing/rules)
+are the expected values given a pre-configured input. Tests are run
+by executing the [test command](/sentinel/commands/test).
+
+Sentinel is opinionated about the folder structure required for tests.
+This opinionated structure allows testing to be as simple as running
+`sentinel test` with no arguments. Additionally, it becomes simple to test
+in a CI or add new policies.
+
+The structure Sentinel expects is `test//*.json` where ``
+is the name of your policy file without the file extension. Within
+that folder is a list of JSON files. Each JSON file represents a single
+test case. Therefore, each policy can have multiple tests associated with
+it.
+
+## Test Case Format
+
+Each JSON file within the test folder for a policy is a single test case.
+
+The JSON file is the same configuration format as the [CLI configuration
+file](/sentinel/commands/config). The format lets you define mock data, imports
+to use, and more. This mock data is the key piece in being able to test
+policies: you craft a specific scenario and assert your policy behaves as you
+expect.
+
+Test cases also use the special key `test` within the configuration file
+to assert the boolean value of [rules](/sentinel/writing/rules). If
+the `test` key is omitted, the policy is expected to pass (the same
+as asserting that the `main` rule is true). If the test key is specified,
+only the rules specified in the map will be asserted. This means if you
+omit `main`, then the final policy result is not asserted.
+
+Example with assertions:
+
+```json
+{
+ "param": {
+ "day": "monday",
+ "hour": 7
+ },
+
+ "test": {
+ "main": false,
+ "is_open_hours": false,
+ "is_weekday": true
+ }
+}
+```
+
+The configuration above specifies some parameter data, and asserts the result of
+some rules. This is the same configuration used in the example section below.
+
+## Example
+
+Lets use the following file as an example. Save this file to a directory
+and name it `policy.sentinel`. It can be named anything with the `sentinel`
+extension, but by naming it `policy.sentinel` your output should match
+the example output on this page.
+
+```sentinel
+// The day of the week.
+param day
+
+// The hour of the day.
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+### A Passing Test
+
+Next, let's define a single test case. Relative to where you saved
+the policy, create a file at the path `test/policy/good.json`.
+
+```json
+{
+ "param": {
+ "day": "monday",
+ "hour": 14
+ }
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/good.json
+```
+
+This passed because the policy passed. We didn't assert any specific
+rules. By not specifying any assertions, `test` expects the policy itself
+to fully pass.
+
+### A Failing Test
+
+Define another test case to fail. We want to verify our policy fails when expected, too.
+
+Save the following as `test/policy/7-am.json`:
+
+```json
+{
+ "param": {
+ "day": "monday",
+ "hour": 7
+ }
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+FAIL - policy.sentinel
+ FAIL - test/policy/7-am.json
+ expected "main" to be true, got: false
+
+ trace:
+ FALSE - policy.sentinel:9:1 - Rule "main"
+ FALSE - policy.sentinel:9:15 - is_open_hours
+ FALSE - policy.sentinel:8:24 - hour > 8 and hour < 17
+ FALSE - policy.sentinel:8:24 - hour > 8
+
+ FALSE - policy.sentinel:8:1 - Rule "is_open_hours"
+ FALSE - policy.sentinel:8:24 - hour > 8
+ PASS - test/policy/good.json
+```
+
+As you can see, the test fails because "main" is false. This is good
+because the policy _should_ have failed since we specified an invalid
+hour. But, we expect main to be false and don't want our test to fail!
+Update `7-am.json` to add test assertions:
+
+```json
+{
+ "param": {
+ "day": "monday",
+ "hour": 7
+ },
+
+ "test": {
+ "main": false,
+ "is_open_hours": false,
+ "is_weekday": true
+ }
+}
+```
+
+And when we run the tests:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/7-am.json
+ PASS - test/policy/good.json
+```
+
+The test passes. We asserted that we expect the `main` rule to be false,
+the `is_open_hours` rule to be false, and the `is_weekday` rule to be
+true. By asserting some rules are true, we can verify that our policy
+is failing for reasons we expect.
+
+## Mocking
+
+The above example demonstrates how to test by supplying different
+[parameters](/sentinel/language/parameters). Parameters in a policy can be
+specifically useful when you want to control user-defined input values to a
+policy.
+
+However, generally, when testing, you will need mimic the conditions you will
+see in production. Production implementations of Sentinel will supply data using
+one of two methods:
+
+- **Global data:** Data is injected directly into the policy's scope and is
+ accessible using normal identifiers, similar to variables.
+- **Imports:** Data is stored behind an [import](/sentinel/language/imports)
+ and loaded on demand as needed by the policy author.
+
+Proper testing of a policy requires that these values be able to be _mocked_ -
+or, in other words, simulated in a way that allows the accurate testing of the
+scenarios that a policy could reasonably pass or fail under.
+
+Mocking both globals and imports can be done by setting various parts of the
+configuration file.
+
+### Mocking Globals
+
+Demonstrating the mocking of globals can be seen by making a few modifications to
+our example policy, removing the `param` declarations:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Then, change the `param` section in the configuration file to
+[`global`](/sentinel/commands/config#global).
+
+```json
+{
+ "global": {
+ "day": "monday",
+ "hour": 14
+ }
+}
+```
+
+This test should still pass, as if nothing had happened, although what we've
+done is shifted our parameters to globals, simulating an environment where `day`
+and `hour` are already defined for us.
+
+### Mocking Imports
+
+To mock imports, we need to use the
+[`mock`](/sentinel/commands/config#mock-imports) section of the
+configuration file.
+
+Let's say the above example is behind an import named `time`.
+
+-> **NOTE:** [`time`](/sentinel/imports/time) is a valid standard import.
+This example may not be accurate to the
+import's syntax.
+
+The code now looks like this:
+
+```sentinel
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To mock this import, we can [mock it as static
+data](/sentinel/commands/config#mocking-static-data). The configuration
+file now looks like, without assertions:
+
+```json
+{
+ "mock": {
+ "time": {
+ "now": {
+ "weekday_name": "Monday",
+ "hour": 14
+ }
+ }
+ }
+}
+```
+
+The policy will now pass, with the `time` import mocked.
+
+Data can also be [mocked as Sentinel
+code](/sentinel/commands/config#mocking-with-sentinel-code). In this case,
+the above configuration file would look like:
+
+```json
+{
+ "mock": {
+ "time": "mock-time.sentinel"
+ }
+}
+```
+
+And a file named `mock-time.sentinel` would now hold your mock values:
+
+```sentinel
+day = "monday"
+hour = 14
+```
+
+Mocking as Sentinel code allows more complex details to be mocked as well, such
+as functions. Say we wanted to mock the `time.load()` function. To mock this,
+just add it to the `mock-time.sentinel` file:
+
+```sentinel
+load = func(_) {
+ return {
+ "weekday_name": "Monday",
+ "hour": 14,
+ }
+}
+```
+
+Your code can now be written as:
+
+```sentinel
+import "time"
+
+t = time.load("a_mock_timestamp")
+
+is_weekday = rule { t.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { t.hour > 8 and t.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To see more details, see the [Mock
+Imports](/sentinel/commands/config#mock-imports) section in the
+configuration file.
+
+## Test Automation
+
+The `sentinel test` command was designed to be automation friendly.
+We encourage you to enable a CI such as [Travis CI](https://travis-ci.com/)
+on your policy repositories to continuously run tests. An example
+`.travis.yml` configuration file is shown below:
+
+```yaml
+language: bash
+script: sentinel test
+```
diff --git a/content/sentinel/v0.15.x/content/sentinel/docs/writing/tracing.mdx b/content/sentinel/v0.15.x/content/sentinel/docs/writing/tracing.mdx
new file mode 100644
index 0000000000..c7261a1e5c
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/docs/writing/tracing.mdx
@@ -0,0 +1,124 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Traces
+sidebar_current: docs-writing-tracing
+description: >-
+ Sentinel provides execution traces to better understand the execution of a
+ policy.
+layout: docs
+---
+
+# Traces
+
+Sentinel provides execution traces to better understand the execution of a policy.
+
+When using the Sentinel [CLI](/sentinel/commands), traces are shown
+on policy failure during `apply` or `test`, or when the `-trace` flag is
+explicitly specified. For Sentinel-enabled applications, traces are optional
+and may require special configuration to enable. Most Sentinel-enabled
+applications log traces on failure.
+
+-> **Note:** We're actively improving Sentinel tracing in each release
+to provide better data and improve readability. The exact format of a
+trace may not match the Sentinel version embedded in the application you're
+using, but the data should be similar.
+
+## Analyzing a Trace
+
+To show traces, let's use the example policy below with the CLI:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Let's cause the policy to fail so the trace is more interesting.
+If you're on a terminal that supports it, the trace output will be colored
+so you can more clearly see passes, failures, and rules.
+
+```
+$ sentinel apply -global 'day=sunday' -global 'hour=14' policy.sentinel
+Fail
+
+FALSE - policy.sentinel:6:1 - Rule "main"
+ TRUE - policy.sentinel:6:15 - is_open_hours
+ TRUE - policy.sentinel:5:24 - hour > 8 and hour < 17
+ TRUE - policy.sentinel:5:24 - hour > 8
+ TRUE - policy.sentinel:5:37 - hour < 17
+ FALSE - policy.sentinel:6:33 - is_weekday
+ FALSE - policy.sentinel:4:21 - day not in ["saturday", "sunday"]
+
+TRUE - policy.sentinel:5:1 - Rule "is_open_hours"
+ TRUE - policy.sentinel:5:24 - hour > 8
+ TRUE - policy.sentinel:5:37 - hour < 17
+
+FALSE - policy.sentinel:4:1 - Rule "is_weekday"
+```
+
+The least-nested values are all rules. It begins with the
+value of that rule (`TRUE` or `FALSE`) followed by the location and name
+of the rule. Notice that the `main` rule is false, the `is_open_hours`
+rule is true, and the `is_weekday` rule is false.
+
+We know that main requires _both_ `is_open_hours` and `is_weekday` to be
+true, so based on the trace, we can tell that main failed because
+`is_weekday` was false. We can then go to the definition of `is_weekday`
+in the source and determine the logical failure.
+
+The trace also breaks down more complex logical expressions. Notice that
+`is_open_hours` is split into the two sides of the `and`, but `is_weekday`
+has no values under it. Because `is_weekday` is itself a single boolean
+expression, the failure isn't repeated. But for `is_open_hours`, you can also
+see the result of each side of the boolean expression.
+
+## Missing Rules or Expressions
+
+The Sentinel runtime sometimes optimizes away executions completely. This
+can result in a trace being incomplete. If you don't see a rule or boolean
+expression within the trace, it means the runtime didn't execute it at all.
+
+Consider the example policy:
+
+```sentinel
+main = rule { 42 > 40 or 10 < 5 }
+```
+
+If we run it with trace:
+
+```
+$ sentinel apply -trace policy.sentinel
+TRUE - policy.sentinel:1:1 - Rule "main"
+ TRUE - policy.sentinel:1:15 - 42 > 40
+```
+
+Notice that the expression `10 < 5` is not present in the trace.
+Since `42 > 40` is true, the entire `or` can be short-circuited and
+the result is known to be true. Remaining parts of the expression are not
+executed and are therefore not present in the trace.
+
+## Policy Description
+
+An additional feature that can be utilised within a trace is the output of
+a policy description. By placing a comment at the top of a policy file,
+ensuring that there is a space between itself and any following content,
+a description will be added to the trace output.
+
+The following policy:
+
+```sentinel
+// Ensure 42 is greater than 40, or 10 is less than 5
+
+main = rule { 42 > 40 or 10 < 5 }
+```
+
+Will output:
+
+```
+$ sentinel apply -trace policy.sentinel
+Description:
+ Ensure 42 is greater than 40, or 10 is less than 5
+
+TRUE - policy.sentinel:1:1 - Rule "main"
+ TRUE - policy.sentinel:1:15 - 42 > 40
+```
diff --git a/content/sentinel/v0.15.x/content/sentinel/intro/getting-started/first-policy.mdx b/content/sentinel/v0.15.x/content/sentinel/intro/getting-started/first-policy.mdx
new file mode 100644
index 0000000000..e0cc4cef50
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/intro/getting-started/first-policy.mdx
@@ -0,0 +1,54 @@
+---
+page_title: Your First Sentinel Policy
+sidebar_title: Your First Policy
+sidebar_current: intro-getting-started-first
+description: Let's begin by writing a working Sentinel policy.
+layout: intro
+---
+
+# Your First Sentinel Policy
+
+Sentinel is a system to enforce complex policies on an integrated application.
+
+Writing Sentinel policy requires minimal programming experience. The Sentinel
+language is designed to be approachable and learned quickly and easily. Whether
+you're a professional programmer or someone who uses SQL and Excel, you can
+learn to write Sentinel policies.
+
+Let's begin by writing a simple, working Sentinel policy:
+
+```sentinel
+hour = 4
+main = rule { hour >= 0 and hour < 12 }
+```
+
+This is a valid Sentinel policy. It will pass since we hardcoded the
+`hour` to be 4. In a real system, `hour` may be something that is provided
+to us and actually set to the current hour. We'll learn more about that later.
+
+For now, try running this policy locally. Save the above example to a file
+named `policy.sentinel` and execute it. Then, modify the policy to make it
+fail. Play around more if you'd like.
+
+```
+$ sentinel apply policy.sentinel
+Pass
+```
+
+## Main
+
+Every Sentinel policy must have a `main` rule. This is the rule that
+is evaluated to determine the result of a policy. A rule describes a
+condition that must be true for the rule to be true. Within the rule,
+a single boolean expression describes the condition. In the example above,
+it is a straightforward check that the time is greater than zero (midnight)
+and less than 12.
+
+It is easy to imagine that such a rule might be used in a system such
+as [Nomad](https://www.nomadproject.io) to restrict the times when a
+deploy can occur. The power of arbitrary logical statements within Sentinel
+allows Sentinel policies to restrict almost any behavior.
+
+Next, we'll
+[introduce and explain rules](/sentinel/intro/getting-started/rules)
+so we can use them in our policies.
diff --git a/content/sentinel/v0.15.x/content/sentinel/intro/getting-started/imports.mdx b/content/sentinel/v0.15.x/content/sentinel/intro/getting-started/imports.mdx
new file mode 100644
index 0000000000..b2b3eb94e7
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/intro/getting-started/imports.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Imports
+sidebar_title: Imports
+sidebar_current: intro-getting-started-imports
+description: Imports enable Sentinel to access external data and functions.
+layout: intro
+---
+
+# Imports
+
+Imports enable Sentinel to access external data and functions.
+
+Sentinel ships with a set of [standard imports](/sentinel/imports).
+Standard imports are available by default to every Sentinel policy. Note that
+the application embedding Sentinel may whitelist or blacklist the available
+set of imports.
+
+To use an import, use the `import` statement. Then, access data and
+functions on the import using the import name followed by a period and
+the field you want to access. The example below checks whether the request
+path starts with a prefix. Assume that `request_path` is available.
+
+```sentinel
+import "strings"
+
+main = rule { strings.has_prefix(request_path, "/admin") }
+```
+
+## External Data
+
+The true power in imports is their ability to reference external data.
+Imports can be added to a Sentinel-enabled application through
+[plugins](/sentinel/extending). This enables any Sentinel-enabled
+application to make policy decision based on any source of data.
+
+This ability allows the policy system to enforce almost any necessary
+organizational policy, since the ability of a policy isn't restricted
+purely to the embedding application's data model.
+
+For example, policies in [Nomad](https://www.nomadproject.io) can
+access data in [Consul](https://www.consul.io) to determine attributes
+of a policy. In the example below, we use a hypothetical Consul import:
+
+```sentinel
+import "consul"
+
+main = rule {
+ job.tasks[0].resources.memory <= int(consul.get("policy/nomad/max-memory"))
+}
+```
+
+In this example, a Nomad job's memory usage is limited to the value of
+a Consul key/value item. By simply changing a Consul KV entry, policy
+can be changed. Imports can do anything, you can [write your own import plugins](/sentinel/extending)
+to extend Sentinel.
+
+Next, we'll learn how to
+[test policies](/sentinel/intro/getting-started/testing)
+to ensure our policy logic is correct.
diff --git a/content/sentinel/v0.15.x/content/sentinel/intro/getting-started/index.mdx b/content/sentinel/v0.15.x/content/sentinel/intro/getting-started/index.mdx
new file mode 100644
index 0000000000..0942cdb637
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/intro/getting-started/index.mdx
@@ -0,0 +1,17 @@
+---
+page_title: Install Sentinel CLI - Getting Started
+sidebar_title: Getting Started
+sidebar_current: intro-getting-started-overview
+description: The first step to using Sentinel is to get the CLI installed.
+layout: intro
+---
+
+# Getting Started With Sentinel
+
+This section covers getting started with Sentinel. Here, we will take you
+through the first steps that you will need to do to get started with installing
+the Sentinel CLI, writing your first policy, and getting familiar with rules,
+boolean logic, imports, and testing.
+
+You can select a topic on the menu to get started. The first step is to [install
+the Sentinel CLI](/sentinel/intro/getting-started/install).
diff --git a/content/sentinel/v0.15.x/content/sentinel/intro/getting-started/install.mdx b/content/sentinel/v0.15.x/content/sentinel/intro/getting-started/install.mdx
new file mode 100644
index 0000000000..4c3959d617
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/intro/getting-started/install.mdx
@@ -0,0 +1,57 @@
+---
+page_title: Install Sentinel CLI - Getting Started
+sidebar_title: Installing the CLI
+sidebar_current: intro-getting-started-install
+description: The first step to using Sentinel is to get the CLI installed.
+layout: intro
+---
+
+# Install Sentinel CLI
+
+Sentinel is a policy framework that is embedded in the enterprise versions of
+HashiCorp tools. The policies you write are deployed to these applications and
+enforced there.
+
+The Sentinel CLI is a command-line interface for local development and testing.
+For the getting started guide, we'll use the CLI to learn how to write policies
+for Sentinel-enabled applications. The Sentinel CLI is distributed as a [binary
+package](/sentinel/downloads) for all supported platforms and architectures.
+
+## Installation Instructions
+
+To install the Sentinel CLI, find the [appropriate package](/sentinel/downloads) for your
+system and download it. The CLI is packaged as a zip archive.
+
+After downloading Sentinel, unzip the package. The CLI runs as a single binary
+named `sentinel`. Any other files in the package can be safely removed and
+Sentinel will still function.
+
+The final step is to make sure that the `sentinel` binary is available on the `PATH`.
+See [this page](https://stackoverflow.com/questions/14637979/how-to-permanently-set-path-on-linux)
+for instructions on setting the PATH on Linux and Mac.
+[This page](https://stackoverflow.com/questions/1618280/where-can-i-set-path-to-make-exe-on-windows)
+contains instructions for setting the PATH on Windows.
+
+## Verifying the Installation
+
+After installing the Sentinel CLI, verify the installation worked by opening a
+new terminal session and checking that the `sentinel` binary is available. By
+executing `sentinel`, you should see help output similar to the following:
+
+```
+$ sentinel
+Usage: sentinel [--version] [--help] []
+
+Available commands are:
+ apply Execute a policy and output the result
+ fmt Format Sentinel policy to a canonical format
+ test Test policies
+ version Prints the Sentinel version
+```
+
+If you get an error that the binary could not be found, then your `PATH`
+environment variable was not setup properly. Please go back and ensure that your
+`PATH` variable contains the directory where Sentinel was installed.
+
+Otherwise, the CLI is installed and ready to go. Let's celebrate by [writing our
+first policy!](/sentinel/intro/getting-started/first-policy)
diff --git a/content/sentinel/v0.15.x/content/sentinel/intro/getting-started/logic.mdx b/content/sentinel/v0.15.x/content/sentinel/intro/getting-started/logic.mdx
new file mode 100644
index 0000000000..893d63c851
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/intro/getting-started/logic.mdx
@@ -0,0 +1,55 @@
+---
+page_title: Logic
+sidebar_title: Logic
+sidebar_current: intro-getting-started-logic
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Logic
+
+The core of a policy is a set of logical expressions to determine whether
+a behavior is allowed or not.
+Sentinel makes it easy to write readable logical expressions to express
+the intended policy.
+
+The logical expression syntax will be familiar to anyone who has programmed
+before. Sentinel also supports a couple more unique constructs to assist
+with common policy requirements. Detailed documentation on how to write
+logical expressions and the supported operators is available in
+[the boolean expression reference](/sentinel/language/boolexpr).
+
+Example logic:
+
+```sentinel
+a is b // Equality, you can also use ==
+a is not b // Inequality, you can also use !=
+
+a > b // Relational operators, comparison
+a < b
+a >= b
+a <= b
+
+a contains b // Inclusion check, substrings, etc.
+a not contains b // Negated version of above
+```
+
+The full list of available operators can be found in
+[the boolean expression reference](/sentinel/language/boolexpr).
+
+Logic can be combined with `and` or `or`. When combined with `and`, both
+sides must result in `true`. When combined with `or`, only one side needs
+to be true to result in `true`.
+
+With these building blocks, basic rules can be built up:
+
+```sentinel
+main = rule {
+ hour >= 8 and
+ hour < 17
+}
+```
+
+Next, we'll introduce
+[imports](/sentinel/intro/getting-started/imports)
+so that we can write rules that interact with external data.
diff --git a/content/sentinel/v0.15.x/content/sentinel/intro/getting-started/next-steps.mdx b/content/sentinel/v0.15.x/content/sentinel/intro/getting-started/next-steps.mdx
new file mode 100644
index 0000000000..8c01c3e6f3
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/intro/getting-started/next-steps.mdx
@@ -0,0 +1,21 @@
+---
+page_title: Next Steps
+sidebar_current: intro-getting-started-nextsteps
+description: That concludes the getting started guide for Sentinel.
+layout: intro
+---
+
+# Next Steps
+
+That concludes the getting started guide for Sentinel. Hopefully you're excited
+about the possibilities of Sentinel and ready to put this knowledge to use to
+improve the safety of your environments.
+
+We've covered the basics of the core features of Sentinel in this guide.
+We recommend the following as next steps:
+
+* [Documentation](/sentinel) - The documentation is an in-depth
+ reference guide of all the features of Sentinel.
+
+* [Language Reference](/sentinel/language) - The language reference
+ is a complete reference to the features of the Sentine policy language.
diff --git a/content/sentinel/v0.15.x/content/sentinel/intro/getting-started/rules.mdx b/content/sentinel/v0.15.x/content/sentinel/intro/getting-started/rules.mdx
new file mode 100644
index 0000000000..6a72af190e
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/intro/getting-started/rules.mdx
@@ -0,0 +1,73 @@
+---
+page_title: Rules
+sidebar_title: Rules
+sidebar_current: intro-getting-started-rules
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Rules
+
+A rule describes a set of conditions that result in either true or false.
+
+Rules in Sentinel are a first-class concept. They are used for making complex
+logic more understandable by breaking it down into a set of rules, for
+testing policies by asserting certain rules are passed, and more.
+
+## Writing Rules
+
+Let's look at the Sentinel policy we wrote in the previous section:
+
+```sentinel
+hour = 4
+main = rule { hour >= 0 and hour < 12 }
+```
+
+The logic in this policy is straightforward and easy to understand. However,
+it is common for policy logic to become much more complicated. Rules are the
+key abstraction for making complex logic understandable. We can turn the
+policy above into a set of rules:
+
+```sentinel
+hour = 4
+
+past_midnight = rule { hour >= 0 }
+before_noon = rule { hour < 12 }
+
+main = rule {
+ past_midnight and
+ before_noon
+}
+```
+
+This policy is easier to read. Our original policy was already quite
+simple, but it is easy to imagine a more complex policy becoming much
+more readable by extracting logical expressions into a set of rules.
+
+Rules don't just exist to make a policy more readable. They're also a
+testing and debugging point. For testing, you can assert that certain rules
+are certain values given a specific input to verify that your policy works
+as you expect. Testing and debugging are covered in later sections.
+
+## Conditional Rules
+
+In many cases, a rule is only valid when something else is also true.
+
+Consider the human language statement "before you eat, you must wash your hands."
+The rule "you must wash your hands" is only valid "before you eat". In any
+other scenario, the rule is meaningless.
+This is also true in a technical context. Imagine the rule "if the driver
+is Docker, you must not expose any ports." For this example, assume
+rules `is_docker` and `has_no_exposed_ports` exist. You can write this rule
+like this:
+
+```sentinel
+main = rule when is_docker { has_no_exposed_ports }
+```
+
+When `is_docker` is true, the rule is evaluated as normal. However,
+when `is_docker` is false, the rule always returns `true`, because the
+logic of the rule is meaningless.
+
+Let's learn how to create more complex rules with
+[logical expressions](/sentinel/intro/getting-started/logic).
diff --git a/content/sentinel/v0.15.x/content/sentinel/intro/getting-started/testing.mdx b/content/sentinel/v0.15.x/content/sentinel/intro/getting-started/testing.mdx
new file mode 100644
index 0000000000..ee245f1c0e
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/intro/getting-started/testing.mdx
@@ -0,0 +1,70 @@
+---
+page_title: Testing
+sidebar_current: intro-getting-started-testing
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Testing
+
+It is important to ensure the policies you write are correct. Sentinel
+includes a built-in test framework that can be run locally and in CI
+environments to test that policies behave as expected.
+
+A common pitfall with many simple ACL systems is that they provide no easy
+way to verify their correctness. You basically have to set the ACL and try
+the behaviors against a real system to verify it is working as expected.
+This requires a lot of setup and is unique to each system.
+
+[Sentinel's built-in test framework](/sentinel/writing/testing) has
+zero dependencies. It is a single binary that can mock the data that real
+systems are exposing to the policy. It is designed to be CI-friendly and
+enables continuous testing of your policies. This is necessary for [policy
+as code](/sentinel/concepts/policy-as-code).
+
+Detailed documentation on testing policies is available in [the Sentinel Testing reference](/sentinel/writing/testing).
+
+## Writing Tests
+
+For this example, save the following policy as `policy.sentinel`:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Next, let's write a passing test case. This will test that the policy tests
+when we expect it to pass. Save the following in `test/policy/good.json`:
+
+```json
+{
+ "global": {
+ "day": "monday",
+ "hour": 14
+ }
+}
+```
+
+And run `sentinel test`:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/good.json
+```
+
+The `sentinel test` command will automatically find all Sentinel policies
+and their associated test cases and run them all. The test framework will
+run all JSON files as individual test cases, allowing you to test a variety
+of scenarios for your policies.
+
+Try adding another test case to force the policy to fail. This test case can
+be saved at `test/policy/fail.json`. For test cases with intentional
+failures, you'll need to use the [test assertions described in the testing
+reference](/sentinel/writing/testing#a-failing-test).
+
+Now that you have a good grasp of what Sentinel is and how to use it, please feel
+free to look through the
+[next steps](/sentinel/intro/getting-started/next-steps)
+you can take to further your Sentinel knowledge.
diff --git a/content/sentinel/v0.15.x/content/sentinel/intro/index.mdx b/content/sentinel/v0.15.x/content/sentinel/intro/index.mdx
new file mode 100644
index 0000000000..785a442fb8
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/intro/index.mdx
@@ -0,0 +1,32 @@
+---
+layout: intro
+page_title: Documentation
+sidebar_current: docs-home
+description: >-
+ Sentinel is a language and framework for policy built to be embedded in
+ existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+[introduction](/sentinel/intro) to understand what Sentinel is, how it
+compares to other software, and more.
+
+
diff --git a/content/sentinel/v0.15.x/content/sentinel/intro/what/index.mdx b/content/sentinel/v0.15.x/content/sentinel/intro/what/index.mdx
new file mode 100644
index 0000000000..fba1f3a0e8
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/intro/what/index.mdx
@@ -0,0 +1,65 @@
+---
+page_title: Introduction
+sidebar_title: What is Sentinel?
+sidebar_current: intro-what
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: intro
+---
+
+# Introduction to Sentinel
+
+Welcome to the intro guide to Sentinel! This guide is the best
+place to start with Sentinel.
+
+If you are already familiar with the basics of Sentinel, the
+[documentation](/sentinel) provides a better reference
+guide for all available features as well as internals.
+
+## What is Sentinel?
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+Sentinel provides a language for writing policy and a workflow for developing
+and testing policies independent of the software they'll be written to. We
+also provide a framework for developers to build plugins that allow a
+Sentinel-enabled system to access external information to make policy
+decisions.
+
+Most systems today have some degree of access control. You are able to define
+identities and what they have access to. These ACL systems solve an immediate
+and necessary problem of locking down a system in very broad strokes. Sentinel
+is a reusable system for more advanced software policy decisions. Sentinel
+enables:
+
+* **Fine-Grained Policy**: Most ACL systems only enable coarse-grained
+ behaviors: "read", "write", etc. Sentinel enables fine-grained behavior such
+ as disallowing a certain API call when specific parameters are present.
+
+* **Logic-Based Policy**: You can write policy using full
+ conditional logic. For example, you may only allow a certain application behavior
+ on Monday to Thursday unless there is a manager override.
+
+* **Accessing External Information**: Sentinel can source external information
+ to be used in policy decisions. For example, a policy that restricts the size
+ of a payload may read data from [Consul](https://www.consul.io) to determine
+ the payload size limit.
+
+* **Enforcement levels**: Sentinel allows policies to be defined along
+ with an "enforcement level" that dictates the pass/fail behavior of a policy.
+ Advisory policies warn if they fail, soft mandatory policies can have their
+ failures overridden, and hard mandatory policies must pass under all
+ circumstances. Having this as a built-in concept enables you to model
+ policy more accurately and completely for your organization.
+
+## Next Steps
+
+See the page on [Why Sentinel?](/sentinel/intro/why) to learn more
+about the origins of the product. Then, continue onwards with
+the [getting started guide](/sentinel/intro/getting-started/install) to write
+your first policies and see how Sentinel works in practice.
diff --git a/content/sentinel/v0.15.x/content/sentinel/intro/why/index.mdx b/content/sentinel/v0.15.x/content/sentinel/intro/why/index.mdx
new file mode 100644
index 0000000000..37e01c6fc3
--- /dev/null
+++ b/content/sentinel/v0.15.x/content/sentinel/intro/why/index.mdx
@@ -0,0 +1,50 @@
+---
+page_title: Why Sentinel?
+sidebar_title: Why Sentinel?
+sidebar_current: intro-why
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: intro
+---
+
+# Why Sentinel?
+
+The growth of infrastructure and applications has been enabled in part
+by an increasing trend towards automation everywhere.
+Configuration as code (such as Chef or Puppet with [Packer](https://www.packer.io))
+has enabled automated machine configuration. Infrastructure
+as code (such as [Terraform](https://www.terraform.io)) has enabled
+automatic infrastructure creation. And schedulers (such as
+[Nomad](https://www.nomadproject.io) and Kubernetes) have enabled
+automatic application deployment.
+Sentinel enables guardrails to be put in place
+on automation while allowing the codification and automatic enforcement
+of business requirements in critical areas of your infrastructure.
+
+Meanwhile, businesses have business requirements and sometimes legal
+requirements which must be expressed in policies. Traditionally, these
+policies are enforced by humans. But in a highly automated world, the
+automation is only as fast as its slowest component. In many cases, this
+is the human verification step.
+
+As an example: before infrastructure as code and autoscaling, if an order
+came through for 5,000 new machines, a human would likely respond to the
+ticket verifying that the user really intended to order 5,000 new machines.
+Today, automation can almost always freely order 5,000 new compute instances
+without any hesitation, which can result in unintended expense or system
+instability.
+
+Sentinel introduces [policy as code](/sentinel/concepts/policy-as-code)
+and a powerful framework built-in to HashiCorp tooling to allow automation
+guardrails, business requirements, legal compliance, and more to be
+actively enforced by the running systems in realtime.
+
+With Sentinel, you can require an override to create certain numbers of
+infrastructure resources. You can disallow unsafe deployment configurations
+with Nomad. You can enforce certain key/value formats in Consul. You
+can restrict secret access by time in Vault. And more.
+
+Sentinel is available today in HashiCorp enterprise products. You can
+try Sentinel immediately with the [getting started guide](/sentinel/intro/getting-started/install) or learn more about the integrations in the
+[documentation](/sentinel).
diff --git a/content/sentinel/v0.15.x/data/docs-nav-data.json b/content/sentinel/v0.15.x/data/docs-nav-data.json
new file mode 100644
index 0000000000..40c9ff2fd0
--- /dev/null
+++ b/content/sentinel/v0.15.x/data/docs-nav-data.json
@@ -0,0 +1,324 @@
+[
+ {
+ "title": "Release Notes",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "changelog"
+ }
+ ]
+ },
+ {
+ "title": "Basic Concepts",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "concepts"
+ },
+ {
+ "title": "Policy as Code",
+ "path": "concepts/policy-as-code"
+ },
+ {
+ "title": "Policy Language",
+ "path": "concepts/language"
+ },
+ {
+ "title": "Imports",
+ "path": "concepts/imports"
+ },
+ {
+ "title": "Enforcement Levels",
+ "path": "concepts/enforcement-levels"
+ }
+ ]
+ },
+ {
+ "title": "Commands (CLI)",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "commands"
+ },
+ {
+ "title": "Configuration File Syntax",
+ "path": "commands/config"
+ },
+ {
+ "title": "apply",
+ "path": "commands/apply"
+ },
+ {
+ "title": "fmt",
+ "path": "commands/fmt"
+ },
+ {
+ "title": "test",
+ "path": "commands/test"
+ }
+ ]
+ },
+ {
+ "title": "Writing Policy",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "writing"
+ },
+ {
+ "title": "Basics",
+ "path": "writing/basic"
+ },
+ {
+ "title": "Rules",
+ "path": "writing/rules"
+ },
+ {
+ "title": "Traces",
+ "path": "writing/tracing"
+ },
+ {
+ "title": "Testing",
+ "path": "writing/testing"
+ },
+ {
+ "title": "Imports",
+ "path": "writing/imports"
+ },
+ {
+ "title": "Debugging",
+ "path": "writing/debugging"
+ }
+ ]
+ },
+ {
+ "title": "Extending Sentinel",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "extending"
+ },
+ {
+ "title": "Modules",
+ "path": "extending/modules"
+ },
+ {
+ "title": "Plugins",
+ "path": "extending/plugins"
+ },
+ {
+ "title": "Internals",
+ "path": "extending/internals"
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Language",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "language"
+ },
+ {
+ "title": "Variables",
+ "path": "language/variables"
+ },
+ {
+ "title": "Values",
+ "path": "language/values"
+ },
+ {
+ "title": "Lists",
+ "path": "language/lists"
+ },
+ {
+ "title": "Maps",
+ "path": "language/maps"
+ },
+ {
+ "title": "Rules",
+ "path": "language/rules"
+ },
+ {
+ "title": "Imports",
+ "path": "language/imports"
+ },
+ {
+ "title": "Parameters",
+ "path": "language/parameters"
+ },
+ {
+ "title": "Boolean Expressions",
+ "path": "language/boolexpr"
+ },
+ {
+ "title": "Arithmetic",
+ "path": "language/arithmetic"
+ },
+ {
+ "title": "Slices",
+ "path": "language/slices"
+ },
+ {
+ "title": "Conditionals",
+ "path": "language/conditionals"
+ },
+ {
+ "title": "Loops",
+ "path": "language/loops"
+ },
+ {
+ "title": "Collection Operations",
+ "path": "language/collection-operations"
+ },
+ {
+ "title": "Functions",
+ "path": "language/functions"
+ },
+ {
+ "title": "Scope",
+ "path": "language/scope"
+ },
+ {
+ "title": "Undefined",
+ "path": "language/undefined"
+ },
+ {
+ "title": "Logging and Errors",
+ "path": "language/logging-errors"
+ },
+ {
+ "title": "Specification",
+ "path": "language/spec"
+ }
+ ]
+ },
+ {
+ "title": "Builtin Functions",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "functions"
+ },
+ {
+ "title": "append",
+ "path": "functions/append"
+ },
+ {
+ "title": "delete",
+ "path": "functions/delete"
+ },
+ {
+ "title": "error",
+ "path": "functions/error"
+ },
+ {
+ "title": "keys",
+ "path": "functions/keys"
+ },
+ {
+ "title": "length",
+ "path": "functions/length"
+ },
+ {
+ "title": "print",
+ "path": "functions/print"
+ },
+ {
+ "title": "range",
+ "path": "functions/range"
+ },
+ {
+ "title": "values",
+ "path": "functions/values"
+ }
+ ]
+ },
+ {
+ "title": "Standard Imports",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "imports"
+ },
+ {
+ "title": "decimal",
+ "path": "imports/decimal"
+ },
+ {
+ "title": "http",
+ "path": "imports/http"
+ },
+ {
+ "title": "json",
+ "path": "imports/json"
+ },
+ {
+ "title": "runtime",
+ "path": "imports/runtime"
+ },
+ {
+ "title": "sockaddr",
+ "path": "imports/sockaddr"
+ },
+ {
+ "title": "strings",
+ "path": "imports/strings"
+ },
+ {
+ "title": "time",
+ "path": "imports/time"
+ },
+ {
+ "title": "types",
+ "path": "imports/types"
+ },
+ {
+ "title": "units",
+ "path": "imports/units"
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Consul",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "consul"
+ }
+ ]
+ },
+ {
+ "title": "Nomad",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "nomad"
+ }
+ ]
+ },
+ {
+ "title": "Terraform",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "terraform"
+ }
+ ]
+ },
+ {
+ "title": "Vault",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "vault"
+ }
+ ]
+ }
+]
diff --git a/content/sentinel/v0.15.x/data/intro-nav-data.json b/content/sentinel/v0.15.x/data/intro-nav-data.json
new file mode 100644
index 0000000000..c95181af70
--- /dev/null
+++ b/content/sentinel/v0.15.x/data/intro-nav-data.json
@@ -0,0 +1,47 @@
+[
+ {
+ "title": "What is Sentinel?",
+ "path": "what"
+ },
+ {
+ "title": "Why Sentinel?",
+ "path": "why"
+ },
+ {
+ "title": "Getting Started",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "getting-started"
+ },
+ {
+ "title": "Installing the CLI",
+ "path": "getting-started/install"
+ },
+ {
+ "title": "Your First Policy",
+ "path": "getting-started/first-policy"
+ },
+ {
+ "title": "Rules",
+ "path": "getting-started/rules"
+ },
+ {
+ "title": "Logic",
+ "path": "getting-started/logic"
+ },
+ {
+ "title": "Imports",
+ "path": "getting-started/imports"
+ },
+ {
+ "title": "Testing",
+ "path": "getting-started/testing"
+ },
+ {
+ "title": "Next Steps",
+ "path": "getting-started/next-steps"
+ }
+ ]
+ }
+]
diff --git a/content/sentinel/v0.15.x/data/version.js b/content/sentinel/v0.15.x/data/version.js
new file mode 100644
index 0000000000..585195c8a3
--- /dev/null
+++ b/content/sentinel/v0.15.x/data/version.js
@@ -0,0 +1,2 @@
+// This file is auto-generated - DO NOT EDIT
+export default "0.15.6"
\ No newline at end of file
diff --git a/content/sentinel/v0.15.x/img/sentinel-import-topology.svg b/content/sentinel/v0.15.x/img/sentinel-import-topology.svg
new file mode 100644
index 0000000000..36f7c79bc9
--- /dev/null
+++ b/content/sentinel/v0.15.x/img/sentinel-import-topology.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/changelog.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/changelog.mdx
new file mode 100644
index 0000000000..69bd7112fa
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/changelog.mdx
@@ -0,0 +1,522 @@
+---
+page_title: Sentinel Runtime Release Notes
+sidebar_title: Release Notes
+sidebar_current: docs-changelog
+description: >-
+ This are the public release notes for the Sentinel runtime. See this document
+ for the latest updates.
+layout: docs
+---
+
+# Sentinel Runtime Release Notes
+
+These are the release notes for the Sentinel runtime.
+
+Each version of the runtime is released with a corresponding version of
+[Sentinel CLI](/sentinel/commands). To download the CLI, see the [downloads
+page](/sentinel/downloads).
+
+Sentinel integrations and embedded runtimes may not always have the latest
+version installed, depending on the product's individual release cycle. For more
+information, contact the support team for your specific integration.
+
+
+
+## 0.16.1 (October 21, 2020)
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where `contains` to ensure any instance of `undefined` would correctly result in `undefined`.
+- `runtime/eval`: Fixed an issue in `any` and `all` expressions to ensure correct boolean logic when dealing with a collection containing `undefined`.
+- `imports`: Fixed an issue where import processes were not killed, which caused non-graceful closures for the clients.
+- `lang/scanner`: Fixed an issue where inline `#` style comments were not being consumed.
+
+## 0.16.0 (October 14, 2020)
+
+BREAKING CHANGES:
+
+- `cmd/doc`: Removal of the deprecated `sentinel doc` command.
+- `sentinel`: Replace `whitelist` and `blacklist` with `allowlist` and `denylist`, as per inclusive language changes.
+
+FEATURES:
+
+- `imports/version`: Added a new import, `version`, which supplies helpers for dealing with semantic versioning.
+- `cmd/apply`: Added an `HCL` based configuration file, which will eventually deprecate the legacy `JSON` configuration.
+- `cmd/apply`: Added support for download remote policies and modules.
+- `cmd/test`: Added support for downloading remote test modules.
+- `cmd/apply`: Added support for evaluating a policy based on it's key within the configuration.
+- `cmd/apply`: Added support for running all policies in a configuration by default.
+
+## 0.15.6 (July 7, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.15.5 (May 20, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel integrations. There are no user-facing changes.
+
+## 0.15.4 (May 13, 2020)
+
+BUG FIXES:
+
+- `imports`: Fixed an issue with the standard imports that was affecting the handling of null data within lists.
+
+## 0.15.3 (April 16, 2020)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where `IndexExpr` were not rewritten, as well as ensuring `SelectorExpr` uses any nested rewrites.
+- `lang/printer`: Fixed issue printing deeply nested structures and/or loops.
+
+IMPROVEMENTS:
+
+- `imports/decimal`: Added alias methods to improve consistency of method names within the `decimal` import. The alias methods are `is_nan` for `isnan`, as well as both `is_inf` and `is_infinite` for `isinfinite`.
+- `command/apply`: `sentinel apply` will now output the policy description when a trace is forced via `-trace`.
+- `sentinel`: Improved `String` output formatting of Policy docstring for `EvalPolicyResult`.
+
+## 0.15.2 (April 2, 2020)
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue with the `printer` that was causing a panic
+ when a `#` line comment was used with no text following it.
+- `imports/types`: Fixed an issue with the `types` import where calls to
+ `type_of` for undefined types were incorrectly returning as map types. These
+ will now be correctly be identified as undefined as expected.
+
+## 0.15.1 (March 6, 2020)
+
+BUG FIXES:
+
+- `sentinel`: Fixed how modules are managed internally in the runtime to correct
+ race conditions in concurrent scenarios and to allow for per-evaluation module
+ restrictions. This functionality is only of interest to embedded applications
+ with long-lived runtimes and is currently not implemented in any HashiCorp
+ integration.
+
+## 0.15.0 (March 5, 2020)
+
+NOTES:
+
+- **Windows Digital Signature**. Our windows releases for this version and up will be signed
+ and verified according to Microsoft's requirements.
+
+FEATURES:
+
+- `cmd/config`: Modules can now be loaded off of local disk using the Sentinel
+ CLI. For information on how to do so, see the Sentinel documentation.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: Changed modules so that they now have singleton scope when
+ loaded. Importing a module under two different aliases, or within a nested
+ module, will now share scopes - modification of state in one will alter the
+ other.
+- `lang/object`: Introduced an interface to allow the duplication of various
+ types, like collections.
+- `runtime/eval`: Changed how collection data is returned from modules. This
+ data is now duplicated to prevent accidental or deliberate circumvention of
+ the prohibition on assignment to import data, and brings behavior to parity
+ with binary imports.
+
+## 0.14.4 (February 6, 2020)
+
+IMPROVEMENTS:
+
+- `imports/time`: Changed the `add` timespace function in the `time` import to
+ allow it to take float types as durations. These values will be truncated to
+ the appropriate integer-value duration.
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where out-of-place right-hand braces were being
+ parsed as empty statements, instead of raising syntax errors.
+
+## 0.14.3 (January 22, 2020)
+
+IMPROVEMENTS:
+
+- `cmd/test`: Fail when an assertion is not found in a trace.
+- `cmd/test`: Added a message to notify when no rules were evaluated in a test.
+
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue where import aliases (the `as baz` in `import
+ "foo/bar" as baz`) were being removed when formatting with `sentinel fmt`.
+
+- `imports/http`: Fixed an issue with the http import where the client library's
+ debug logs were being printed to standard error.
+
+## 0.14.2 (January 15, 2020)
+
+NOTES:
+
+With this release, we are discontinuing support for MacOS 32-bit. 64-bit builds
+are now the only builds available for MacOS.
+
+IMPROVEMENTS:
+
+- `lang/printer`: A newline is now added to files formatted via `sentinel fmt`.
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue in printing where multi-line expressions would
+ indent infinitely. They will now only indent once at the most. This is mostly
+ seen when using `sentinel fmt`.
+- `runtime/encoding`: Fixed an issue where if a plugin returned a deeply
+ embedded undefined value, the value was instead decoded into a map.
+
+## 0.14.1 (January 6, 2020)
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where reserved words could not be used as
+ selectors when the selector expression was the last lexeme on a line.
+
+## 0.14.0 (December 18, 2019)
+
+LANGUAGE CHANGES:
+
+- **Filter Expression** `filter`. A new quantifier expression, `filter` has been added.
+ `filter` returns a subset of the provided collection based on a boolean condition that
+ is asserted for each element.
+
+NOTES:
+
+- **Apple Notarization**. Our darwin releases for this version and up will be signed
+ and notarized according to Apple's requirements.
+
+## 0.13.1 (November 25, 2019)
+
+BUG FIXES:
+
+- `runtime/eval`: Parameters are no longer allowed in mock files. Adding one to
+ a mock will result in a runtime error.
+- `runtime/eval`: Fixed an issue where import calls from within mocks were
+ failing under certain circumstances.
+- `imports/decimal`: `int` should now correctly provide the truncated integer
+ representation of a decimal number, not the rounded one.
+
+## 0.13.0 (November 15, 2019)
+
+LANGUAGE CHANGES:
+
+- **Policy Parameters**. The new [policy
+ parameters](https://docs.hashicorp.com/sentinel/language/parameters) feature
+ allows authors to use a `param` declaration at the top of a policy to supply
+ values that are expected to come from outside of a policy. See the linked
+ documentation for more details.
+
+FEATURES:
+
+- `imports/http`: The [`http`
+ import](https://docs.hashicorp.com/sentinel/imports/http) is a new addition
+ to the standard library enabling the use of HTTP-accessible data from outside
+ the runtime in policy rules.
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where loading other imports before a mocked
+ import would cause those imports to no longer be visible from within the
+ policy.
+
+## 0.12.0 (October 7, 2019)
+
+LANGUAGE CHANGES:
+
+- **New `case` statement**. This statement is a selection control mechanism to
+ conditionally execute a branch based on expression equality, allowing
+ simplification of complex conditional chains that may otherwise need to be
+ written with `else if`. See the [Case
+ Statements](https://docs.hashicorp.com/sentinel/language/spec#case-statements)
+ section of the Sentinel language specification for more details.
+
+IMPROVEMENTS:
+
+- `lang/semantic`: Added a semantic check to ensure usage of append is not using
+ a return value.
+
+BUG FIXES:
+
+- `runtime/eval`: Corrected an issue where calling a method on an import object
+ value that was the result of a method call on another import object value
+ would have erroneously tried to call an import of the name of the "parent"
+ import object value. Example: in `a = subject.new(); b = a.call(); c =
+ b.call()`, `b.call()` would attempt to call a method named `call` on the root
+ namespace for an import named `a`. This has now been corrected so that
+ `b.call()` will now correctly call the `call` method for the respective object
+ namespace residing in the import named `subject`.
+- `imports`: Some standard imports may have been returning null for some unknown
+ keys in objects when they should have been returning undefined. This was due
+ to an SDK issue which was corrected in SDK version 0.3.2, which has now been
+ corrected.
+- `cmd/test`: `sentinel test` will now correctly fail a policy if it encounters
+ an error.
+- `cmd/test`: `sentinel test` will now correctly display errors and other output
+ that were missing due to a formatting issue.
+- `runtime/eval`: The `append` builtin now correctly returns undefined for all
+ calls, as called for by the Specification. Note that in most cases, the
+ semantic check outlined above will trigger an error if the return value is
+ used.
+
+## 0.11.0 (September 5, 2019)
+
+LANGUAGE CHANGES:
+
+- **New builtin function:** `range()`. This function existed in the spec in
+ earlier versions but was removed as it lacked an implementation. This has now
+ been implemented and re-added to the spec. See the
+ [Range](https://docs.hashicorp.com/sentinel/language/spec#range) section of
+ the Sentinel Specification for more details.
+- Lists are now comparable. Lists are equal if their corresponding elements are
+ comparable and equal.
+- Method calls on values returned by imports are now supported. See the
+ [Imports](https://docs.hashicorp.com/sentinel/language/spec#imports) section
+ of the Sentinel Specification for more details.
+
+FEATURES:
+
+- `imports/decimal`: This is a new import designed to do exact precision
+ mathematical calculations.
+- `runtime/eval`: Compound call expressions that refer to imports (example:
+ foo.bar().baz() when `foo` is a loaded import) will now function as expected.
+ Previously, this was only supported up to the first call (example:
+ `foo.bar()`).
+- `imports/time`: Timespaces returned by calls such as `time.now` are now
+ callable. Example: `t = time.now; t.after(some_previous_time)` will now
+ function.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: The implementation of comparison of non-comparable types has
+ now changed. Rather than triggering a runtime error, non-comparable types will
+ now return false when attempting to compare.
+
+## 0.10.4 (August 15, 2019)
+
+BUG FIXES:
+
+- `runtime/encoding`: Fixed an issue with conversion of null values that could
+ lead to crashes in imports.
+
+## 0.10.3 (July 15, 2019)
+
+BUG FIXES:
+
+- `command/config`: Parsing a configuration file where malformed data is
+ encountered after apparently properly-formed data will now report an error. An
+ example would be a situation where misplaced braces would cause a JSON object
+ to only parse part of the configuration file.
+
+## 0.10.2 (June 25, 2019)
+
+BUG FIXES:
+
+- `lang/parser`: Corrected an issue where parsing certain compound binary
+ expressions where the negation predicate (`not`) was in use would cause the
+ negation to have no effect. Example: `foo else "bar" not in "baz"` would have
+ been parsed and evaluated as `foo else "bar" in "baz"`, effectively producing
+ the opposite result.
+
+## 0.10.1 (May 9, 2019)
+
+BUG FIXES:
+
+- `command/test`: `sentinel test` now displays policies without
+tests as an unknown result with [no test files], instead of the somewhat
+erroneous behavior of displaying it as a PASS. A full test run with a mixture
+of passing tests and no tests still results in an overall successful result.
+
+## 0.10.0 (April 18, 2019)
+
+LANGUAGE CHANGES:
+
+- Mixed-number arithmetic operations are now allowed. Addition (`+`),
+ subtraction (`-`), multiplication (`*`), division (`/`), and remainder
+ operations (`%`) are no longer restricted to number values of the same
+ type, and can mix integer and floating point. The result of these operations
+ is always a floating-point number.
+- Remainder (modulo, `%`) operations are now allowed on floating-point numbers.
+
+
+BUG FIXES:
+
+- `lang/parser`: Fixed a bug that affected the use of `not` with `contains`,
+ `matches`, or `in` in a compound expression.
+- `runtime/eval`: Fixed error messages on evaluation errors with `contains` and
+ `in` to indicate that strings are an allowed type along with lists and maps.
+
+
+## 0.9.2 (March 15, 2019)
+
+This is a dependency update related to the changes mentioned in 0.9.1. No other
+changes have been made.
+
+## 0.9.1 (March 14, 2019)
+
+This is a patch release that is required to integrate with the latest versions
+of the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). No other
+changes have been made.
+
+## 0.9.0 (January 28, 2019)
+
+FEATURES:
+
+- `imports/strings`: Added a `join` function to the `strings` import. This can
+ be used to join a list into a string with a specific separator.
+ Multi-dimensional lists and all Sentinel primitives are supported.
+
+## 0.8.1 (January 17, 2019)
+
+BUG FIXES:
+
+- `lang/eval`: Fixed a bug that prevents the effective use of `return`
+ statements in `for` loops.
+
+## 0.8.0 (January 14, 2019)
+
+FEATURES:
+
+- Mocks can now be represented by Sentinel code. This allows for the mocking of
+ functions and other complex data structures that cannot be represented in
+ JSON. For more information on using this feature via mocks, click
+ [here](https://docs.hashicorp.com/sentinel/commands/config#mocking-with-sentinel-code).
+
+
+IMPROVEMENTS:
+
+- Import validation has now been moved to the semantic checking phase. This
+ should result in better reporting of validation errors. In addition, import
+ validation will now enforce the use of an `as` identifier when an import path
+ is not a valid identifier on its own (example: `import "foo/bar"`).
+
+## 0.7.0 (December 12, 2018)
+
+BUG FIXES:
+
+- There have been changes to the runtime in how scope is handled over multiple
+ policy executions. Scope is now correctly unique per single policy execution,
+ and values set or builtins that are overridden in one policy will no longer
+ affect those values within another.
+
+## 0.6.0 (November 30, 2018)
+
+FEATURES:
+
+- `imports/runtime`: This new import allows for one to check various aspects of
+ the Sentinel runtime as it may be embedded in the simulator or a specific
+ implementation. For now, it allows the version to be checked.
+
+## 0.5.1 (November 28, 2018)
+
+IMPROVEMENTS:
+
+- `imports/time`: Added the `zone` and `zone_string` attributes to assist with
+ validation of a timespace's zone.
+- `command/fmt`: Added a new -check flag. This option does not commit changes,
+ but instead checks to see what files need formatting and outputs them on
+ stdout.
+
+BUG FIXES:
+
+- `command/test`: Ensure that passing test results are correctly output one per
+ line. Tests are also now run in a deterministic fashion based on
+ lexicographical (alphabetical) order.
+- `imports/time`: `month_name` and `weekday_name` will now show up correctly in
+ a returned timespace result.
+
+
+## 0.5.0 (November 5, 2018)
+
+IMPROVEMENTS:
+
+- `spec`: Selectors can now contain any reserved word (example: `rule`) or
+ keyword operator (example: `any`, `all`, `is`, `not`). This only works for the
+ _selector_ part of the expression (after the first period) - the first primary
+ expression (before the first period) still needs to be an identifier that does
+ not conflict with reserved words.
+
+BUG FIXES:
+
+- The simulator should now display import function call names correctly in
+ import errors.
+
+## 0.4.0 (October 1, 2018)
+
+FEATURES:
+
+- `builtin`: Added the `bool` built-in type conversion function. Booleans will
+ also now accepted as conversion into other values as well, with the full list
+ of behaviors available in the spec.
+
+## 0.3.2 (September 27, 2018)
+
+FEATURES:
+
+- `command/apply`: `sentinel apply` now prints out messages output by the
+ `print()` function when a trace is output on policy failure, or when a trace
+ is forced with `-trace`.
+- `imports/time`: Added the `month_name` and `weekday_name` keys to the
+ timespace, which return full-English names for the month and day of the week.
+
+
+BUG FIXES:
+
+- `command/fmt`: `sentinel fmt -` Will no longer print out the filter status
+ message on the output stream when `-write=false` Is not explicitly stated.
+ This brings the behavior of the command in line with the help text.
+- `runtime`: Index operations on the right-hand-side that have negative indexes
+ that go out of range (example: `length(list) * -1 - 1`) now correctly return
+ `undefined`. left-hand-side index assignments with a out-of-range negative
+ index still return runtime errors.
+
+## 0.3.1 (August 3, 2018)
+
+BUG FIXES:
+
+- `runtime`: Basic index assignment has been implemented as per the spec.
+- `runtime`: Index expressions for lists with negative indexes will no longer panic if the
+ list index is less than `length(list) * -1`.
+
+## 0.3.0 (July 20, 2018)
+
+FEATURES:
+
+- **New standard import: `types`.** This can be used to dynamicaly detect the
+ type of some value.
+
+## 0.2.0 (April 11, 2018)
+
+FEATURES:
+
+- **New standard import: `json`.** Marshal and unmarshal JSON documents and
+ access their contents as native Sentinel values.
+- **`break` and `continue`.** These are now both specified and implemented.
+ `break` allows loop exiting and `continue` allows immediate execution of the
+ next iteration.
+
+IMPROVEMENTS:
+
+- runtime: `print()` map values are now ordered alphabetically by keys.
+
+BUG FIXES:
+
+- command/test: If no `test` block exists, test behaves like it is asserting
+ `main: true`.
+- runtime: default maximum stack depth to 500
+- runtime: `print()` map values now appear like more typical maps.
+- runtime: division by zero is an error, not a crash
+- runtime: plugins that send map values with `null` values now decode properly
+ into native Sentinel values.
+
+## 0.1.0 (September 19, 2017)
+
+Initial release.
+
+
+
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/commands/apply.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/commands/apply.mdx
new file mode 100644
index 0000000000..267c991f9e
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/commands/apply.mdx
@@ -0,0 +1,55 @@
+---
+page_title: 'Command: apply'
+sidebar_title: 'apply'
+sidebar_current: docs-commands-apply
+description: >-
+ The `sentinel apply` command is used to execute a policy locally for
+ development purposes.
+layout: docs
+---
+
+# Command: `apply`
+
+The `sentinel apply` command is used to execute a policy locally for development
+purposes.
+
+## Usage
+
+Usage: `sentinel apply [options] POLICY`
+
+This command executes the policy file at the path specified by POLICY. In
+addition to a path to a policy, POLICY can reference a label of a
+[policy](/sentinel/configuration#policies) entry in the configuration.
+
+Use the exit code of this command to determine the exact status of the policy
+evaluation. `0` is pass, `1` is fail, `2` is undefined (fail, but because the
+result was undefined), and `3` is a runtime error. Errors unrelated to the
+policy status itself are returned with an exit status of `9`.
+
+To control the behavior of the `apply` command, create a [configuration
+file](/sentinel/configuration). With this, you can define available
+[import plugins](/sentinel/concepts/imports), mock data, and global values.
+This can help you simulate a policy embedded within an application.
+
+As of the 0.16 release, POLICY is optional. When it is not supplied,
+`sentinel apply` will run __all__ policies in the supplied configuration.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-config=path` - Path to a configuration file specifying available imports,
+ mock data, globals, etc. The default is `sentinel.[hcl|json]`.
+
+- `-global key=value` - Set global values. This is the same as setting `global`
+ in the configuration file, and will override any of these respective values
+ set in the configuration. The value is either a string, or a JSON number,
+ array, or object. To force strings, use quotes.
+
+- `-param key=value` - Set parameters, the same as setting `param` in the
+ configuration file. Values are handled in the same way they are with the
+ `-global` flag.
+
+- `-trace` - Always show the execution trace. This shows intermediate
+ boolean expression values. This always shows for failed policies.
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/commands/fmt.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/commands/fmt.mdx
new file mode 100644
index 0000000000..5c85ce1019
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/commands/fmt.mdx
@@ -0,0 +1,34 @@
+---
+page_title: 'Command: fmt'
+sidebar_title: 'fmt'
+sidebar_current: docs-commands-fmt
+description: The `sentinel fmt` command formats a policy source to a canonical format.
+layout: docs
+---
+
+# Command: `fmt`
+
+The `sentinel fmt` command formats a policy source to a canonical format.
+
+## Usage
+
+Usage: `sentinel fmt [options] FILE ...`
+
+This command formats all the specified policy files to a canonical format.
+
+By default, policy files are overwritten in place. This behavior can be
+changed with the `-write` flag. If a specified FILE is `-` then stdin is
+read and the output is always written to stdout.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-write=true` - Write formatted policy to the named source file. If false,
+ output will go to stdout. If multiple files are specified, the output will
+ be concatenated directly.
+
+- `-check=false` - Don't format, only check if formatting is necessary. Files
+ that require formatting are printed, and a non-zero exit code is returned if
+ changes are required.
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/commands/index.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/commands/index.mdx
new file mode 100644
index 0000000000..7177093cec
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/commands/index.mdx
@@ -0,0 +1,23 @@
+---
+page_title: Commands (CLI)
+sidebar_current: docs-commands
+description: >-
+ Sentinel provides a `sentinel` command-line interface (CLI) for developing and
+ testing policies.
+layout: docs
+---
+
+# Sentinel CLI Commands
+
+The Sentinel command-line interface (CLI) allows for the developing and testing
+of policies outside of a particular Sentinel implementation. Having a standard
+workflow to develop policies is critical for our mission of [policy as
+code](/sentinel/concepts/policy-as-code).
+
+The CLI takes a subcommand to execute. The complete list of subcommands is in
+the navigation to the left.
+
+The Sentinel CLI is a well-behaved command line application. In erroneous cases,
+a non-zero exit status will be returned. It also responds to -h and --help as
+you'd expect. To view a list of the available commands at any time, just run
+`sentinel` with no arguments.
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/commands/test.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/commands/test.mdx
new file mode 100644
index 0000000000..781fbce7bf
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/commands/test.mdx
@@ -0,0 +1,43 @@
+---
+page_title: 'Command: test'
+sidebar_title: 'test'
+sidebar_current: docs-commands-test
+description: The `sentinel test` command runs policies against the Sentinel test framework.
+layout: docs
+---
+
+# Command: `test`
+
+The `sentinel test` command runs policies against the Sentinel test framework.
+
+To learn more about testing, see the [testing
+policies](/sentinel/writing/testing) page.
+
+## Usage
+
+Usage: `sentinel test [options] [PATH] ...`
+
+This command runs the defined test cases against a set of policies.
+
+The test cases are expected to live at the path `test//*.[hcl|json]` relative
+to the policy being tested. `` should be the name of the policy
+excluding the file extension. The files should be in the [configuration
+file structure](/sentinel/configuration). The `test` key is used for
+assertions.
+
+The PATH arguments specified may be a list of zero or more files or directories.
+When no arguments are given, it defaults to the current directory. When a
+directory is given, all policies ending with the extension `.sentinel` are
+tested.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-run=regexp` - Run only tests matching the regular expression pattern.
+ A `/` splits policy name and test case name.
+
+- `-verbose` - Show tracing and log output for tests that succeed in addition
+ to tests that fail. By default, traces and logs are only shown for tests that
+ fail.
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/concepts/enforcement-levels.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/concepts/enforcement-levels.mdx
new file mode 100644
index 0000000000..673ae3865e
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/concepts/enforcement-levels.mdx
@@ -0,0 +1,45 @@
+---
+page_title: Enforcement Levels
+sidebar_title: Enforcement Levels
+sidebar_current: docs-concepts-levels
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Enforcement Levels
+
+Enforcement levels are a first class concept in Sentinel allowing pass/fail
+behavior to be associated separately from the policy logic. This enables
+any policy to be a warning, allow overrides, or be absolutely mandatory.
+Because this level is not part of the policy body itself, different uses of
+the same policy can have different enforcement levels.
+
+Sentinel has three enforcement levels:
+
+- **Advisory:** The policy is allowed to fail. However, a warning should be
+ shown to the user or logged.
+
+- **Soft Mandatory:** The policy must pass unless an override is specified.
+ The semantics of "override" are specific to each Sentinel-enabled application.
+ The purpose of this level is to provide a level of privilege separation
+ for a behavior. Additionally, the override provides non-repudiation since
+ at least the primary actor was explicitly overriding a failed policy.
+
+- **Hard Mandatory:** The policy must pass no matter what. The only way to
+ override a hard mandatory policy is to explicitly remove the policy.
+ Hard mandatory is the default enforcement level. It should be used in
+ situations where an override is not possible.
+
+## Configuring Enforcement Levels
+
+Enforcement levels are configured when a policy is deployed to a
+Sentinel-enabled application. The exact mechanism that the level is specified
+is determined by each application. Please reference the documentation for
+your Sentinel-enabled application for more information.
+
+Enforcement levels are not configured and are not known by the policy body
+itself. All policies should be written to describe exactly the behavior
+they're attempting to control. For example, a policy that restricts deploys
+to business hours should be written exactly like so. When that policy is
+configured on an application, the operator may specify that it is advisory,
+soft, or hard mandatory.
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/concepts/imports.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/concepts/imports.mdx
new file mode 100644
index 0000000000..844a06ed59
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/concepts/imports.mdx
@@ -0,0 +1,34 @@
+---
+page_title: Imports
+sidebar_title: Imports
+sidebar_current: docs-concepts-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom import](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only
+local context for making policy decisions.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that the
+import calendar allows access to engineer calendars. This import only exists
+for the purpose of this example and at the time of writing is not a real
+available import.
+
+```sentinel
+import "calendar"
+
+// Get the calendar for Bob for today
+bob_calendar = calendar.for("bob").today
+
+// Allow this policy to pass if Bob is not on vacation.
+main = rule { not bob_calendar.has_event("vacation") }
+```
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/concepts/index.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/concepts/index.mdx
new file mode 100644
index 0000000000..ce7ac51454
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/concepts/index.mdx
@@ -0,0 +1,15 @@
+---
+page_title: Basic Concepts
+sidebar_current: docs-concepts
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Basic Concepts
+
+This section covers some high level basic concepts that are important
+to understand for day to day Sentinel usage. Every page in
+this section is recommended reading for anyone consuming
+or extending Sentinel.
+
+Please use the navigation to the left to learn more about a topic.
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/concepts/language.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/concepts/language.mdx
new file mode 100644
index 0000000000..dd083d0720
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/concepts/language.mdx
@@ -0,0 +1,92 @@
+---
+page_title: Policy Language
+sidebar_title: Policy Language
+sidebar_current: docs-concepts-language
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Policy Language
+
+Sentinel defines and uses its own [policy language](/sentinel/language).
+
+The language was designed to be approachable by non-programmers, since
+there are many use cases where the individual defining policy may not
+be a developer. However, the language includes constructs that
+are familiar to developers to enable powerful policies.
+
+To learn more about the language, please see the
+[writing policy section](/sentinel/writing)
+or the [language reference](/sentinel/language).
+
+An example of the policy language is shown below:
+
+```sentinel
+import "time"
+
+# Validate time is between 8 AM and 4 PM
+valid_time = rule { time.now.hour >= 8 and time.now.hour < 16 }
+
+# Validate day is M - Th
+valid_day = rule {
+ time.now.weekday_name in ["Monday", "Tuesday", "Wednesday", "Thursday"]
+}
+
+main = rule { valid_time and valid_day }
+```
+
+## Why?
+
+To explain why Sentinel defines and uses its own language, the question
+can be more easily split into roughly two types of languages: _configuration_ languages
+and _programming_ languages.
+
+### Configuration Languages
+
+Configuration languages are formats such as JSON, YAML, XML, etc. Many applications
+use configuration languages as their ACL format. Configuration languages
+are good for static, declarative information. ACL systems are a good fit
+for this. ACL systems are focused, providing the limiting options necessary
+to restrict access to a system.
+
+Configuration languages are not good for dynamic or logical rules. They
+typically only contain limited conditions, loops, or functions. Further
+configuration is often declarative, which can introduce complexities to
+logical statements that depend on ordering.
+
+The use cases that Sentinel was built for require the ability to perform
+complex behavior that didn't model well into a configuration format or a
+declarative form.
+
+### Programming Languages
+
+The Sentinel language was designed with the following goals:
+
+- **Non-programmer friendly.** Sentinel was built to be used by non-programmers.
+ For the use cases we discovered, non-programmers needed the ability to
+ enforce certain rules within a system. For example, a person responsible for
+ compliance may need to insert rules into a system.
+
+- **Programmer friendly.** At the same time, the language needed to support
+ programmer-friendly constructs such as conditionals, loops, and functions
+ for complex policies that a programmer may be writing.
+
+- **Embeddable.** Sentinel is embedded in existing software. The language
+ itself needs to be easily embeddedable. Further, Sentinel was designed
+ to be embedded in [HashiCorp](https://www.hashicorp.com) software
+ written in Go, so it needed to be easily embeddable in Go.
+
+- **Safe.** The language is used in highly security-sensitive environments.
+ It must not be able to crash its host system or access system resources
+ without explicit approval.
+
+Prior to building our own language, Sentinel evaluated other languages.
+Some languages worked quite well. As we continued to develop Sentinel, we
+found that the flexibility and control over designing our own language
+outweighed the costs.
+
+Because the language itself doesn't need to be general purpose, building
+a language focused on policy proved to be the best route for Sentinel.
+It allows us to build powerful tracing capabilities for debugging, make
+interesting performance choices, and extend the language as needed in the
+future.
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/concepts/policy-as-code.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/concepts/policy-as-code.mdx
new file mode 100644
index 0000000000..9e3a530b31
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/concepts/policy-as-code.mdx
@@ -0,0 +1,73 @@
+---
+page_title: Policy as Code
+sidebar_title: Policy as Code
+sidebar_current: docs-concepts-pac
+description: >-
+ Policy as code is the idea of writing code in a high-level language to manage
+ and automate policies. By representing policies as code in text files, proven
+ software development best practices can be adopted such as version control,
+ automated testing, and automated deployment.
+layout: docs
+---
+
+# Policy as Code
+
+Policy as code is the idea of writing code in a high-level language to
+manage and automate policies. By representing policies as code in text files,
+proven software development best practices can be adopted such as version
+control, automated testing, and automated deployment.
+
+Many existing policy or ACL systems do not practice policy as code. Many
+policies are set by clicking in a GUI, which isn't easily repeatable nor
+versionable. They usually don't provide any system for testing policies
+other than testing an action that would violate the policy. This makes it
+difficult for automated testing. And the policy language itself varies by
+product.
+
+Sentinel is built around the idea and provides all the benefits of policy as code.
+
+## Benefits
+
+Policy as code provides a number of benefits:
+
+- **Sandboxing.** Policies provide the guardrails for other automated systems.
+ As the number of automated systems grow, there is also a growing need to
+ protect those automated systems from performing dangerous actions. Manual
+ verification is too slow; policies need to be represented as code to
+ keep up with other automated systems.
+
+- **Codification.** By representing policy logic as code, the information
+ and logic about a policy is directly represented in code and can be augmented
+ with comments rather than relying on oral tradition to learn about the
+ reason for policies.
+
+- **Version Control.** Policies are encouraged to be stored as simple text
+ files managed by a version control system. This lets you gain all the
+ benefits of a modern VCS such as history, diffs, pull requests, and more.
+
+- **Testing.** Policies are just code. Their syntax and behavior can be
+ [easily validated with Sentinel](/sentinel/commands/test). This also encourages
+ automated testing such as through a CI. Paired with a VCS system, this
+ allows a pull request workflow to verify that a policy keeps the system
+ behavior as expected before merging.
+
+- **Automation.** With all policies as code in simple text files, various
+ automation tools can be used. For example, it is trivial to create tools
+ to automatically deploy the policies into a system.
+
+## Sentinel and Policy as Code
+
+Sentinel fully embraces policy as code in a number of ways:
+
+- **Language.** All Sentinel policies are written using the
+ [Sentinel language](/sentinel/concepts/language). This language is
+ made to inputted directly to text files. As an additional benefit,
+ all Sentinel-enabled applications share the same policy language.
+
+- **Development.** Sentinel provides a [CLI](/sentinel/commands)
+ for development and testing. This local CLI can be used to verify policies
+ before deploying them to a system.
+
+- **Testing.** Sentinel provides a [test framework](/sentinel/commands/test)
+ designed specifically for automation. This allows developers and CI systems
+ to further verify policies.
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/configuration/index.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/configuration/index.mdx
new file mode 100644
index 0000000000..e1e1744ee5
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/configuration/index.mdx
@@ -0,0 +1,319 @@
+---
+page_title: Sentinel CLI Configuration File Syntax
+sidebar_title: Configuration File Syntax
+sidebar_current: docs-configuration
+description: >-
+ The Sentinel CLI's configuration file can be used to control the
+ behavior of the simulator during apply and test operations.
+layout: docs
+---
+
+# Sentinel CLI Configuration File Syntax
+
+The Sentinel CLI's configuration file can be used to control the behavior
+of the simulator during [`apply`](/sentinel/commands/apply) and
+[`test`](/sentinel/commands/test) operations.
+
+## Usage
+
+The configuration file is used in different ways depending on what operation you
+are trying to execute.
+
+### Apply
+
+When using `sentinel apply`, supply the configuration file by using the
+`-config=FILE` flag, where `FILE` is the path to the configuration file. The
+default is `sentinel.[hcl|json]`.
+
+See the [apply command](/sentinel/commands/apply) reference for more
+details.
+
+### Test
+
+When using `sentinel test`, each file matching `test//*.[hcl|json]` is a
+configuration file representing a single test case, where `` is the name
+of the policy being tested. Configure assertions for each [test
+case](#test-cases) using the test section.
+
+See the [test command](/sentinel/commands/test) reference for more details.
+
+#### Remote sources
+
+When declaring a `policy` or `module` block within the configuration, a
+`source` attribute must be supplied. This `source` should declare the location
+of the resource, which can be either a local file or a URL pointing to a remote
+file. Examples of local `source` attributes are detailed both in the
+[Policies](#policies) and [Modules](#modules) sections of this page. Below
+are a few examples of remote `source` attributes.
+
+Example:
+
+```hcl
+policy "foo" {
+ source = "git::https://github.com/hashicorp/sentinel-example.git//main.sentinel"
+ enforcement_level = "hard-mandatory"
+}
+```
+
+The above example will fetch the policy from the provided URL and place it into
+the cache ready for use. Be sure to read the
+[Remote Sources](/sentinel/configuration/remote-sources) page for an
+in-depth overview.
+
+## Configuration File Reference
+
+The format of the configuration file is either JSON or HCL. The available
+blocks are:
+
+- `mock` - A mock import specification.
+- `policy` - Configuration for a [policy](/sentinel/writing).
+- `module` - Configuration for a [module](/sentinel/extending/modules).
+- `import` - Configuration for a [plugin](/sentinel/extending/plugins).
+- `global` - Data that is inserted into the global scope.
+- `param` - Values for [parameters](/sentinel/language/parameters) defined in the policy.
+- `test` - Test cases for the [test command](/sentinel/commands/test).
+
+### Mock Imports
+
+Mock imports allow running a policy with an
+[import](/sentinel/concepts/imports) that you may not have access to or is
+too difficult to run. For example, it can be easier to mock data for [Terraform
+Enterprise](https://www.terraform.io/docs/enterprise/sentinel/index.html)
+locally instead of having to run a workspace through a plan/policy check cycle.
+
+Mock imports are specified using the `mock` block, labelled by the import
+name that you want to mock. For example, if you wanted to mock the `time`
+import, you could create an entry with the `time` label pointing to the data
+you want to mock.
+
+The data can take one of two forms:
+
+#### Mocking static data
+
+Static data can be mocked directly via HCl using the `data` attribute on the
+`mock` block.
+
+Example:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ hour = 9
+ minute = 42
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```sentinel
+import "time"
+
+main = time.now.hour == 9
+```
+
+#### Mocking with Sentinel code
+
+There are some Sentinel types that raw data cannot mock, such as functions,
+and maps that don't have string keys. To mock this data, you can use
+a Sentinel file itself. In this case, you can set the `module` attribute to
+a file with the Sentinel code in it, relative to the current working directory in
+the event of `apply`, or relative to the configuration file location in the
+event of `test`.
+
+Note that `main` is not required in a mock data file.
+
+Example:
+
+```hcl
+mock "foo" {
+ module = {
+ source = "mock-foo.sentinel"
+ }
+}
+```
+
+`mock-foo.sentinel` would contain:
+
+```sentinel
+bar = func() {
+ return "baz"
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```sentinel
+import "foo"
+
+main = foo.bar() == "baz"
+```
+
+### Policies
+
+The `policy` block allows for the the configuration of policies to assist
+with integrating into other commands.
+
+Each `policy` block has a label to identify the policy, with the block
+providing configuration of the policy. The available configuration options for
+policy are `source` and `enforcement_level`. The `source` key provides the
+location of the policy, while `enforcement_level` is currently used by
+integrations such as [Terraform Cloud](https://www.terraform.io/docs/cloud/sentinel/manage-policies.html#enforcement-levels). For more information on the `source`
+value, see [Policy and Module Sources](#policy-and-module-sources).
+
+```hcl
+policy "foo" {
+ source = "foo.sentinel"
+ enforcement_level = "hard-mandatory"
+}
+```
+
+### Modules
+
+The `module` block allows you to map a Sentinel import to a
+[module](/sentinel/extending/modules). The Sentinel CLI will parse the module
+source and make it available for evaluation with the policy.
+
+Each `module` block has a label aligning to the name of the import, with
+the block set to the configuration object for the module. Currently, the only
+value within the configuration object is `source`, which points to the
+location of the module. For more information on the `source` value, see
+[Policy and Module Sources](#policy-and-module-sources).
+
+```hcl
+module "foo" {
+ source = "modules/foo.sentinel"
+}
+
+module "bar" {
+ source = "modules/bar.sentinel"
+}
+```
+
+Note when using [`sentinel test`](/sentinel/commands/test), module paths must be
+specified relative to the location of the configuration file.
+
+```hcl
+module "foo" {
+ source = "../../modules/foo.sentinel"
+}
+
+module "bar" {
+ source = "../../modules/bar/sentinel"
+}
+```
+
+See [Writing and Using a
+Module](/sentinel/extending/modules#writing-and-using-a-module) for more details
+on using a module with this configuration.
+
+### Plugins
+
+Plugins allow you to run a real [import plugin](/sentinel/extending/plugins)
+with a policy. The Sentinel CLI will launch the plugin, connect to it, configure
+it, and execute it as needed by the policy.
+
+Import plugins are specified by the `import` key. The value of this is a map
+where the key is the name of the import and the value is another map with
+configuration about the plugin. The configuration map has the following keys:
+
+- `path` (string, required) - Path to the import plugin executable.
+- `args` (list of string, optional) - A list of arguments to pass to the
+ executable when starting it.
+- `env` (map of string to string, optional) - A set of environmental variables
+ to set when launching the plugin.
+- `config` (map, optional) - Configuration for the plugin itself. This is
+ specific to each individual plugin. Please reference the documentation for
+ the plugin for more information.
+
+Example:
+
+```hcl
+import "time" {
+ path = "/path/to/sentinel-import-time"
+ config = { "fixed_time": 1504155600 }
+}
+```
+
+With the above configuration, the following policy would pass assuming the
+plugin configuration is valid:
+
+```sentinel
+import "time"
+
+main = time.now.day == 31
+```
+
+### Global
+
+Global data is injected directly into the global scope of the policy.
+This can be used to simulate global data that may be injected by a
+Sentinel-enabled application.
+
+Global data is specified by the `global` key. The value is a map of
+variables to inject directly into the running policy.
+
+Example:
+
+```hcl
+global "time" {
+ value = {
+ now = {
+ day = 31
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass. Notice
+that we don't have to do any `import`. The values of `global` are
+injected directly into the global scope.
+
+```sentinel
+main = time.now.day == 31
+```
+
+### Parameters
+
+The `param` section allows you to supply values for the
+[parameters](/sentinel/language/parameters) found in a policy. Values
+entered here will satisfy required parameters in a policy, in addition to
+override any defaults. Note that any of the manual parameter value methods
+supported by `sentinel apply` will override the values set here.
+
+```hcl
+param "foo" {
+ value = "bar"
+}
+```
+
+### Test Cases
+
+Test cases specify the test cases for the [test command](/sentinel/commands/test).
+
+Tests are specified by the `test` key. The value of this is a map of
+string to boolean. The key is the name of a rule and the value is the
+expected value of that rule. If a rule is not specified, than any value is
+allowed.
+
+Example:
+
+```hcl
+test {
+ rules = {
+ main = true
+ valid_day = false
+ }
+}
+```
+
+For the policy:
+
+```sentinel
+valid_day = rule { 2 < 1 } # This is just an example!
+main = rule { not valid_day }
+```
+
+For more information, read about the [test command](/sentinel/commands/test).
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/configuration/remote-sources.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/configuration/remote-sources.mdx
new file mode 100644
index 0000000000..fbf22b9a03
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/configuration/remote-sources.mdx
@@ -0,0 +1,166 @@
+---
+page_title: Remote Sources
+sidebar_title: Remote Sources
+sidebar_current: docs-configuration-remote-sources
+description: >-
+ There are a number of options for formatting the URL provided to the
+ source attribute on policy and module blocks.
+layout: docs
+---
+
+# Remote Sources
+
+There are a number of options for formatting the URL provided to the
+`source` attribute on `policy` and `module` blocks. This flexibility
+should solve most use cases and allow for reusable policies and modules.
+
+### Supported Protocols
+
+-> **NOTE:** All protocols are supported on the CLI, however each production
+Sentinel integration may not. Be sure to read the appropriate integration
+documentation to check the supported protocols.
+
+- Local filesystem
+- Git
+- Mercurial
+- HTTP
+- Amazon S3
+- Google GCP
+
+### Forced Protocols
+
+Due to the ambiguous nature of URLs, it is possible to force a particular
+protocol to be used during the fetch. To achieve this, simply prefix the url
+with the appropriate protocol as listed below:
+
+- `file` - Local filesystem
+- `git` - Git
+- `hg` - Mercurial
+- `http` or `https` - HTTP
+- `s3` - Amazon S3
+- `gcp` - Google GCP
+
+Example:
+
+```
+git::http://github.com/hashicorp/sentinel.git
+```
+
+The above would download the provided HTTP URL using the Git protocol.
+
+### Protocol Options
+
+In addition to some global options, there are options available to specific
+protocols to perform features such as authentication.
+
+#### Subdirectories / File Pathing
+
+When supplying URLs that point to a directory (eg. `git`), a subdirectory and
+file can be provided by suffixing the URL with `//` and the path. For example,
+if we have a git repository that has a policy, `main.sentinel` in the root, we
+could directly fetch the file by using the following:
+
+```
+git::https://github.com/hashicorp/sentinel-docs-example.git//main.sentinel
+```
+
+Or, if the file was nested in the `policies` folder:
+
+```
+git::https://github.com/hashicorp/sentinel-docs-example.git//policies/main.sentinel
+```
+
+#### Checksumming
+
+For downloads of any protocol, you can automatically verify a checksum. To
+checksum a file, append a `checksum` query parameter to the URL. The parameter
+value can be in the format of type:value or just value, where type is "md5",
+"sha1", "sha256", "sha512" or "file" . The "value" should be the actual
+checksum value or download URL for "file". When type part is omitted, type will
+be guessed based on the length of the checksum string.
+
+```
+./foo.sentinel?checksum=md5:b7d96c89d09d9e204f5fedc4d5d55b21
+
+./foo.sentinel?checksum=b7d96c89d09d9e204f5fedc4d5d55b21
+
+./foo.sentinel?checksum=file:./foo.txt.sha256sum
+```
+
+#### Unarchiving
+
+An `archive` query parameter can be supplied to explicitly state what format
+to attempt to extract, if required. The supported formats are:
+
+- `tar.gz` and `tgz`
+- `tar.bz2` and `tbz2`
+- `tar.xz` and `txz`
+- `zip`
+- `gz`
+- `bz2`
+- `xz`
+
+If a URL has an extension that matches one of the supported formats, it will
+use that format to unarchive.
+
+```
+./file.zip
+```
+
+The above would use the `zip` format to unarchive.
+
+```
+./path/to/file?archive=zip
+```
+
+Would force the `zip` format. If for some reason you required archiving to be
+disabled, this can also be achieved by using the `false` value.
+
+```
+./path/to/file?archive=false
+```
+
+#### Git (`git`)
+
+- `ref` - The Git ref to checkout. This is a ref, so it can point to a commit
+ SHA, a branch name, etc. If it is a named ref such as a branch name, it will
+ be updated to the latest on each get.
+- `sshkey` - An SSH private key to use during clones. The provided key must be
+ a base64-encoded string. For example, to generate a suitable sshkey from a
+ private key file on disk, you would run base64 -w0 \.
+ **Note:** Git 2.3+ is required to use this feature.
+- `depth` - The Git clone depth. The provided number specifies the last `n`
+ revisions to clone from the repository.
+
+The git getter accepts both URL-style SSH addresses like
+`git::ssh://git@example.com/foo/bar`, and "scp-style" addresses like
+`git::git@example.com/foo/bar`. In the latter case, omitting the `git::` forced
+prefix is allowed if the username prefix is exactly git@.
+
+The "scp-style" addresses cannot be used in conjunction with the `ssh://`
+scheme prefix, because in that case the colon is used to mark an optional port
+number to connect on, rather than to delimit the path from the host.
+
+#### Mercurial (`hg`)
+
+- `rev` - The Mercurial revision to checkout.
+
+#### HTTP (`http` or `https`)
+
+Basic Authentication
+
+To use HTTP basic authentication, simply prepend `username:password@` to the
+hostname in the URL such as `https://Aladdin:OpenSesame@www.example.com/index.html`.
+All special characters, including the username and password, must be URL
+encoded.
+
+#### S3 (`s3`)
+
+The following query parameters are available and will take priority when
+authenticating against an S3 bucket.
+
+- `aws_access_key_id` - AWS access key.
+- `aws_access_key_secret` - AWS access key secret.
+- `aws_access_token` - AWS access token if this is being used.
+- `aws_profile` - Use this profile from local ~/.aws/ config. Takes priority
+ over the other three.
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/consul.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/consul.mdx
new file mode 100644
index 0000000000..16b3f502a6
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/consul.mdx
@@ -0,0 +1,49 @@
+---
+page_title: Consul and Sentinel
+sidebar_title: Consul
+sidebar_current: docs-app-consul
+description: >-
+ Consul Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies are applied during writes to
+ the KV Store.
+layout: docs
+---
+
+# Consul
+
+[Consul Enterprise](https://www.hashicorp.com/products/consul/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies are applied during writes to the KV Store.
+
+Sentinel policies have access to the key/value being written. They can be used to
+allow or deny the modification. The information that Sentinel policies have access
+to will expand over time.
+
+The Consul integration with Sentinel is documented in depth in the
+[Consul Enterprise documentation](https://www.consul.io/docs/guides/sentinel.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Input validation depending on the name of the key.**
+
+```sentinel
+
+main = rule { valid_key() }
+
+required = [
+ ["port", "\\d+"], # ports must be integers
+ ["name", "\\w+"], # name must be a word
+]
+
+valid_key = func() {
+ for required as v {
+ if key is v[0] {
+ return value matches v[1]
+ }
+ }
+
+ return false
+}
+```
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/extending/index.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/extending/index.mdx
new file mode 100644
index 0000000000..f3843470f8
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/extending/index.mdx
@@ -0,0 +1,44 @@
+---
+page_title: Extending Sentinel
+sidebar_current: docs-extending
+description: Learn how to create your own Sentinel imports to extend Sentinel's functionality.
+layout: docs
+---
+
+# Extending Sentinel
+
+-> **NOTE:** Sentinel's extensibility is currently undergoing large
+improvements - watch this space for future updates!
+
+Sentinel can be extended by writing additional
+[imports](/sentinel/language/imports). These imports can bring new functionality
+to Sentinel by allowing access to new data or by the addition of new functions.
+This section is designed to show you how to accomplish this extensibility and
+describe how it works.
+
+Currently, the only usable way to add additional imports is via
+[modules](#modules). While [plugins](#plugins) currently work under the Sentinel
+CLI, no Sentinel integration currently supports their use.
+
+-> You may also be interested in advanced details on [import
+internals](/sentinel/extending/internals).
+
+## Modules
+
+Modules allow you to re-use Sentinel code as an import. Modules are loaded
+external of policies and mapped to imports. This is usually configured via a
+file such as the [CLI configuration file](/sentinel/configuration).
+
+See the [modules](/sentinel/extending/modules) section for more details.
+
+## Plugins
+
+-> **NOTE:** Plugins are currently only supported on the CLI and not in any
+production Sentinel integration, with the exception of existing configuration
+in [Nomad](https://www.nomadproject.io/docs/configuration/sentinel).
+
+Imports can also be implemented as plugins when Sentinel code itself is too
+restrictive to represent the import, or if you need access to features not
+normally available to the Sentinel runtime.
+
+See the [plugins](/sentinel/extending/plugins) section for more details.
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/extending/internals.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/extending/internals.mdx
new file mode 100644
index 0000000000..1a222ad000
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/extending/internals.mdx
@@ -0,0 +1,253 @@
+---
+page_title: >-
+ Extending Sentinel: Internals
+sidebar_title: Internals
+sidebar_current: docs-extending-internals
+description: This Section covers some details about Sentinel's import internals.
+layout: docs
+---
+
+# Extending Sentinel: Internals
+
+-> **Advanced Topic!** This page covers low-level technical details of Sentinel.
+You don't need to understand these details to effectively use Sentinel. The
+details are documented here for those who wish to learn about them.
+
+This section covers some of the internal details of Sentinel's import system.
+The goal of this section is to help remove notion of "magic" from Sentinel, to
+allow you to trust and understand what Sentinel is doing with imports, and also
+to help practitioners and developers alike understand the differences between
+the ways that imports can be loaded into Sentinel.
+
+## Language and Runtime
+
+Understanding the import system in Sentinel generally requires understanding of
+two key concepts within Sentinel: the _language_, and the _runtime
+implementation_ of the language.
+
+The Sentinel language and the rules governing it are detailed in the [Sentinel
+Language Specification](/sentinel/language/spec). A runtime implementation must
+conform to the rules laid out in the specification at a minimum. However, to
+meet the design goals of a particular implementation, the runtime may implement
+features on top of the language. The subtle implementation details within the
+runtime may also vary while still being compliant with the specification.
+
+The core runtime included within the [Sentinel CLI](/sentinel/commands) is
+sometimes referred to as the _reference implementation_ of the Sentinel
+language. This runtime is also the main runtime embedded within each
+Sentinel-enabled HashiCorp product such as Terraform Cloud and Enterprise, Vault
+Enterprise, Nomad Enterprise, and Consul Enterprise. The runtime includes an API
+to allow these integrations implement Sentinel in as robust or as restrictive of
+a way as possible, so depending on the implementation, your experience may vary.
+
+## Sentinel's Import Model
+
+The concept of Imports within Sentinel is a _language_ feature. You can see the
+exact rules governing imports within the
+[Imports](/sentinel/language/spec#imports) section of the specification.
+
+Within the runtime, there are currently two major classifications of imports:
+
+- **Modules:** The mapping of Sentinel code to an import, essentially mapping a
+ scope of values to a particular package.
+- **Binary Imports:** A grouping of embedded and external plugins written using
+ the Sentinel SDK. These are comprised of internal or embedded imports (usually
+ the [standard imports](/sentinel/imports) and imports included by an
+ integration) and import plugins.
+
+Below is a diagram explaining in brief the relationship between the langauge
+and runtime features.
+
+
+
+This kind of topology ultimately minimizes the visibility of implementation
+internals to the actual policy language. This allows us to keep the Sentinel
+language itself simple and keep concerns such as module or plugin management,
+validation, and configuration out of the language. These kinds of things would
+impact the readability of a policy, possibly present security challenges, and
+would ultimately be of no use to more minimal embedded applications.
+
+### Implementation Differences Between Import Types
+
+As one would imagine, both modules and binary imports are loaded in much
+different ways, and the methods that they expose data to Sentinel differ as
+well. The effect is generally the same however across both, and we take care to
+ensure that both modules and binary imports behave as close as possible to each
+other, however there are some subtle differences:
+
+- Modules currently support the exporting of rules, but do not support function
+ calls with object receiver data (as seen in some standard imports such as
+ [`decimal`](/sentinel/imports/decimal), [`http`](/sentinel/imports/http), and
+ [`time`](/sentinel/imports/time)). This will be coming in a later release.
+- Binary imports cannot export rules. However, they do support object receiver
+ data (see [`framework.New`](/sentinel/extending/plugins#framework-new) in
+ [Extending Sentinel: Plugins](/sentinel/extending/plugins)).
+
+## Modules
+
+_Modules_ are essentially Sentinel code that is intended to be re-used in multiple
+policies as an import.
+
+The mechanism of module loading is fairly straightforward: during initialization
+of the Sentinel runtime, modules are parsed along with policies. The parsed
+_abstract syntax tree_ (AST) for each module is loaded into the runtime under
+the specified _import path_. These are then passed to the lower-level
+interpreter during the evaluation of each policy.
+
+As with policy code, during evaluation, a module's AST is walked to execute the
+code within that specific module. The module data is then stored within an
+specific scope mapped to the import name. The module data can then be accessed
+through the import via selector expressions and function calls (e.g.,
+`foo.some_map` or `foo.some_func()` for a module loaded via `import "foo"`).
+
+The AST for a module is only walked if a policy imports it, and it is _only
+walked once_. That means if a policy and a module import the same module, or a
+policy imports the same module twice (using [import
+aliases](/sentinel/language/imports#aliases)), those instances will share state.
+If you call a function that alters a singleton variable within the module, that
+singleton will be modified for all instances of the import.
+
+-> **Fun fact!** When you [mock an import with Sentinel
+code](/sentinel/configuration#mocking-with-sentinel-code), you are actually
+using a module. The module is loaded with a flag that allows it to override an
+existing import, which would normally give a runtime error.
+
+### Map and List Cloning for Module Calls
+
+It's also worthwhile noting that map and list values are cloned for modules.
+
+Consider the case where you have a module with a map singleton.
+
+```sentinel
+// modules/foo.sentinel
+a_map = {"a": "b"}
+```
+
+Normally, assignment to the singleton, even when using an index expression, is
+blocked, so `foo.a_map["c"] = "d"` would not pass semantic checking.
+
+However, even when you try to do assignment via an intermediary, you will still
+be only modifying the copy, and the original will not be affected:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+a_map_copy = foo.a_map
+a_map_copy["c"] = "d" // Only modifies copy, does not modify module singleton
+print(foo.a_map) // Still only prints {"a": "b"}
+```
+
+This is to ensure that modules are consistent with binary imports in how return
+data is handled, and the expectation that most non-object data within an import
+is read-only, with the exception of modification of singleton data via
+functions. As return of complex object and collection data in a binary import is
+always via copy, it's not possible to modify any value elements within the
+import in a similar fashion.
+
+There are also some other implications of this cloning that are of note:
+
+- Rules are not cloned, either within a list or map, or by themselves. This is
+ to ensure that [memoization](/sentinel/language/rules#lazy-and-memoized)
+ functions correctly. As only a module can export rules at this time, there is no
+ parity for this in binary imports.
+- Functions are _not_ cloned. This mainly has implications for scope - for
+ example, if you assign a function to another value, or assign an object to a
+ value that has a method that utilizes a global module variable, those calls will
+ reference and/or modify those values.
+
+Functions, while represented differently in binary imports, generally follow the
+same logic here - as they would be invoked in the same package within the binary
+import, any singleton values they accessed there would be affected in a similar
+fashion. As rules are pseudo-functions, this generally makes sense here too.
+
+## Binary Imports
+
+_Binary imports_ is a grouping referring to all imports that are designed with
+the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). These can be
+either internally embedded, or executed via a plugin.
+
+All imports found in the [standard library](/sentinel/imports) are binary
+imports, embedded internally.
+
+The main difference between internally embedded imports and external plugins is
+whether or not the runtime needs to execute a plugin binary as part of
+[lifecycle management](#lifecycle), and whether or not it needs to
+[communicate](#communication) over RPC. Internal binary imports do not require
+these steps, so their execution model is somewhat simpler; however, technically,
+they still are the same as external plugins in terms of design model and value
+conversion. Most standard imports are even tested using the [SDK test
+suite](/sentinel/extending/plugins#testing), which builds the import as an
+external plugin.
+
+The remainder of this section discusses topics mostly relevant to external
+plugins. A large amount of technical detail regarding binary import development
+(also applicable to embedded binary imports) can be seen on the
+[Plugins](/sentinel/extending/plugins) page.
+
+-> **NOTE:** At this time, plugins can only be written for the Sentinel CLI, and
+cannot be used with any of Sentinel's integrations.
+
+### Plugin Basics
+
+Sentinel plugins are built on top of the HashiCorp [go-plugin
+system](https://github.com/hashicorp/go-plugin). This is the same plugin system
+powering all pluggable HashiCorp tools such as
+[Terraform](https://www.terraform.io), [Vault](https://www.vaultproject.io), and
+more. go-plugin is a system that has been used in production for millions of
+users for over 5 years.
+
+Plugins are executable binaries. Sentinel is responsible for launching and
+managing the lifecycle of plugins. Once a plugin is running, Sentinel
+communicates with the plugin via [gRPC](https://grpc.io/). The [protocol is open
+source](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/import.proto)
+to allow anyone to write a Sentinel plugin.
+
+### Lifecycle
+
+Sentinel launches plugins and is responsible for plugin lifecycle.
+
+The runtime optimizes for latency by launching the plugin as soon as it is
+configured, rather than when a policy requires it. This ensures that the plugin
+is ready to be used immediately. A plugin is only closed when Sentinel is
+reconfigured to no longer allow that plugin or if the Sentinel-enabled
+application is closing.
+
+When a plugin is removed from the configuration, Sentinel may wait to shut down
+the plugin until all currently executing policies that are using that plugin
+complete. New policy executions will not be allowed to use the old plugins.
+
+Plugins are automatically restarted if they shut down unexpectedly. Policies
+that were executing while this happens may fail. The Sentinel system is
+improving to more gracefully understand locations where it is safe to retry an
+execution.
+
+### Communication
+
+An [initial
+handshake](https://github.com/hashicorp/go-plugin/blob/master/docs/internals.md)
+is done via stdout to determine the main communication location. Following the
+handshake, communication will either occur on a local Unix domain socket or via
+a local-only TCP socket. This communication is done mostly over the low-level
+[`Get`](https://github.com/hashicorp/sentinel-sdk/blob/2b4db07dd12b55142f0c016f301af80aa4422520/proto/import.proto#L12)
+RPC call.
+
+#### Value Conversion
+
+As can generally be seen in [Developing an Import
+Plugin](/sentinel/extending/plugins#developing-an-import-plugin), the Sentinel
+type system is not exposed to binary imports. While explicit values for null and
+undefined exist, values are mostly converted over the wire from their Go values
+to their Sentinel equivalents.
+
+This has a few implications:
+
+- As discussed in [Map and List Cloning for Module
+ Calls](#map-and-list-cloning-for-module-calls), Map and list data returned from
+ binary import value lookups or function calls is always cloned. These can be
+ freely modified without affecting anything within the binary import.
+- It's currently impossible to represent certain Sentinel types such as
+ [rules](/sentinel/language/rules) within a binary import. This is not a major
+ issue at this point in time as practitioners generally do not look to binary
+ imports for rules; we may examine this as a later time if this situation
+ changes.
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/extending/modules.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/extending/modules.mdx
new file mode 100644
index 0000000000..87a9fbc055
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/extending/modules.mdx
@@ -0,0 +1,137 @@
+---
+page_title: >-
+ Extending Sentinel: Modules
+sidebar_title: Modules
+sidebar_current: docs-extending-modules
+description: >-
+ Modules allow you to re-use Sentinel code as an import.
+layout: docs
+---
+
+# Extending Sentinel: Modules
+
+Modules allow you to re-use Sentinel code as an import. This allows for the
+export of any general construct that Sentinel supports, including values,
+functions, and even rules. As such, it's a great way to package code that is
+useful across multiple policies, reducing boilerplate and making final policy
+code simpler.
+
+You may want to use modules for:
+
+- **Writing a simple re-usable helper library.** If you mostly know Sentinel or
+ have helpers that you have written in Sentinel, moving these helpers to a module
+ will offer a low-friction way of facilitating their re-use.
+- **Writing re-usable rules.** If you have a set of
+ [rules](/sentinel/language/rules) you know you want to use across multiple
+ policies, bundling them into a module is a great way of abstracting the logic
+ away.
+- **Abstracting existing Sentinel imports.** Using a module is a great way to
+ extend existing Sentinel imports by abstracting the common functionality that
+ you use within an import to your own workflow.
+
+This page describes how you can use modules within the context of the Sentinel
+CLI. For instructions for a particular integration, see the documentation for
+that product.
+
+-> **Not all Sentinel-enabled applications support modules.** Please refer
+to the documentation of the specific Sentinel-enabled application. Some
+applications disable modules for security reasons.
+
+## Configuring a Module
+
+A module is configured within the Sentinel CLI by adding an entry for it in
+within the `modules` section of the [CLI configuration
+file](/sentinel/configuration). The key for each entry specifies the path that
+the module is imported as. The `path` value within each entry specifies the path
+to the module file on the local filesystem.
+
+```hcl
+module "foo" {
+ source = "modules/foo.sentinel"
+}
+
+module "bar" {
+ source = "modules/bar.sentinel"
+}
+```
+
+In this example, we have two modules:
+
+- Import path `foo`, being loaded from the code within `modules/foo.sentinel`.
+- Import path `bar`, being loaded from the code within `modules/bar.sentinel`.
+
+See the [modules](/sentinel/configuration#modules) section within the CLI
+configuration file syntax page for more details.
+
+### Remote Modules
+
+In addition to loading from a local source, modules can be loaded from a remote
+location. This can increase reusability and allow for sharing modules across
+multiple configurations.
+
+### Testing Using Modules
+
+As with [mocks](/sentinel/configuration#mocking-with-sentinel-code), modules
+are loaded relative to the configuration file location when using `sentinel test`. As such, you need to account for this in your test configuration files:
+
+```hcl
+module "foo" {
+ source = "../../modules/foo.sentinel"
+}
+
+module "bar" {
+ source = "../../modules/bar.sentinel"
+}
+```
+
+This could be a test file for a policy (example: `policy.sentinel`), residing
+under the correct test file directory for this policy (example:
+`test/policy/pass.json`).
+
+## Writing and Using a Module
+
+Writing a module is nearly the same as writing a Sentinel policy, except for the
+following two exceptions:
+
+- Modules do not require [`main`](/sentinel/writing/basic#the-simplest-policy)
+ to be present. It can be, but it will not carry any extra significance as it
+ does within a policy.
+- [`param`](/sentinel/language/parameters) declarations cannot be present in a module. If they are encountered,
+ a runtime error is given.
+
+Once you have a complete module ready for use and configured, using the module
+is as simple as importing it.
+
+Building on the above [configuration example](#configuring-a-module), we can
+export a simple test function in the module `foo` by adding this code to
+`modules/foo.sentinel`:
+
+```sentinel
+// modules/foo.sentinel
+hello = func() {
+ print("hello world!")
+ return undefined
+}
+```
+
+We can then import it within our policy and use it:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+// prints "hello world" in the trace
+foo.hello()
+
+main = true
+```
+
+Note that modules are considered [imports](/sentinel/language/spec#imports) by
+the Sentinel runtime and as such conform to the specification. This means that
+you cannot directly assign values to anything behind a module scope. You can,
+however, use functions to change state.
+
+-> **NOTE:** At this time, modules do not support object receiver data in the
+way that some imports do, like [`time`](/sentinel/imports/time),
+[`decimal`](/sentinel/imports/decimal), and [`http`](/sentinel/imports/http).
+Support for this will be coming in later versions of Sentinel.
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/extending/plugins.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/extending/plugins.mdx
new file mode 100644
index 0000000000..05fe702e54
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/extending/plugins.mdx
@@ -0,0 +1,522 @@
+---
+page_title: >-
+ Extending Sentinel: Plugins
+sidebar_title: Plugins
+sidebar_current: docs-extending-plugins
+description: >-
+ Plugins allow you to write imports as standalone executables, allowing you to
+ extend Sentinel in ways not normally possible with policy code alone.
+layout: docs
+---
+
+# Extending Sentinel: Plugins
+
+-> **NOTE:** Plugins are currently only supported on the CLI and not in any
+production Sentinel integration, with the exception of existing configuration
+in [Nomad](https://www.nomadproject.io/docs/configuration/sentinel).
+
+Plugins allow you to write imports as standalone executables, allowing you to
+extend Sentinel in ways not normally possible with policy code alone.
+
+You might want to write or use a plugin when you need to:
+
+- **Use a provider or API integration for Sentinel.** In this case, you will
+ probably be working with a provider SDK, or other libraries that will be making
+ complex network requests to external services. Sentinel only provides limited
+ capabilities for these kinds of operations in the standard library, so writing
+ this kind of import as a module is not optimal.
+- **Introduce novel functionality not currently supported by Sentinel.**
+ Sentinel's policy language is limited by design to encourage simple policies,
+ reduce its runtime footprint, and to keep it efficient and safe. This will
+ mean that some functionality may be difficult or impossible to express as
+ Sentinel code, and would require a plugin to write.
+- **Extend a complex module.** Additionally, modules are restricted to a single
+ file of Sentinel code, so these will naturally become more and more unwieldy as
+ you continue to add to them. Eventually, there might be a time where a plugin is
+ better suited for the functionality, especially if it's a general-purpose
+ library.
+
+This section details how import plugins are currently configured in the Sentinel
+CLI, and some detail on how they are written. As we continue to build on the
+ability to use import plugins, we will extend this section with more details.
+
+-> **Not all Sentinel-enabled applications will support plugins.** Some
+applications will disable plugins for security reasons. For those that do enable
+plugins, the method to configure plugins will vary.
+
+## Installing Plugins
+
+Sentinel plugins are standalone executables. Sentinel-enabled applications such
+as the CLI launch these plugins and communicate with them via
+[RPC](https://en.wikipedia.org/wiki/Remote_procedure_call). To install a plugin,
+the first step is to download the plugin executable.
+
+After downloading the plugin, you must add it in the CLI configuration file,
+setting its name, path, and any arguments, environment variables, or
+configuration. An example is below:
+
+```json
+{
+ "imports": {
+ "time": {
+ "path": "/path/to/sentinel-import-time",
+ "config": { "fixed_time": 1504155600 }
+ }
+ }
+}
+```
+
+After configuring the plugin, it will be available on the next `sentinel apply`
+or `sentinel test`.
+
+For more details on configuration, see the
+[plugins](/sentinel/configuration#plugins) section of the [CLI configuration
+file syntax](/sentinel/configuration).
+
+## Debugging Plugins
+
+The Sentinel CLI logs when it launches, configures, and closes a plugin. The
+plugin may also log additional information when it is running.
+
+To debug issues with a plugin, you can usually refer to these logs. Depending on
+the plugin configuration, the logs you see may vary - refer to the plugin
+documentation on how to enable logs for a particular plugin.
+
+## Developing an Import Plugin
+
+Anyone can develop a Sentinel import plugin using the [Sentinel
+SDK](https://github.com/hashicorp/sentinel-sdk). The SDK contains a high-level
+framework for writing plugins in [Go](https://golang.org) including a test
+framework. Additionally, the SDK contains the low-level [gRPC protocol buffers
+definition](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/import.proto)
+for writing plugins in other languages.
+
+The rest of this page will document how to write a new Sentinel import in Go
+using the Sentinel SDK and the high-level framework provided. You are expected
+to already know the basics of Go and have a properly configured Go installation.
+
+Throughout this page, we'll be developing a simple import to access time values.
+
+-> **NOTE:** The example here is not reflective of the actual implementation of
+the [`time`](/sentinel/imports/time) standard import, so make sure to not
+get the two confused.
+
+### Import Framework
+
+The Sentinel SDK provides a high-level
+[framework](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework)
+for writing imports. We recommend using this framework.
+
+#### Basic Concepts
+
+This framework works by implementing the correct Go interfaces.
+
+As a general overview:
+[`framework.Import`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Import)
+implements the
+[`sdk.Import`](https://godoc.org/github.com/hashicorp/sentinel-sdk#Import)
+interface and therefore is a valid import plugin. You must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface to configure the import. The root may then delegate nested access to
+various
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+implementations. Other interfaces are implemented to provide functionality past
+what is provided by the basic `framework.Namespace` implementation - these are
+documented below.
+
+This all may sound like a lot of interfaces, but each interface typically only
+requires a single function implementation and you're only required to implement
+a single namespace (`framework.Namespace`).
+
+As we move on, we'll use real examples to make this clear.
+
+#### Implementing framework.Root
+
+To begin, you must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface. This is the interface representing the root of your import. The root
+itself must be implement
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace),
+which allows values to be accessed. Note that the root can optionally implement
+other interfaces, but they're more advanced and omitted here for simplicity.
+
+For our time example, our root may look like this:
+
+```sentinel
+type root struct {
+ time time.Time
+}
+
+// framework.Root impl.
+func (m *root) Configure(raw map[string]interface{}) error {
+ if _, ok := raw["timestamp"]; !ok {
+ raw["timestamp"] = time.Now().Unix()
+ }
+
+ v := raw["timestamp"]
+ timestamp, ok := v.(int)
+ if !ok {
+ return fmt.Errorf("invalid timestamp type %T", v)
+ }
+
+ m.time = time.Unix(timestamp, 0).UTC()
+ return nil
+}
+
+// framework.Namespace impl.
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "minute":
+ return m.time.Minute(), nil
+ }
+
+ return nil, nil
+}
+```
+
+This example implements `framework.Root` and `framework.Namespace` and would
+enable the following within a Sentinel policy once the completed import is
+installed.
+
+```sentinel
+import "time"
+
+print(time.minute)
+main = true
+```
+
+#### framework.Namespace
+
+The
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+interface implements a namespace of values. You saw above that
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+must itself be a namespace. However, a namespace `Get` implementation may
+further return namespaces to access nested values.
+
+This enables behavior such as `time.month.string` vs. `time.month.index`.
+Notice each of these examples accesses a nested value within `month`. This can
+be modeled as namespaces within the framework.
+
+In the example below, we return a new namespace for `month` to do just this:
+
+```sentinel
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "month":
+ return &namespaceMonth{Month: m.time.Month()}, nil
+ }
+
+ // ...
+}
+
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil nil
+}
+```
+
+#### Primitive Types and Structs
+
+A namespace may also return any primitive Go type as well as structs. The
+framework automatically exposes primitive values as you would expect. For
+structs, exported fields are lowercased and exposed to the policies. The
+`sentinel` struct tag can be used to control how Sentinel can access the field.
+
+In the example below, we expose a struct directly from the root namespace:
+
+```sentinel
+type Location struct {
+ Name string
+ TimeZone string `sentinel:"time_zone"`
+}
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "location":
+ return &Location{Name: "somewhere", TimeZone: "some zone"}, nil
+ }
+
+ // ...
+}
+```
+
+This makes the following values work within a Sentinel policy:
+`time.location.name`, `time.location.time_zone`.
+
+If a field has an empty `sentinel` struct tag (example: `sentinel:""`),
+then that field will not be accessible from a policy.
+
+#### Optional Interfaces
+
+The following are optional interfaces that can be implemented on top of
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace).
+All of these interfaces can be implemented at any level, except for
+[`framework.New`](#framework-new), which only works at the root level.
+
+##### `framework.Call`
+
+The
+[`framework.Call`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Call)
+interface can be implemented to support function calls.
+
+Implementing this interface is very similar to attribute access, except instead
+of returning the attribute value, you return a function. The framework uses Go
+reflection to determine the argument and result types and calls it. If the
+argument types do not match or the signature is otherwise invalid, an error is
+returned.
+
+For example, let's implement a function to add months to the current month:
+
+```sentinel
+func (m *root) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *root) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.time.AddDate(0, n, 0).Month()}
+}
+```
+
+You can now call the function. If today was in the month of September,
+the policy below would pass:
+
+```sentinel
+import "time"
+
+main = time.add_month(4).string == "January"
+```
+
+##### `framework.Map`
+
+The optional
+[`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+interface allows an alternative method to to present a namespace back to a
+Sentinel policy as a map.
+
+`framework.Map` is best paired with
+[`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys),
+which allows you to quickly fetch the necessary data via `Get` calls on the
+namespace:
+
+```
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil, nil
+}
+
+func (m *namespaceMonth) Map() (map[string]interface{}, error) {
+ return framework.MapFromKeys(m, []string{"string", "index"})
+}
+```
+
+##### `framework.New`
+
+The optional
+[`framework.New`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#New)
+interface can be implemented on a root namespace to add _methods_ to your
+namespaces.
+
+Data returned from a namespace is memoized and returned as a map, and is
+normally not callable - to call a function to operate on the data, you would
+need to create a new namespace from the top-level of the import, and then make a
+function call on the result within the same expression.
+
+Let's consider the [root example](#implementing-framework-root). Let's say,
+instead of hard-coding the time value at configuration, we wanted to load it
+on-demand using a `time.now` key, and allow `add_month` to be callable on that
+value. Our root and de-coupled time namespace would now look like:
+
+```
+type root struct{}
+
+func (m *root) Configure(raw map[string]interface{}) error { return nil }
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "now":
+ return &namespaceTime{Time: time.Now()}
+ }
+
+ return nil
+}
+
+func (m *root) New(data map[string]interface{}) (framework.Namespace, error) {
+ if v, ok := data["unix"]; ok {
+ if t, ok := v.(int64); !ok {
+ return &namespaceTime{Time: time.Unix(t, 0)}
+ }
+
+ return nil, fmt.Errorf("expected timestamp to be int64, got %T", v)
+ }
+
+ return nil, nil
+}
+
+type namespaceTime struct {
+ Time time.Time
+}
+
+func (m *namespaceTime) Get(key string) interface{} {
+ switch key {
+ case "unix":
+ return m.Time.Unix()
+
+ // case ...
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) Get(key string) (interface{}, error) {
+ return framework.MapFromKeys(m, []string{"unix", "..."})
+}
+
+func (m *namespaceTime) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.Time.AddDate(0, n, 0).Month()}
+}
+```
+
+Via this example, we can now create and assign a `namespaceTime` using `t = time.now`, and then call `t.add_month(months_to_add)` to return a
+`namespaceMonth` for the corresponding month.
+
+Methods on a namespace can also _modfiy_ the receiver data. So you can, for
+example, add a method named `increment_month` that takes no arguments, but
+increments the time stored in `namespaceTime.Time` by a month. Subsequent
+statements using the receiver would see the new value.
+
+Note that there are some restrictions and guidelines that you should take into
+account when using `framework.New`:
+
+- Only data that makes it back to a policy can be used as receiver data to
+ instantiate new namespaces. As such it's recommended to make use of
+ [`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+ and
+ [`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys)
+ to return all of the attribute data you need to construct new objects.
+- `framework.New` is only supported on the root namespace and has no effect on
+ namespaces below the root. Check all possible cases of receiver data within
+ the top-level `New` function and return the appropriate namespace based on the
+ input data.
+- Do not return general errors from your `New` method. When an unknown lookup is
+ made off of memoized data, it will hit your `New` method for possible
+ instantiation and key calls. This will ensure `undefined` is correctly
+ returned for these cases.
+- `framework.New` is designed to add utility to imports where calling methods on
+ a value is more intuitive than just making a function call, or just returning
+ all of a data set as a memoized map. Use it sparingly and avoid using it on
+ recursively complex data sets.
+
+### Testing
+
+The SDK exposes a testing framework to verify your plugin works as
+expected. This test framework integrates directly into `go test` so it
+is part of a familiar workflow.
+
+The test framework works by dynamically building your import and
+running it against the `sentinel` CLI to verify it behaves as expected.
+This ensures that your plugin builds, your plugin communicates via RPC
+correctly, and that the behavior within a policy is also correct.
+
+The example below shows a complete ready table-driven test for our time plugin.
+You can run this with a normal `go test`.
+
+```sentinel
+package main
+
+import (
+ "os"
+ "testing"
+
+ "github.com/hashicorp/sentinel-sdk"
+ plugintesting "github.com/hashicorp/sentinel-sdk/testing"
+)
+
+func TestMain(m *testing.M) {
+ exitCode := m.Run()
+ plugintesting.Clean()
+ os.Exit(exitCode)
+}
+
+func TestImport(t *testing.T) {
+ cases := []struct {
+ Name string
+ Data map[string]interface{}
+ Source string
+ }{
+ {
+ "month",
+ map[string]interface{}{"timestamp": 1495483674},
+ `main = subject.month.string == "September"`,
+ },
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.Name, func(t *testing.T) {
+ plugintesting.TestImport(t, plugintesting.TestImportCase{
+ Config: tc.Data,
+ Source: tc.Source,
+ })
+ })
+ }
+}
+```
+
+### Building Your Import
+
+To build your import, you must first implement the `main` function.
+The main function should just use the `rpc.Serve` method to serve the
+plugin over the Sentinel RPC layer:
+
+```sentinel
+package main
+
+import (
+ "github.com/hashicorp/sentinel-sdk"
+ "github.com/hashicorp/sentinel-sdk/rpc"
+)
+
+func main() {
+ rpc.Serve(&rpc.ServeOpts{
+ ImportFunc: func() sdk.Import {
+ return &framework.Import{Root: &root{}}
+ },
+ })
+}
+```
+
+You can then build this using `go build`. The output can be named
+anything. Once your plugin is built, [install it](#installing-plugins)
+and try it out!
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/functions/append.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/functions/append.mdx
new file mode 100644
index 0000000000..1b3e54b8e5
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/functions/append.mdx
@@ -0,0 +1,47 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Append'
+sidebar_title: 'append'
+sidebar_current: docs-funcs-append
+description: The built-in function `append` appends a value to the end of a list.
+layout: docs
+---
+
+# Builtin Function: append
+
+The built-in function `append` appends a value to the end of a list. The list
+is modified in-place. The return value will always be [undefined](/sentinel/language/undefined).
+
+`append` called on any value other than a list or undefined will result
+in an immediate error.
+
+Examples:
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+### Why does this function return undefined?
+
+This function returns `undefined` to avoid unintended side effects of the function in the context
+of a [rule](/sentinel/language/rules).
+
+For example, if `append` returned the list value as a result, the following policy would depend
+on the order in which rules are referenced:
+
+```sentinel
+list = []
+a = rule { append(list, 1) contains 1 }
+b = rule { append(list, 2) contains 2 }
+
+// Scenario 1: list becomes [1, 2]
+main = rule { a and b }
+
+// Scenario 2: list becomes [2, 1]
+main = rule { b and a }
+```
+
+This sort of side effect behavior introduces complex and difficult to track logical errors in programs.
+As a result, functions like this return `undefined` and modify values in-place.
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/functions/delete.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/functions/delete.mdx
new file mode 100644
index 0000000000..0a6340876d
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/functions/delete.mdx
@@ -0,0 +1,26 @@
+---
+page_title: 'Sentinel Language - Builtin Function: delete'
+sidebar_title: 'delete'
+sidebar_current: docs-funcs-delete
+description: The built-in function `delete` deletes an element from a map.
+layout: docs
+---
+
+# Builtin Function: delete
+
+The built-in function `delete` deletes an element from a map.
+
+If the element to delete does not exist, the map is left unchanged.
+Calling `delete` on a value that is not a map or
+[undefined](/sentinel/language/undefined)
+is an immediate error. The result of `delete` is always `undefined`.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/functions/error.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/functions/error.mdx
new file mode 100644
index 0000000000..1d06dfc432
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/functions/error.mdx
@@ -0,0 +1,16 @@
+---
+page_title: 'Sentinel Language - Builtin Function: error'
+sidebar_title: 'error'
+sidebar_current: docs-funcs-error
+description: >-
+ The built-in function `error` is used to exit immediately with an error
+ message.
+layout: docs
+---
+
+# Builtin Function: error
+
+The built-in function `error` is used to exit immediately with an error message.
+
+Please see the [page on errors](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/functions/index.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/functions/index.mdx
new file mode 100644
index 0000000000..0d900b33d0
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/functions/index.mdx
@@ -0,0 +1,14 @@
+---
+page_title: Sentinel Language - Builtin Functions
+sidebar_title: Builtin Functions
+sidebar_current: docs-funcs
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Language: Builtin Functions
+
+Builtin functions are
+[functions](/sentinel/language/functions)
+that are available in all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in function.
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/functions/keys.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/functions/keys.mdx
new file mode 100644
index 0000000000..36d99bc2b7
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/functions/keys.mdx
@@ -0,0 +1,23 @@
+---
+page_title: 'Sentinel Language - Builtin Function: keys'
+sidebar_title: 'keys'
+sidebar_current: docs-funcs-keys
+description: The built-in function `keys` returns the keys of a map.
+layout: docs
+---
+
+# Builtin Function: keys
+
+The built-in function `keys` returns the keys of a map.
+
+`keys` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned keys are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+```
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/functions/length.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/functions/length.mdx
new file mode 100644
index 0000000000..efe184c0f1
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/functions/length.mdx
@@ -0,0 +1,24 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Length'
+sidebar_title: 'length'
+sidebar_current: docs-funcs-length
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Builtin Function: length
+
+The built-in function `length` returns the length of a collection or string.
+
+The length of a string is the number of bytes in the string. It is not
+necessarilly the number of characters. The length of a collection is the
+number of elements in that collection. The length of
+[undefined](/sentinel/language/undefined) is `undefined`.
+
+Examples:
+
+```sentinel
+length("foo") // 3
+length([1, 2, 3, 4]) // 4
+length({ "key": "value" }) // 1
+```
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/functions/print.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/functions/print.mdx
new file mode 100644
index 0000000000..9d714195db
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/functions/print.mdx
@@ -0,0 +1,14 @@
+---
+page_title: 'Sentinel Language - Builtin Function: print'
+sidebar_title: 'print'
+sidebar_current: docs-funcs-print
+description: The built-in function `print` is used for logging and debugging.
+layout: docs
+---
+
+# Builtin Function: print
+
+The built-in function `print` is used for logging and debugging.
+
+Please see the [page on logging](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/functions/range.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/functions/range.mdx
new file mode 100644
index 0000000000..0a9a07aa6f
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/functions/range.mdx
@@ -0,0 +1,50 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Range'
+sidebar_title: 'range'
+sidebar_current: docs-funcs-range
+description: The built-in function `range` returns a list of numbers in a range.
+layout: docs
+---
+
+# Builtin Function: range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it
+defaults to `1`.
+
+Negative steps are permitted and count down from `start` towards `end`, instead
+of up.
+
+The range ends when the next value given by the sum of the last value and `step`
+would exceed `end`, regardless of if it has been reached.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4] - starts at 1 instead of 0
+range(1, 5, 2) // [1,3] - 3+2 does not satisfy r[-1] < end
+range(0, -3, -1) // [0,-1,-2] - example of negative range
+```
+
+Impossible ranges, or ranges where `start` will never reach `end` given `step`,
+yield an empty list.
+
+```
+range(1, 1) // [] - already starting at 1
+range(0, 1, -1) // [] - negative step will never reach 1
+```
+
+Supplying an undefined value to any argument of `range` yields an undefined
+value back.
+
+A range with a zero step is a runtime error.
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/functions/values.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/functions/values.mdx
new file mode 100644
index 0000000000..95b448f6c7
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/functions/values.mdx
@@ -0,0 +1,23 @@
+---
+page_title: 'Sentinel Language - Builtin Function: values'
+sidebar_title: 'values'
+sidebar_current: docs-funcs-values
+description: The built-in function `values` returns the values of a map.
+layout: docs
+---
+
+# Builtin Function: values
+
+The built-in function `values` returns the values of a map.
+
+`values` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned values are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+values(data) // [3, 2]
+```
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/guides/index.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/guides/index.mdx
new file mode 100644
index 0000000000..8577828033
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/guides/index.mdx
@@ -0,0 +1,12 @@
+---
+page_title: Documentation
+sidebar_current: guides-index
+description: TODO Guides
+layout: docs
+---
+
+# Sentinel Guides
+
+Welcome to the Sentinel Guides!
+
+TODO
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/imports/decimal.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/imports/decimal.mdx
new file mode 100644
index 0000000000..88524e0dd9
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/imports/decimal.mdx
@@ -0,0 +1,319 @@
+---
+page_title: 'Import: decimal'
+sidebar_title: 'decimal'
+sidebar_current: docs-imports-decimal
+description: |-
+ The decimal import provides functions for operating on numbers represented
+ as decimals.
+layout: docs
+---
+
+# Import: decimal
+
+The decimal import provides functions for operating on numbers represented
+as decimals.
+
+Binary floating-point numbers provided by the `float` type are unable to
+fully represent numbers like `1.1` and `2.2`. The decimal import can
+represent these numbers exactly, and so should be used when exactness is
+required.
+
+Decimals are created through the `new` function, which takes any type that
+can represent a number. Arguments to the decimal operators can also take
+a value of any of these types. The full list is:
+
+- float
+- int
+- string
+- existing decimal value
+
+### decimal.infinity(sign)
+
+Creates an infinite-value decimal. Infinite-value decimals are always larger
+or smaller, depending on sign, than any non-infinite decimals.
+
+```sentinel
+decimal.infinity(1) // +Infinity
+decimal.infinity(-1) // -Infinity
+```
+
+Also available as `decimal.inf`.
+
+### decimal.nan
+
+A decimal representing a "not-a-number" value. NaNs are not equal to any
+other number, even themselves.
+
+```sentinel
+decimal.new(-1).sqrt() // NaN
+decimal.new(0).mul(decimal.inf(1)) // NaN
+decimal.nan.is(decimal.nan) // false
+```
+
+### decimal.is_infinite(d)
+
+Evaluates to true if the decimal is infinite.
+
+```sentinel
+decimal.is_infinite(100) // false
+decimal.is_infinite("Infinity") // true
+```
+
+Also available as `decimal.is_inf`, `decimal.isinf` and `decimal.isinfinite`.
+
+### decimal.is_nan(d)
+
+Evaluates to true if the decimal is not-a-number.
+
+```sentinel
+decimal.is_nan("NaN") // true
+```
+
+Also available as `decimal.isnan`.
+
+### decimal.new(v)
+
+Constructs a decimal from another value.
+
+```sentinel
+decimal.new("1.1") // Constructs a decimal from a string.
+decimal.new(2.2) // Constructs a decimal from a float.
+decimal.new(3) // Constructs a decimal from an integer.
+decimal.new("1.1234E+400") // Constructs a decimal from a string using E-notation.
+```
+
+## Type: number
+
+Numbers are represented internally by a sign, coefficient, and exponent.
+The value is calculated with the formula `sign * coefficient * 10^exponent`.
+Exponent is limited to 32 bits, and the coefficient is limited by the total
+precision.
+
+The maximum precision a number can represent is 100 digits. This includes
+both before and after the decimal place.
+
+### number.string
+
+A string representation of the decimal.
+
+```sentinel
+decimal.new(1.5).string // 1.5
+```
+
+### number.sign
+
+A number between `-1` and `+1` representing the sign of the decimal.
+
+```sentinel
+decimal.new(1.5).sign // 1
+```
+
+### number.coefficient
+
+The coefficient component of the decimal.
+
+```sentinel
+decimal.new(1.5).coefficient // 15
+```
+
+### number.exponent
+
+The exponent component of the decimal.
+
+```sentinel
+decimal.new(1.5).exponent // -1
+```
+
+### number.float
+
+A floating-point representation of the decimal.
+
+```sentinel
+decimal.new(1.5).float // 1.500000
+```
+
+### number.int
+
+An integer representation of the decimal. This always rounds down to the nearest integer.
+
+```sentinel
+decimal.new(1.5).int // 1
+```
+
+### number.is(v)
+
+Test for equality with another value.
+
+```sentinel
+decimal.new(1.5).is(1) // false
+```
+
+### number.is_not(v)
+
+Test for inequality with another value.
+
+```sentinel
+decimal.new(1.5).is_not(1) // true
+```
+
+### number.less_than(v)
+
+Test that the decimal is less than another value.
+Can also be called as `number.lt(v)`
+
+```sentinel
+decimal.new(1.5).less_than(1) // false
+```
+
+### number.less_than_or_equals(v)
+
+Test that the decimal is less than or equals to another value.
+Can also be called as `number.lte(v)`
+
+```sentinel
+decimal.new(1.5).less_than_or_equals(1) // false
+```
+
+### number.greater_than(v)
+
+Test that the decimal is greater than another value.
+Can also be called as `number.gt(v)`
+
+```sentinel
+decimal.new(1.5).greater_than(1) // true
+```
+
+### number.greater_than_or_equals(v)
+
+Test that the decimal is greater than or equals to another value.
+Can also be called as `number.gte(v)`
+
+```sentinel
+decimal.new(1.5).greater_than_or_equals(1) // true
+```
+
+### number.add(v)
+
+Add another number to the decimal.
+
+```sentinel
+decimal.new(1.5).add(1) // 2.5
+```
+
+### number.subtract(v)
+
+Subtract another number from the decimal.
+Can also be called as `number.sub(v)`
+
+```sentinel
+decimal.new(1.5).subtract(1) // 0.5
+```
+
+### number.multiply(v)
+
+Multiply the decimal by a number.
+Can also be called as `number.mul(v)`
+
+```sentinel
+decimal.new(1.5).multiply(2) // 3.0
+```
+
+### number.divide(v)
+
+Divide the decimal by a number.
+Can also be called as `number.div(v)`
+
+```sentinel
+decimal.new(3.0).divide(1.5) // 2
+```
+
+### number.modulo(v)
+
+Find the remainder after dividing the decimal by a number.
+Can also be called as `mod`, `remainder`, or `rem`.
+
+```sentinel
+decimal.new(1.5).modulo(1) // 0.5
+```
+
+### number.power(v)
+
+Raise the decimal to a power.
+Can also be called as `number.pow(v)`
+
+```sentinel
+decimal.new(1.5).power(2) // 2.25
+```
+
+### number.exp()
+
+The natural exponent of the decimal. This calculates `e^number`.
+
+```sentinel
+decimal.new(1.5).exp() // 4.4816...
+```
+
+### number.loge()
+
+The natural logarithm of the decimal.
+Can also be called as `number.ln()`
+
+```sentinel
+decimal.new(1.5).loge() // 0.4054...
+```
+
+### number.log10()
+
+The base-10 logarithm of the decimal.
+Can also be called as `number.log()`
+
+```sentinel
+decimal.new(1.5).log10() // 0.1760...
+```
+
+### number.square_root()
+
+The square root of the decimal.
+Can also be called as `number.sqrt()`
+
+```sentinel
+decimal.new(1.5).square_root() // 1.2247...
+```
+
+### number.ceiling()
+
+Round the decimal to the smallest integer greater or equal to itself.
+Can also be called as `number.ceil()`
+
+```sentinel
+decimal.new(1.5).ceiling() // 2
+```
+
+### number.floor()
+
+Round the decimal to the largest integer less than or equal to itself.
+
+```sentinel
+decimal.new(1.5).floor() // 1
+```
+
+### number.absolute()
+
+The absolute value of the decimal.
+Can also be called as `number.abs()`
+
+```sentinel
+decimal.new(1.5).absolute() // 1.5
+decimal.new(-1.5).absolute() // 1.5
+```
+
+### number.negate()
+
+The negated value of the decimal. A positive decimal will become negative,
+and a negative decimal will become positive.
+Can also be called as `number.neg()`
+
+```sentinel
+decimal.new(1.5).negate() // -1.5
+decimal.new(-1.5).negate() // 1.5
+```
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/imports/http.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/imports/http.mdx
new file mode 100644
index 0000000000..ecfc5d2b42
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/imports/http.mdx
@@ -0,0 +1,305 @@
+---
+page_title: 'Import: http'
+sidebar_title: 'http'
+sidebar_current: docs-imports-http
+description: The http import enables the use of HTTP-accessible data from outside the runtime in Sentinel policy rules.
+layout: docs
+---
+
+# Import: http
+
+The http import enables the use of HTTP-accessible data from outside
+the runtime in Sentinel policy rules.
+
+At the center of the import is the [`get()`](#clientgeturl_or_request) function, which issues a GET
+request and returns a `response` type:
+
+```sentinel
+import "http"
+
+resp = http.get("https://example.hashicorp.com")
+main = rule { resp.body contains "something" }
+```
+
+By default, any request response that is not a successful "200 OK" HTTP
+response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information and
+how to customize this behavior.
+
+For more advanced requests which require setting request headers, use a
+[`request` type](#type-request):
+
+```sentinel
+import "http"
+import "json"
+
+param token
+
+req = http.request("https://example.hashicorp.com").with_header("Authorization", "token "+token)
+resp = json.unmarshal(http.get(req).body)
+main = rule { resp["some_key"] is true }
+```
+
+The `with_header` function above returns the `request` object with the
+`Authorization` HTTP header set to value of the variable `some_value`. Many
+of the methods within the http import API are designed around this concept
+of method chaining, allowing for complex building of HTTP requests
+encapsulated in functions. The following is an idiomatic example further
+demonstrating this pattern:
+
+```sentinel
+import "http"
+import "json"
+
+param github_user
+param github_token
+
+client = func(req) {
+ return http.with_timeout(5).with_retries(3)
+}
+
+github_request = func(path) {
+ full_url = "https://api.github.com" + path
+ return http.request(full_url).
+ with_basic_auth(github_user, github_token).
+ with_header("Accept", "application/vnd.github.v3+json").
+ with_header("Custom", "foo"),
+}
+
+default_response = client.get(github_request("/example"))
+
+# Override the Custom header for this single request
+custom_header_response = json.unmarshal(
+ client.get(
+ github_request("/example").with_header("Custom", "bar"),
+ ),
+)
+
+# Override the timeout value for a known slower request
+slow_response = json.unmarshal(
+ client.with_timeout(15).get(github_request("/slow-endpoint")),
+)
+
+some_key_is_true = rule { json.unmarshal(default_response.body)["some_key"] is true }
+body_contains_text = rule { custom_header_response.body contains "something" }
+response_is_ok = rule { slow_response.status_code is 200 }
+
+main = rule { some_key_is_true and body_contains_text and response_is_ok }
+```
+
+### http.client
+
+Retrieve the base HTTP client with default settings. The returned client may be
+configured by calling configuration functions on it; all of these configuration
+functions on are also aliased as top-level functions in this namespace. See
+[`client`](#type-client) below.
+
+### http.request(url)
+
+Create a new [`request`](#type-request) to the specified `url` (string). A
+`request` type enables customization of headers and other concerns before then
+providing the constructed `request` to [`get()`](#clientgeturl_or_request).
+
+```sentinel
+req = http.request("example.hashicorp.com/foo").with_header("Foo", "bar")
+resp = http.get(req)
+```
+
+## Type: client
+
+All of the following configuration functions on the default client are also
+aliased as top-level functions in the import. This allows a short-hand (and
+idiomatic) way of configuring a new client without having to directly access
+`http.client` each time:
+
+```sentinel
+# The following two lines are semantically the same:
+resp = http.client.with_timeout(5).get(url)
+resp = http.with_timeout(5).get(url)
+```
+
+### client.get(url_or_request)
+
+Issue a GET request with a URL string or a `request` type. Returns a `response`
+type.
+
+When `url_or_request` is a string, a GET request is made with the URL
+specified by the string. To customize HTTP headers and other settings, pass
+a request. By default, a request has a timeout value of 10 seconds and 1
+retry. These settings can be adjusted with [`with_timeout()`](#clientwith_timeoutinteger) and
+[`with_retries()`](#clientwith_retriesinteger), respectively.
+
+If the response is one of the following redirect codes, `get()` follows the
+redirect, up to a maximum of 10 redirects:
+
+301 (Moved Permanently)
+302 (Found)
+303 (See Other)
+307 (Temporary Redirect)
+308 (Permanent Redirect)
+
+If the redirect limit is exceeded, the result is a policy error.
+
+By default, after any redirects are followed (up to the maximum), any request
+response that is not a successful "200 OK" response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information
+and how to customize this behavior.
+
+### client.accept_status_codes(list)
+
+Configures a client to not automatically cause a policy failure if
+the resulting response's status code is in `list`. Any status code received
+that is not present in this list will trigger a runtime error.
+
+By default, any request response that is not a successful "200 OK" response
+causes a policy error. This is for convenience, as the most common scenario
+by far is to receive a response and immediately signal a policy error if the
+status code is not 200. Use this scoping to specify a list of non-redirect
+HTTP codes that you wish to accept without error and evaluate the response
+yourself.
+
+Redirects are not considered final status codes in this context. That is,
+redirects are always followed up to the maximum allowed value (see [`get()`](#clientgeturl_or_request))
+and the final response code in the redirect chain is validated against
+`list`.
+
+```sentinel
+resp = http.accept_status_codes([200, 404]).get(url)
+main = rule { resp.status_code is 404 }
+```
+
+### client.accepted_status_codes
+
+Returns a list of the client's accepted status codes.
+
+```sentinel
+client = http.accept_status_codes([200, 404])
+main = rule { client.accepted_status_codes contains 404 }
+```
+
+### client.with_retries(integer)
+
+Sets the maximum number of retries, specified by `integer`, that should be
+attempted on an unsuccessful request. An unsuccessful request is considered
+to be any 5xx response except `501 (Not Implemented)` or a request timeout.
+By default, one retry is attempted.
+
+The maximum number of retries is 10, for a total of 11 request attempts.
+When a request exceeds the maximum number of retries without a response, the
+result is a policy error. Specifying a number larger than this will result
+in a runtime error.
+
+Between each retry, an exponentially increasing delay is observed, allowing
+time for the server to recover. This delay starts at 1 second for the first
+retry and increases each attempt thereafter. The maximum delay for a single
+attempt is 30 seconds.
+
+Retries can be disabled by specifying 0.
+
+### client.retries
+
+Returns the client's configured number of retries as an integer.
+
+### client.with_timeout(integer)
+
+Sets the request timeout to the number of seconds indicated by `integer`.
+
+Each individual request performed by the HTTP client will be limited by the
+given request timeout. This timeout includes connection time, any redirects,
+and reading the response body.
+
+A maximum of 30 seconds is allowed. Specifying a number larger than this
+will result in a runtime error.
+
+By default, requests will time out after 10 seconds.
+
+### client.timeout
+
+Returns the client's configured timeout in whole seconds as an integer.
+
+### client.without_certificate_verification()
+
+Skips verification of TLS certificates during secure HTTP operations,
+allowing for requests to `https://` endpoints which are secured with a TLS
+certificate signed by an untrusted certificate authority. Takes no arguments.
+
+By default, the HTTP client will trigger a runtime error if a TLS certificate
+presented by a server is from an untrusted certificate authority.
+
+Do not change this setting unless you are aware of the risks involved.
+Without verification, TLS accepts any certificate presented by the server
+and any host name in that certificate. In this mode, TLS is susceptible to
+man-in-the-middle attacks. This option should only be used for testing or in
+trusted networks with self-signed certificates.
+
+```sentinel
+http.without_certificate_verification().get(url)
+```
+
+### client.certificate_verification
+
+Returns true if the client requires TLS certificates to be verifiable.
+
+## Type: request
+
+### request.url
+
+Returns the request URL as a string.
+
+### request.headers
+
+Returns the configured request headers as a string-keyed map of strings.
+
+### request.with_header(key, value)
+
+Returns a `request` with the header `key` (string) set to `value` (string).
+
+Values are overridden on subsequent calls to the same `key`. To specify
+multiple values, use the existing value when setting the new one.
+
+```sentinel
+original = http.request("http://example.hashicorp.com").with_header("Foo", "bar")
+overridden = original.with_header("Foo", "baz")
+multi_value = original.with_header("Foo", original.headers["Foo"] + ",baz")
+
+main = rule {
+ original.headers["Foo"] is "bar" and
+ overridden.headers["Foo"] is "baz" and
+ multi_value.headers["Foo"] is "bar,baz"
+}
+```
+
+### request.with_basic_auth(username, password)
+
+Returns a `request` with the `Authorization` header set to use HTTP Basic
+Authentication with the provided username and password.
+
+Note that with HTTP Basic Authentication the provided username and password
+are encoded, but not encrypted.
+
+## Type: response
+
+### response.status_code
+
+The response status code as an integer, e.g. `200`.
+
+### response.headers
+
+The response headers as a map.
+
+### response.body
+
+The response body as a string. Callers should unmarshal the content to a useful
+representation as necessary based on the content type. For example, if the
+response is in JSON:
+
+```sentinel
+import "http"
+import "json"
+
+# This example endpoint returns {"some_key": true}
+req = http.request("https://example.hashicorp.com/some-json")
+
+resp = json.unmarshal(http.get(req).body)
+main = rule { keys(resp) contains "some_key" and resp["some_key"] is true }
+```
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/imports/index.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/imports/index.mdx
new file mode 100644
index 0000000000..0a62cb7b02
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/imports/index.mdx
@@ -0,0 +1,19 @@
+---
+page_title: Sentinel Language - Standard Imports
+sidebar_title: Standard Imports
+sidebar_current: docs-imports
+description: >-
+ Standard Imports are the built-in imports that are available to all Sentinel
+ policies.
+layout: docs
+---
+
+# Standard Imports
+
+Standard Imports are the built-in [imports](/sentinel/language/imports)
+that are available to all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in import.
+
+Sentinel-embedded applications can choose to allow or deny certain
+standard imports. Please reference the documentation for the Sentinel-enabled
+application you're using to determine if all standard imports are available.
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/imports/json.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/imports/json.mdx
new file mode 100644
index 0000000000..45a75d30d5
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/imports/json.mdx
@@ -0,0 +1,32 @@
+---
+page_title: 'Import: json'
+sidebar_title: 'json'
+sidebar_current: docs-imports-json
+description: The json import enables a Sentinel policy to parse and access a JSON document.
+layout: docs
+---
+
+# Import: json
+
+The json import enables a Sentinel policy to parse and access a JSON
+document.
+
+### json.unmarshal(obj)
+
+Unmarshals the JSON object `obj` into a native Sentinel structure.
+
+All native JSON types can be represented perfectly as Sentinel native types.
+
+```sentinel
+// Typically the input for this would come from an external source.
+config = json.unmarshal("{ \"foo\": 42 }")
+config.foo // 42
+config.bar // undefined (as usual for accessing a non-existent map key)
+```
+
+### json.marshal(obj)
+
+Marshals the Sentinel object `obj` into a JSON object encoded as a string.
+
+Functions and `undefined` cannot be natively encoded as JSON. If values of
+either type are found in the structure, this will return undefined.
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/imports/runtime.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/imports/runtime.mdx
new file mode 100644
index 0000000000..5bfa156825
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/imports/runtime.mdx
@@ -0,0 +1,17 @@
+---
+page_title: 'Import: runtime'
+sidebar_title: 'runtime'
+sidebar_current: docs-imports-runtime
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: runtime
+
+The runtime import contains various information about the Sentinel runtime. It
+can be used to get information about the runtime as it's embedded in the CLI or
+a specific integration, such as validating a specific runtime version.
+
+### runtime.version
+
+Returns the version of Sentinel within this implementation.
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/imports/sockaddr.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/imports/sockaddr.mdx
new file mode 100644
index 0000000000..c200e69796
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/imports/sockaddr.mdx
@@ -0,0 +1,42 @@
+---
+page_title: 'Import: sockaddr'
+sidebar_title: 'sockaddr'
+sidebar_current: docs-imports-sockaddr
+description: The sockaddr import makes working with IP addresses easy.
+layout: docs
+---
+
+# Import: sockaddr
+
+The sockaddr import makes working with IP addresses easy.
+
+### sockaddr.is_contained(outer, inner)
+
+The is_contained function returns true if `inner` is an address that
+is contained within `outer`.
+
+```sentinel
+sockaddr.is_contained("192.168.0.0/24", "192.168.0.32") // true
+sockaddr.is_contained("192.168.0.0/24", "192.174.1.1") // false
+```
+
+### sockaddr.is_equal(addr1, addr2)
+
+The is_equal function returns true if addr1 is equal to addr2.
+
+```sentinel
+sockaddr.is_equal("192.168.12.24", "192.168.12.24") // true
+```
+
+### sockaddr.is_ipv4(addr)
+
+The is_ipv4 function returns true if addr is an IPv4 address.
+
+```sentinel
+sockaddr.is_ipv4("192.168.12.24") // true
+sockaddr.is_ipv4("2001:0db8:85a3:0000:0000:8a2e:0370:7334") // false
+```
+
+### sockaddr.is_ipv6(addr)
+
+The is_ipv6 function returns true if addr is an IPv6 address.
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/imports/strings.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/imports/strings.mdx
new file mode 100644
index 0000000000..bdacfc68a1
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/imports/strings.mdx
@@ -0,0 +1,92 @@
+---
+page_title: 'Import: strings'
+sidebar_title: 'strings'
+sidebar_current: docs-imports-strings
+description: The strings import exposes common string operations.
+layout: docs
+---
+
+# Import: strings
+
+The strings import exposes common string operations.
+
+### strings.has_prefix(s, prefix)
+
+Returns true if `s` starts with `prefix`.
+
+```sentinel
+strings.has_prefix("billing-id", "billing-") // true
+strings.has_prefix("bill-id", "billing-") // false
+```
+
+### strings.has_suffix(s, suffix)
+
+Returns true if `s` ends with `suffix`.
+
+```sentinel
+strings.has_suffix("billing-id", "id") // true
+strings.has_suffix("billing-name", "id") // false
+```
+
+### strings.join(a, sep)
+
+Joins a list with the string supplied by `sep`.
+
+Multi-dimensional lists are supported, and non-string primitive
+types will be converted to their string equivalents. Maps and
+other complex non-list types are not supported.
+
+```sentinel
+strings.join(["foo", "bar", "baz"], ".") // "foo.bar.baz"
+strings.join([["foo", "bar"], "baz"], ".") // "foo.bar.baz"
+strings.join(["a", 1, 1.01, true], ",") // "a,1,1.01,true"
+```
+
+### strings.trim_prefix(s, prefix)
+
+Trim the prefix from the string `s`. If the string doesn't have the
+prefix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_prefix("billing-id", "billing-") // "id"
+strings.trim_prefix("bill-id", "billing-") // "bill-id"
+```
+
+### strings.trim_suffix(s, suffix)
+
+Trim the suffix from the string `s`. If the string doesn't have the
+suffix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_suffix("billing-id", "-id") // "billing"
+strings.trim_suffix("bill-id", "-foo") // "bill-id"
+```
+
+### strings.to_lower(s)
+
+Lowercase the string s.
+
+```sentinel
+strings.to_lower("FoO") // "foo"
+```
+
+### strings.to_upper(s)
+
+Uppercase the string s.
+
+```sentinel
+strings.to_upper("FoO") // "FOO"
+```
+
+### strings.split(s, sep)
+
+Split the string 's' via a substring 'sep'. If 'sep' is not contained in
+'s', the return value will be a single-element list containing only 's'.
+As a special-case, if the input is already a list, it will be returned
+as-is. This allows calling the split function when not knowing if the input
+is already a list or not.
+
+```sentinel
+strings.split("foo,bar,baz", ",") // ["foo", "bar", "baz"]
+strings.split(["foo", "bar", "baz"], ",") // ["foo", "bar", "baz"]
+```
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/imports/time.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/imports/time.mdx
new file mode 100644
index 0000000000..d4d8d6d089
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/imports/time.mdx
@@ -0,0 +1,258 @@
+---
+page_title: 'Import: time'
+sidebar_title: 'time'
+sidebar_current: docs-imports-time
+description: The time import provides access to the execution time and time functions.
+layout: docs
+---
+
+# Import: time
+
+The time import provides access to the execution time and time functions.
+
+During the execution of a policy the time is fixed to the moment of
+execution. This gives the illusion that a policy instantly executes at a
+single moment of time.
+
+The import uses Coordinated Universal Time (UTC).
+
+As an example of this, if a policy accessed `time.now.second` multiple times,
+for each access the same value would be returned even if they are several seconds apart.
+This is the execution `timespace`, which is mapped to `time.now`.
+
+It is also possible to run functions on a custom time by using the
+`time.load()` function to create a new timespace from the specified value.
+See the documentation for available actions on timespaces.
+
+Times specified as the reference time or via the `time.load()` function can
+be specified in a `timeish` fashion. This is either one of:
+
+- The number of seconds since the Unix epoch (Thursday, 1 January 1970,
+ 00:00:00 UTC),
+- A timestamp matching the format outlined in RFC3339, either in the
+ format `2006-01-02T15:04:05+07:00`, which includes a timezone offset, or
+ `2006-01-02T15:04:05Z`, which indicates UTC.
+
+### time.now
+
+Create a `timespace` locked either to execution time or, if set, the
+reference time. In a given execution, this will always produce the same
+value.
+
+```sentinel
+time.now // {"day": 17, "hour": 23, "minute": 19, "month": 11 ... }
+```
+
+### time.load(timeish)
+
+Create a timespace using the given value. Please see the import
+documentation for valid values for "timeish".
+
+```sentinel
+time.load("2019-11-16T01:20:30Z") // {"day": 16, "hour": 1, "minute": 20, "month": 11 ... }
+```
+
+### time.nanosecond
+
+A nanosecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.nanosecond * 1) // 2019-11-16T01:20:30.000000001Z
+```
+
+### time.microsecond
+
+A microsecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.microsecond * 1) // 2019-11-16T01:20:30.000001Z
+```
+
+### time.millisecond
+
+A millisecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.millisecond * 1) // 2019-11-16T01:20:30.001Z
+```
+
+### time.second
+
+A second duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.second * 1) // 2019-11-16T01:20:31Z
+```
+
+### time.minute
+
+A minute for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.minute * 1) // 2019-11-16T01:21:30Z
+```
+
+### time.hour
+
+An hour duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+## Type: timespace
+
+### timespace.hour
+
+The hour from 0 to 23.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").hour // 1
+```
+
+### timespace.minute
+
+The minute from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").minute // 20
+```
+
+### timespace.second
+
+The second from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").second // 30
+```
+
+### timespace.day
+
+The day of the month, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").day // 16
+```
+
+### timespace.month
+
+The month of the year as an integer, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month // 11
+```
+
+### timespace.month_name
+
+The name of the month of the year, in English. Example: `January`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month_name // November
+```
+
+### timespace.weekday
+
+The weekday as an integer, where 0 is Sunday and 6 is Saturday.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday // 6
+```
+
+### timespace.weekday_name
+
+The name of the day of the week, in English. Example: `Sunday`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday_name // Saturday
+```
+
+### timespace.year
+
+The year as an integer.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").year // 2019
+```
+
+### timespace.unix
+
+The number of seconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix // 1573867230
+```
+
+### timespace.unix_nano
+
+The number of nanoseconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix_nano // 1573867230000000000
+```
+
+### timespace.zone
+
+The timezone offset, in seconds. This can be a negative number to indicate
+time west of UTC.
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone // -28800
+```
+
+### timespace.zone_string
+
+The timezone offset, in the format `+HH:MM` (example: `-08:00` for PST).
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone_string // -08:00
+```
+
+### timespace.before(timeish_or_timespace)
+
+Check if the time in the timespace is before the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").before("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.after(timeish_or_timespace)
+
+Check if the time in the timespace is after the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").after("2019-11-15T01:20:30Z") // true
+```
+
+### timespace.equal(timeish_or_timespace)
+
+Check if two times are equivalent
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").equal("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.add(duration)
+
+Add a duration in nanoseconds to the time in the timespace and return a new timespace. The
+duration can be negative. The duration should be specified as an integer
+multiplied with the correct unit.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+### timespace.sub(timeish_or_timespace)
+
+Subtract a time from the time in the timespace and return a duration in nanoseconds.
+
+```sentinel
+// Gives a duration of 3 hours.
+duration = time.load(
+ "2019-11-16T01:20:30Z",
+).sub("2019-11-16T04:20:30Z")
+
+// Returns 2019-11-16T01:20:30Z
+time.load(
+ "2019-11-16T04:20:30Z",
+).add(duration)
+```
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/imports/types.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/imports/types.mdx
new file mode 100644
index 0000000000..a25e9ef4c1
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/imports/types.mdx
@@ -0,0 +1,36 @@
+---
+page_title: 'Import: types'
+sidebar_title: 'types'
+sidebar_current: docs-imports-types
+description: The types import enables a Sentinel policy to parse an object's type.
+layout: docs
+---
+
+# Import: types
+
+The types import enables a Sentinel policy to parse an object's type.
+
+### types.type_of(obj)
+
+Returns the object's type as a string
+
+```sentinel
+import "types"
+
+// Typically the inputs would come from an external source.
+is_boolean = rule { types.type_of(true) is "bool" }
+is_string = rule { types.type_of("Hello!") is "string" }
+is_integer = rule { types.type_of(42) is "int" }
+is_float = rule { types.type_of(42.123) is "float" }
+is_null = rule { types.type_of(null) is "null" }
+is_undefined = rule { types.type_of(undefined) is "undefined" }
+
+main = rule {
+ is_boolean and
+ is_string and
+ is_integer and
+ is_float and
+ is_null and
+ is_undefined
+}
+```
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/imports/units.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/imports/units.mdx
new file mode 100644
index 0000000000..0838cf0ae8
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/imports/units.mdx
@@ -0,0 +1,40 @@
+---
+page_title: 'Import: units'
+sidebar_title: 'units'
+sidebar_current: docs-imports-units
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: units
+
+The units import provides access to quick calculations for various byte
+units.
+
+Note that this import uses base-2 terminology:
+
+- One kilobyte is 1024 bytes
+- One megabyte is 1024^2 = 1048576 bytes
+- One gigabyte is 1024^3 = 1073741824 bytes
+- One terabyte is 1024^4 = 1099511627776 bytes
+- One petabyte is 1024^5 = 1125899906842624 bytes
+
+### units.kilobyte
+
+The base-2 representation of a kilobyte (1024 bytes).
+
+### units.megabyte
+
+The base-2 representation of a megabyte (1048576 bytes).
+
+### units.gigabyte
+
+The base-2 representation of a gigabyte (1073741824 bytes).
+
+### units.terabyte
+
+The base-2 representation of a terabyte (1099511627776 bytes).
+
+### units.petabyte
+
+The base-2 representation of a petabyte (1125899906842624 bytes).
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/imports/version.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/imports/version.mdx
new file mode 100644
index 0000000000..3ab4236d81
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/imports/version.mdx
@@ -0,0 +1,137 @@
+---
+page_title: 'Import: version'
+sidebar_title: 'version'
+sidebar_current: docs-imports-version
+description: |-
+ The version import provides functions for parsing versions and version constraints,
+ and verifying versions against a set of constraints.
+layout: docs
+---
+
+# Import: version
+
+The version import provides functions for parsing versions and version constraints,
+and verifying versions against a set of constraints.
+
+Versions are created through the `new` function, which accepts a string type in dotted-tri format (i.e. 1.0.0-alpha.1+001) that can represent a version.
+
+### version.new(v)
+Constructs a version from string value.
+
+```sentinel
+version.new("1.0.0-alpha.1+001")
+```
+
+### version.major
+An integer representation of the Major number for a given version .
+
+```sentinel
+version.new("1.0.0-alpha.1+001").major // 1
+```
+
+### version.minor
+An integer representation of the Minor number for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").minor // 0
+```
+
+### version.patch
+An integer representation of the Patch number for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").patch // 0
+```
+
+### version.prerelease
+A string representation of the Prerelease for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").prerelease // "alpha.1"
+```
+
+### version.metadata
+A string representation of the Metadata for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").metadata // "001"
+```
+
+### version.version
+A string representation of the version including pre-release and metadata information.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").version // "1.0.0-alpha.1+001"
+```
+
+### version.greater_than(v)
+Tests that the version is greater than a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").greater_than("0.12.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").gt("1.0.1-alpha.1+001") // False
+```
+
+### version.greater_than_or_equals(v)
+Tests that the version is greater than or equal to a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").greater_than_or_equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").gte("1.0.1-alpha.1+001") // False
+```
+
+### version.less_than(v)
+Tests that the version is less than a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").less_than("1.0.1-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").lt("0.12.0-alpha.1+001") // False
+```
+
+### version.less_than_or_equals(v)
+Tests that the version is less than or equal to a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").less_than_or_equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").lte("0.12.0-alpha.1+001") // False
+```
+
+### version.less_than_or_equals(v)
+Tests that the version equals a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").eq("0.12.0-alpha.1+001") // False
+```
+
+### version.compare(v)
+Compares one version with a given version. Compare returns -1, 0, or 1 if the version is less, equal, or greater than the other version, respectively.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").compare("0.12.0-alpha.1+001") // -1
+version.new("1.0.0-alpha.1+001").compare("1.0.0-alpha.1+001") // 0
+version.new("0.12.0-alpha.1+001").cmp("1.0.0-alpha.1+001") // 1
+```
+
+### version.satisfies(v, x)
+Tests that a version adheres to one or more version constraints. Satisfies supports the following restriction operators:
+* =
+* !=
+* \>
+* <
+* \>=
+* <=
+* ~>
+
+Satisfies supports the following usage scenarios:
+
+* A constraint with a pre-release can only match a pre-release version that contains the same major, minor and patch identifiers.
+* A constraint without a pre-release can only match a version without a pre-release.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").satisfies("> 1.0.0-alpha.1+001") // false
+version.new("1.0.0-alpha.1+001").satisfies(">= 1.0.0-alpha.1+001") // true
+version.new("1.0.0-alpha.1+001").satisfies(">= 1.0.0-alpha.1+001, < 1.0.0-beta+001") // true
+version.new("1.0.0").sat("= 1.0.0") // true
+version.new("0.12.7").sat("~> 0.12.0") // true
+```
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/index.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/index.mdx
new file mode 100644
index 0000000000..4497b40b83
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/index.mdx
@@ -0,0 +1,31 @@
+---
+layout: 'docs'
+page_title: 'Documentation'
+sidebar_current: 'docs-home'
+description: |-
+ Sentinel is a language and framework for policy built to be embedded in existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+[introduction](/sentinel/intro) to understand what Sentinel is, how it
+compares to other software, and more.
+
+
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/language/arithmetic.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/language/arithmetic.mdx
new file mode 100644
index 0000000000..3a341db72e
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/language/arithmetic.mdx
@@ -0,0 +1,68 @@
+---
+page_title: Sentinel Language - Arithmetic
+sidebar_title: Arithmetic
+sidebar_current: docs-language-arith
+description: >-
+ Sentinel supports arithmetic operators for integers and floats. Sentinel
+ supports sum, difference, product, quotient, and remainder.
+layout: docs
+---
+
+# Language: Arithmetic
+
+Sentinel supports arithmetic operators for integers and floats. Sentinel
+supports sum, difference, product, quotient, and remainder.
+
+```plaintext
++ sum
+* difference
+* product
+/ quotient
+% remainder
+```
+
+These operators work in a typical infix style:
+
+```sentinel
+4 + 8 // 12
+8 * 2 // 16
+8 / 4 // 2
+8 / 5 // 1
+8 % 5 // 3
+```
+
+## Order of Operations
+
+Arithmetic follows a standard mathematical order of operations.
+Grouping with parentheses can be used to affect ordering.
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+(4 + 5) * 2 // 18
+```
+
+A full table of operator precendence can be found on the
+[boolean expressions page](/sentinel/language/boolexpr). This
+shows how arithmetic operators relate to other operators.
+
+## Integer Division
+
+Integer division with a remainder rounds down to the nearest integer.
+Example: `8 / 3 is 2`.
+
+If the divisor is zero, an error occurs.
+
+## Mixed Numeric Operations
+
+Mixed numeric operations between integer and floating-point values are
+permitted. The result is a floating-point operation with the integer converted
+to a floating-point value for purposes of calculation.
+
+```sentinel
+import "types"
+
+a = 1.1 + 1 // 2.1
+types.type_of(a) // "float"
+```
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/language/boolexpr.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/language/boolexpr.mdx
new file mode 100644
index 0000000000..86432c4153
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/language/boolexpr.mdx
@@ -0,0 +1,193 @@
+---
+page_title: Sentinel Language - Boolean Expressions
+sidebar_title: Boolean Expressions
+sidebar_current: docs-language-boolexpr
+description: >-
+ Boolean expressions are expressions that evaluate to a boolean value from the
+ result of a combination of one or more comparisons and logical operators.
+layout: docs
+---
+
+# Language: Boolean Expressions
+
+Boolean expressions are expressions that evaluate to a boolean value
+from the result of a combination of one or more comparisons and logical
+operators. Boolean expressions form the basis of policy since policy can be broken
+down to a set of logical decisions that turn into true or false.
+
+A single boolean expression is the body of a [rule](/sentinel/language/rules).
+Additionally, boolean expressions are used with [conditionals](/sentinel/language/conditionals),
+and more.
+
+## Order of Operations
+
+The precedence of operators is shown below. Operators with higher
+precedence values (larger numbers) are evaluated first. Operators with
+the same precedence value are evaluated left-to-right.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Examples of this precedence are shown below:
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+```
+
+## Logical Operators
+
+Logical operators are used to change or combine logical expressions.
+There are three binary operators `and`, `or`, and `xor`. There is
+a single unary operator `not` (which can also be specified as `!`).
+
+The binary operators require two boolean operands and the unary
+operator requires a single boolean operand. In both case, the operands
+are values or expressions that are boolean types.
+
+Below is a basic explanation of the logical operators directly from
+the specification:
+
+```sentinel
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+Using parentheses to group boolean expressions, you can combine
+boolean expressions to become more complex or affect ordering:
+
+```sentinel
+(p and q) or r
+p and (q or r)
+```
+
+## Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+For any comparison, the two operands must be equivalent types. The one
+exception are integers and floats. When an integer is compared with
+a float, the integer is promoted to a floating point value.
+
+The available comparison operators are:
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+The behavior of `is` with `==` and `is not` with `!=` is identical.
+
+Example comparisons:
+
+```sentinel
+name is "Mitchell"
+idnumber > 42
+idnumber <= 12
+```
+
+Using the logical operators, these can be combined:
+
+```sentinel
+name is "Mitchell" and idnumber > 42
+```
+
+The language specification provides more detail on exactly
+[how comparison behaves](/sentinel/language/spec#comparison-operators).
+
+## Set Operators
+
+The set operators `contains` and `in` test for inclusion in a collection
+(a list or map).
+
+Set operators may 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.
+
+`contains` tests if the left-hand collection contains the right-hand value.
+`in` tests if the right-hand collection contains the left-hand value. For
+maps, "contains" looks at the keys, not the values.
+
+Examples:
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+```
+
+## Matches Operator
+
+The `matches` operator tests if a string matches a regular expression.
+The `matches` operator can be negated by prefixing the operator with
+`not`, such as `not matches`.
+
+The left-hand value is the string to test. The right-hand value is
+a string value representing a regular expression. The syntax of the regular
+expression is the same general syntax used by Python, Ruby, and other languages.
+The precise syntax accepted is the syntax accepted by RE2.
+
+Regular expressions are not anchored by default; any anchoring must be
+explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+## Any, All Expressions
+
+`any` and `all` expressions allow testing that any or all elements of a
+collection match some boolean expression. The result of an `any` and `all`
+expression is a boolean.
+
+`any` returns the boolean value `true` if any value in the collection expression
+results in the body expression evaluating to `true`. If the body expression
+evalutes to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression
+result in the body expression evaluating to `true`. If any value in the
+collection expression result in the body expression evaluating to `false`,
+the `all` expression returns `false`.
+
+For empty collections, `any` returns `false` and `all` returns `true`.
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" }
+any group.tasks as t { t.driver is "vmware" }
+```
+
+Since `any` and `all` expressions are themselves boolean expressions, they
+can be combined with other operators:
+
+```sentinel
+any ["a", "b"] as char { char is "a" } or
+other_value is "another"
+```
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/language/collection-operations.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/language/collection-operations.mdx
new file mode 100644
index 0000000000..571cc04b08
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/language/collection-operations.mdx
@@ -0,0 +1,44 @@
+---
+page_title: Sentinel Language - Collection Operations
+sidebar_title: Collection Operations
+sidebar_current: docs-language-collection-operations
+description: |-
+ Operations for interacting with collections (list, map).
+layout: docs
+---
+
+# Language: Collection Operations
+
+Collection operations are expressions that are performed on a list or map to
+return a variation of the initial data.
+
+At the moment, `filter` is the only collection operation available.
+
+## Filter Expression
+
+`filter` is a [quantifier
+expression](/sentinel/language/spec#quantifier-expressions-any-all-filter) that
+returns a subset of the provided collection. Only elements whose filter body
+returns true will be returned. If any of the elements filter body returns
+undefined, the final result will be undefined.
+
+Filter uses the same syntax as the `any` and `all` [boolean
+expressions](/sentinel/language/boolexpr#any-all-expressions):
+
+```sentinel
+filter list as value { condition } // Single-iterator, list
+filter list as idx, value { condition } // Double-iterator, list
+
+filter map as key { condition } // Single-iterator, map
+filter map as key, value { condition } // Double-iterator, map
+```
+
+Examples:
+
+```sentinel
+l = [1, 1, 2, 3, 5, 8]
+evens = filter l as v { v % 2 is 0 } // [2, 8]
+
+m = { "a": "foo", "b": "bar" }
+matched_foo = filter m as _, v { v is "foo" } // { "a": "foo" }
+```
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/language/conditionals.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/language/conditionals.mdx
new file mode 100644
index 0000000000..af85dafcc6
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/language/conditionals.mdx
@@ -0,0 +1,162 @@
+---
+page_title: Sentinel Language - Conditionals
+sidebar_title: Conditionals
+sidebar_current: docs-language-conditional
+description: |-
+ Conditional statements allow your policy to behave differently depending on a condition.
+layout: docs
+---
+
+# Language: Conditionals
+
+Conditional statements allow your policy to behave differently depending
+on a condition.
+
+Conditional statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## If Statements
+
+`if` statements only execute their bodies if a condition is met.
+The syntax of an `if` statement is:
+
+```sentinel
+if condition {
+ // ... this is executed if condition is true
+}
+```
+
+The `condition` must result in a boolean, such as by calling a function
+or evaluating a [boolean expression](/sentinel/language/boolexpr). If
+the `condition` is `true`, the body (within the `{}`) is executed. Otherwise,
+the body is skipped.
+
+Examples:
+
+```sentinel
+// This would execute the body
+value = 12
+if value is 18 {
+ print("condition met")
+}
+
+// Direct boolean values can be used
+value = true
+if value {
+ print("condition met")
+}
+
+// This would not execute the body since the boolean expression will
+// result in undefined.
+value = {}
+if value["key"] > 12 {
+ print("condition met")
+}
+```
+
+### Else, Else If
+
+An `else` clause can be given to an `if` statement to execute a body
+in the case the condition is _not met_. By putting another `if` statement
+directly after the `else`, multiple conditions can be tested for.
+The syntax is:
+
+```sentinel
+if condition {
+ // ...
+} else {
+ // ...
+}
+
+if condition {
+ // ...
+} else if other_condition {
+ // ...
+} else {
+ // ...
+}
+```
+
+### Scoping
+
+The body of an `if` statement does not create a new
+[scope](/sentinel/language/scope). Any variables assigned within the body
+of an if statement will modify the scope that the `if` statement itself
+is in.
+
+Example:
+
+```sentinel
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+```sentinel
+a = 18
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+## Case Statements
+
+`case` statements are a selection control mechanism that execute a clause based
+on matching expressions. It is worth noting that the expression for `case` is
+optional. When no expression is provided, it defaults the expression to `true`.
+Additionally, the order of clauses is important, as they are evaluated from top
+to bottom, executing the first match.
+The syntax of a case statement is:
+
+```sentinel
+case expression {
+ when clause_expression:
+ // executed when clause_expression and expression are equal
+ else:
+ // executed if no clause matches expression
+}
+```
+
+### When Clause
+
+Any clause that has an expression for comparison must use the `when` keyword.
+It accepts a list of expressions, seperated by a `,`.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+}
+```
+
+```sentinel
+case {
+ when x > 40:
+ return true
+}
+```
+
+### Else Clause
+
+The `else` keyword allows for capturing any expressions that have no matching
+`when` clause.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+ else:
+ return false
+}
+```
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/language/functions.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/language/functions.mdx
new file mode 100644
index 0000000000..c55dfe4d81
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/language/functions.mdx
@@ -0,0 +1,174 @@
+---
+page_title: Sentinel Language - Functions
+sidebar_title: Functions
+sidebar_current: docs-language-functions
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Functions
+
+Functions allow you to create reusable code to perform computations.
+
+Below is a trivial example function that doubles a number and shows
+the main rule using the function:
+
+```sentinel
+double = func(x) {
+ return x * 2
+}
+
+main = rule { double(12) is 24 }
+```
+
+Functions may contain any expressions,
+[conditionals](/sentinel/language/conditionals),
+and [loops](/sentinel/language/loops). They must return a value
+at the end of their execution. If no reasonable return value exists,
+the function should return [undefined](/sentinel/language/undefined).
+
+## Creating a Function
+
+A function is created by assigning a variable to a `func`.
+
+A function can have zero or more parameters. These parameters are specified
+within parentheses `()` separated by commas. If a function has no parameters,
+the parentheses must still be present.
+
+A function must terminate with a `return` statement to return a value back to
+the caller. Only a single value can be returned. The type of a return can
+vary between return statements.
+
+Example:
+
+```sentinel
+add1 = func(x) { return x + 1 }
+```
+
+This example creates a function that adds 1 to the parameter `x`. It is
+assigned to the variable `add1`.
+
+## Calling a Function
+
+Functions are called by accessing their name followed by parentheses with
+a list of comma-separated values for the parameters. If the function takes no
+parameters, an empty set of parentheses must still be used to denote a
+function call.
+
+To call the function in the example above:
+
+```sentinel
+x = 1
+y = add1(x)
+```
+
+## Scoping
+
+The body of a `func` creates a new [scope](/sentinel/language/scope).
+
+The parent scope is the scope in which the function is created. This allows
+for the creation of functions within functions that can access their outer
+scopes.
+
+Example:
+
+```sentinel
+f = func() {
+ a = 42
+ print(a) // 42
+ return undefined
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+f = func() {
+ a = 42
+ return undefined
+}
+
+print(a) // 18
+```
+
+And below is an example of creating a function in a function which uses
+outer values:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double()
+}
+
+print(f()) // 84
+```
+
+A more complex example below shows how scoping works when passing
+functions around as arguments or results:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double
+}
+
+double = f()
+print(double()) // 84
+```
+
+## Pass By Value
+
+The parameters to a function are passed by value, but not deep copied.
+This means that elements of collections can still be modified but the
+root value cannot be changed.
+
+Example:
+
+```sentinel
+f = func(x) {
+ x = "value"
+ return x
+}
+
+x = "outside"
+f(x)
+print(x) // "outside"
+```
+
+```sentinel
+f = func(x) {
+ append(x, "value")
+ return x
+}
+
+x = []
+f(x)
+print(x) // ["value"]
+```
+
+## Recursion
+
+A function is allowed to call other functions, including itself.
+This can be used to implement recursion. For example, the fibonacci sequence
+is implemented below:
+
+```sentinel
+fib = func(x) {
+ if x <= 0 {
+ return undefined
+ }
+
+ if x == 1 {
+ return 1
+ } else {
+ return x + fib(x - 1)
+ }
+}
+```
+
+Note that this example also shows using
+[undefined](/sentinel/language/undefined)
+as a return value in cases where a function has undefined behavior.
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/language/imports.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/language/imports.mdx
new file mode 100644
index 0000000000..945bb95930
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/language/imports.mdx
@@ -0,0 +1,117 @@
+---
+page_title: Sentinel Language - Imports
+sidebar_title: Imports
+sidebar_current: docs-language-imports
+description: >-
+ Imports enable a Sentinel policy to access reusable libraries and external
+ data and functions.
+layout: docs
+---
+
+# Language: Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external
+data and functions. The exact list of available imports is dependent on the
+host system. Host systems may also make the available imports configurable
+via plugins.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that
+the import `calendar` allows access to engineer calendars. This import
+only exists for the purpose of this example and at the time of writing is
+not a real available import.
+
+```sentinel
+import "calendar"
+
+// Get the calendar for Bob for today
+bob_calendar = calendar.for("bob").today
+
+// Allow this policy to pass if Bob is not on vacation.
+main = rule { not bob_calendar.has_event("vacation") }
+```
+
+This example shows an import being used to deny a behavior if Bob is on
+vacation. Hopefully real policies do not depend on a single person being
+in the office, but the example shows the power of imports.
+
+In addition to consuming imports, you can also
+[extend Sentinel by writing custom imports](/sentinel/extending)
+This allows you to add any arbitrary behavior to your Sentinel-protected
+systems.
+
+## Importing
+
+Whether accessing a built-in library or an external plugin, the syntax
+for an import is the same:
+
+```sentinel
+import "name"
+```
+
+This adds the import with the name `name`. It can be referenced using
+the identifier `name`. For example, if the import above exposed first
+and last name data, you could access it via `name.first` or `name.last`.
+
+You can shadow imports by assigning a variable. In the example below,
+the import `name` becomes unavailable within the function because it is
+shadowed by a variable of the same name.
+
+Shadowing an import is not the same as overwriting a variable. Imports
+are not part of the [scope](/sentinel/language/scope), meaning that
+the shadow only exists for the scope it is created within. For everything
+else, the import works even after it is shadowed. The example below shows that
+as well.
+
+```sentinel
+import "name"
+
+f = func() {
+ name = "jane"
+ return name
+}
+
+print(f()) // "jane"
+print(name.first) // "bob"
+```
+
+## Aliases
+
+An import can be aliased to another name using the syntax below:
+
+```sentinel
+import "name" as other
+```
+
+This would make the import "name" available as `other`.
+
+An import may only be imported once, regardless of aliases. The following
+would be **invalid** and would result in an error:
+
+```sentinel
+import "name" as one
+import "name" as two
+```
+
+## Accessing Import Data
+
+Imports are accessed with dot-separated identifiers, such as `name.first` or
+`name.stage.first`. These are called _selector expressions_. An import may
+return nested data that further can be accessed via selector expressions.
+
+In the first example on this page with the "calendar" import, we see this:
+
+```sentinel
+import "calendar"
+
+a = calendar.for("bob") // selector expression calendar.for
+b = a.today // selector expression on the result
+c = b.vacation // accessing further nested data
+```
+
+The exact structure of an import is dependent on the import. Please reference
+the documentation for an import to learn more.
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/language/index.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/language/index.mdx
new file mode 100644
index 0000000000..fe23ee1144
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/language/index.mdx
@@ -0,0 +1,100 @@
+---
+page_title: Sentinel Language
+sidebar_title: Language
+sidebar_current: docs-language-basics
+description: |-
+ Sentinel policies are written using the Sentinel language. This language
+ is easy to learn and easy to write. You can learn the Sentinel language
+ and be productive within an hour. Learning Sentinel doesn't require any
+ formal programming experience.
+layout: docs
+---
+
+# Sentinel Language
+
+Sentinel policies are written using the Sentinel language. This language
+is easy to learn and easy to write. You can learn the Sentinel language
+and be productive within an hour. Learning Sentinel doesn't require any
+formal programming experience.
+
+This language guide will contain many details that are not necessary to
+be productive with Sentinel or are targeted to those who are looking for
+exact information about the language (such as what types of line endings are
+allowed). You can safely ignore these sentences.
+
+You may also view the
+[official language specification](/sentinel/language/spec)
+This is a specific and detailed document on the syntax and behavior of
+the language primarily intended for implementation creators and to disambiguate
+the language.
+
+## Simplest Example
+
+The example below is about the simplest practical example of Sentinel.
+It is reasonable to imagine this as a realistic policy. This shows that
+in most cases, Sentinel will be extremely simple:
+
+```sentinel
+main = rule { request.method is "GET" and request.headers contains "X-Key" }
+```
+
+## Files
+
+Sentinel policies are single files that end in the `.sentinel` file extension.
+There is currently no built-in mechanism to Sentinel for merging multiple
+files. This is purposefully done to make Sentinel policies easy to submit
+to systems that support Sentinel policies.
+
+Sentinel policy files must be UTF-8 encoded and can end in both
+Unix (LF) or Windows (CRLF) line breaks. When running the
+[auto-formatter](#),
+line endings will always use Unix line breaks.
+
+## Ordering
+
+Sentinel policies are executed top-down. For example:
+
+```sentinel
+a = 1 // a = 1 here
+b = a + 1 // b = 2 here
+a = 3 // a = 3, b = 2
+```
+
+In this example, the value of `a` and `b` is shown at each line. Since Sentinel
+executes values top-down, the final value of `a` is 3 and `b` is 2. `b` _does
+not_ become 4.
+
+## Main
+
+Sentinel expects there to be a `main` [rule](/sentinel/language/rules).
+The value of this rule is the result of the entire policy.
+
+If the result of `main` is true, the policy passes. If the value is anything
+else (false or a non-boolean value), the policy fails. The exact meaning
+of what happens a policy passes or fails is dependent on the host system.
+
+## More Complex Example
+
+The simple example above is a full working example. In our experience with
+Sentinel, many policies can be representing using this simple form. However,
+to show more features of the language, a more complex example is shown below.
+This example is also a realistic example of what Sentinel may be used for.
+
+```sentinel
+import "units"
+
+memory = func(job) {
+ result = 0
+ for job.groups as g {
+ for g.tasks as t {
+ result += t.resources.memory else 0
+ }
+ }
+
+ return result
+}
+
+main = rule {
+ memory(job) < 1 * units.gigabyte
+}
+```
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/language/lists.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/language/lists.mdx
new file mode 100644
index 0000000000..d1e02cdb15
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/language/lists.mdx
@@ -0,0 +1,136 @@
+---
+page_title: Sentinel Language - Lists
+sidebar_title: Lists
+sidebar_current: docs-language-lists
+description: Lists are a collection of zero or more values.
+layout: docs
+---
+
+# Language: Lists
+
+Lists are a collection of zero or more values.
+
+Lists can be created using by wrapping values in `[]` and separating them
+by commas. An optional trailing comma is allowed. List elements can be
+differing types. Examples:
+
+```sentinel
+[] // An empty list
+["foo"] // Single element list
+["foo", 1, 2, true] // Multi element list with different types
+["foo", [1, 2]] // List containing another list
+```
+
+A list can be [sliced](/sentinel/language/slices) to create sublists.
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for value inclusion in a list.
+
+## Accessing Elements
+
+List elements can be accessed with the syntax `name[index]` where
+`index` is zero-indexed.
+
+A negative index accesses the list in reverse. It is the same as
+reversing a list and then using a positive index. Similar to a positive
+index, it is bounded by the length of the list as a negative value.
+
+Accessing beyond the length of the list results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+a = ["foo", 1, true, [1, 2]]
+
+a[0] // "foo"
+a[2] // true
+a[4] // undefined
+a[-2] // true
+a[-4] // "foo"
+a[-5] // undefined
+a[3][1] // 2
+```
+
+## List Append
+
+Values can be appended to a list using the built-in
+[append](/sentinel/functions/append) function.
+
+This modifies the list in-place and returns
+[undefined](/sentinel/language/undefined). For more information on
+why `append` behaves this way, please read the full documentation for the
+[append](/sentinel/functions/append) function.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append([1,2], "foo") // [1, 2, "foo"]
+append([1,2], [3]) // [1, 2, [3]]
+append(1, 3) // error()
+```
+
+## List Concatenation
+
+Two lists can be concatenated using the `+` operator or the shorthand
+`+=` assignment operator. For the `+` operator, a new list is returned.
+For `+=`, the left-hand list is modified in place.
+
+Examples:
+
+```sentinel
+[1] + [2] // [1, 2]
+[1] + [[1]] // [1, [1]]
+[1] + 1 // error
+
+a = [1]
+a += [2] // a = [1, 2]
+a += 3 // error
+```
+
+## List Length
+
+The length of a list can be retrieved using the
+[length](/sentinel/functions/length) function.
+
+Examples:
+
+```sentinel
+length([]) // 0
+length(["foo"]) // 1
+```
+
+## Removing Items From a List
+
+You can use a combination of [list concatenation](#list-concatenation) and
+[slicing](/sentinel/language/slices) to remove elements from a list.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+a = a[:2] + a[3:] // [1, 2, 4, 5]
+```
+
+The shorthand shown here is effectively the same as `a[0:2] + a[3:length(a)]`,
+which creates a new list out of the concatenation two sub-lists composed of the
+first two elements, and the rest of the list starting at index 3. This
+effectively removes the 3rd element from the list (index 2).
+
+## List Comparison
+
+Lists may be [compared](/sentinel/language/boolexpr#comparison-operators)
+for equality. Lists are equal if they are of equal length and their
+corresponding elements are comparable and equal. Lists with the same elements
+but in a different order are not equal.
+
+```sentinel
+[1, 2] is [1, 2] // true
+[1, 2] is [2, 1] // false
+["a"] is ["a", "b"] // false
+["a", ["b", "c"]] is ["a", ["b", "c"]] // true
+```
+
+List comparison speed is O(N), meaning that the speed of the comparison is
+_linearly_ proportional to the number of elements in the list. The more
+elements, the more iterations that are necessary to verify equality.
+
+-> The N-value quoted above should account for the sum of the elements of _all_
+lists in the subjects of comparison, as list comparison will recurse into these
+lists to check for equality.
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/language/logging-errors.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/language/logging-errors.mdx
new file mode 100644
index 0000000000..32427697d9
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/language/logging-errors.mdx
@@ -0,0 +1,53 @@
+---
+page_title: Sentinel Language - Logging and Errors
+sidebar_title: Logging and Errors
+sidebar_current: docs-language-log
+description: The built-in functions `print` and `error` can be used for logging and errors.
+layout: docs
+---
+
+# Language: Logging and Errors
+
+The built-in functions `print` and `error` can be used for logging
+and errors. Logging is helpful to understand how a policy is executing
+in development. And errors are useful to halting execution immediately in
+certain scenarios.
+
+## Print
+
+The `print` function is used to output debug information. The exact location
+where this print statements go is dependent on the host application and
+the Sentinel runtime itself.
+
+The `print` function takes one or more Sentinel values as arguments.
+Each value is formatted for human-friendly output and space-separated.
+Example:
+
+```sentinel
+value = 42
+print("the value is", value) // the value is 42
+
+map = { "foo": false }
+print(map) // { "foo": false }
+
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // false
+```
+
+## Errors
+
+Certain actions in Sentinel, such as accessing an undefined variable,
+attempting to add two incompatible types, etc. result in _runtime errors_.
+These errors halt the execution of a policy immediately. The pass/fail result
+of a policy is considered fail.
+
+Runtime errors can be manually triggered using the `error` function.
+The `error` function behaves exactly like the `print` function except that
+execution is halted immediately.
+
+This should only be used in scenarios where the policy _must fail_ due to
+some unexpected or invalid condition. The line between `error` and
+[undefined](/sentinel/language/undefined) can sometimes be unclear. Undefined
+is recommended when a policy can conceivably recover or safely ignore the
+undefined behavior. An error should be used when the policy should fail and
+notify someone regardless.
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/language/loops.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/language/loops.mdx
new file mode 100644
index 0000000000..2a04c6957e
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/language/loops.mdx
@@ -0,0 +1,93 @@
+---
+page_title: Sentinel Language - Loops
+sidebar_title: Loops
+sidebar_current: docs-language-loops
+description: >-
+ Loop statements allow you to execute a body of code for each element in a
+ collection or for some fixed number of times.
+layout: docs
+---
+
+# Language: Loops
+
+Loop statements allow you to execute a body of code for each element in
+a collection or for some fixed number of times.
+
+Loop statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## For Statements
+
+`for` statements allow repeated execution of a block for each
+element in a collection.
+
+Example:
+
+```sentinel
+// A basic sum
+count = 0
+for [1, 2, 3] as num {
+ count += num
+}
+```
+
+The syntax is `for COLLECTION as value`. This will iterate over the
+collection, assigning each element to `value`. In the example above,
+each element is assigned to `num`. The body is executed for each element.
+In the example above, the body adds `num` to the `count` variable. This
+creates a basic sum of all values in the collection.
+
+For a map, the assigned element is the key in the map. In the example
+below, `name` would be assigned map keys.
+
+```sentinel
+list = []
+for { "a": 1, "b": 2 } as name {
+ append(list, name)
+}
+
+print(list) // ["a" "b"]
+```
+
+An alternate syntax is `for COLLECTION as key, value`. This will assign
+both the key and value to a variable. For a list, the key is the element
+index. For a map, it is the key and value is assigned the element value.
+Example:
+
+```sentinel
+count = 0
+for { "a": 1, "b": 2 } as name, num {
+ count += num
+}
+
+print(count) // 3
+```
+
+## Scoping
+
+The body of a `for` statement creates a new
+[scope](/sentinel/language/scope). If a variable is assigned within
+the body of a for statement that isn't assigned in a parent scope,
+that variable will only exist for the duration of the body execution.
+
+Example:
+
+```sentinel
+for list as value {
+ a = 42
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+for list as value {
+ a = 42
+}
+
+print(a) // 18
+```
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/language/maps.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/language/maps.mdx
new file mode 100644
index 0000000000..8b6c71abeb
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/language/maps.mdx
@@ -0,0 +1,100 @@
+---
+page_title: Sentinel Language - Maps
+sidebar_title: Maps
+sidebar_current: docs-language-maps
+description: >-
+ Maps are a collection of zero or more key/value pairs. These are useful for
+ storing values that are looked up by a unique key.
+layout: docs
+---
+
+# Language: Maps
+
+Maps are a collection of zero or more key/value pairs. These are useful
+for storing values that are looked up by a unique key.
+
+Maps can be created using `{}` and specifying key/value pairs. Keys and
+values can be differing types. An optional trailing comma is allowed.
+Examples:
+
+```sentinel
+// Empty map
+{}
+
+// Map with a single value on one line
+{ "key": "value" }
+
+// Map with multiple values with differing types on multiple lines
+{
+ "key": "value",
+ 42: true,
+}
+```
+
+Maps are **unordered**. When
+[looping](/sentinel/language/loops)
+over a map, the key/value pairs can be returned in any order.
+
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for key inclusion in a map.
+
+## Accessing Elements
+
+Map elements can be accessed with the syntax `name[key]`. This looks up
+a value by a key.
+
+Accessing a key that doesn't exist results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+map = { "key": "value", 42: true, }
+
+map["key"] // "value"
+map[42] // true
+map[0] // undefined
+```
+
+## Modifying or Adding Elements
+
+Elements can be added or modified in a map by assigning to `name[key]`.
+If the key doesn't exist, the value is added. If the key already exists,
+the value is overridden.
+
+```sentinel
+map = { "key": "value" }
+
+map[42] = true // Add a new key/value
+map["key"] = 12 // Modify the value of "key"
+```
+
+## Deleting Elements
+
+An element can be deleted from a map using the
+[delete](/sentinel/functions/delete) function.
+
+Examples:
+
+```sentinel
+map = { "key": "value" }
+delete(map, "key") // map is now empty
+delete(map, "other") // no effect for non-existent key
+```
+
+## Keys and Values
+
+The keys and values of a map can be retrieved as
+[lists](/sentinel/language/lists) using the
+[keys](/sentinel/functions/keys) and
+[values](/sentinel/functions/values) functions.
+
+Because maps are unordered, the keys and values are returned in an
+unspecified order. It should not be assumed that keys and values will be
+returned in a consistent order.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/language/parameters.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/language/parameters.mdx
new file mode 100644
index 0000000000..db803f74e5
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/language/parameters.mdx
@@ -0,0 +1,136 @@
+---
+page_title: Sentinel Language - Parameters
+sidebar_title: Parameters
+sidebar_current: docs-language-parameters
+description: >-
+ Parameteres allow a Sentinel policy to describe variables that are expected
+ to be supplied by the calling application, in addition to any default value.
+layout: docs
+---
+
+# Language: Parameters
+
+Sentinel allows a policy author to supply parameters to help facilitate policy
+reuse and ensure sensitive values do not need to be hard-coded in a policy.
+
+Parameters are supplied by using the `param` keyword, followed by an identifier.
+A default value can also be supplied by using the `default` keyword.
+
+```sentinel
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+```
+
+Once declared, parameters can be used like any other variable, including being
+re-assigned.
+
+```sentinel
+param foo default 1 // 1 (default)
+
+foo = foo + 1 // 2
+```
+
+## Variable Descriptions
+
+You can supply a description to a parameter by adding a comment at the top of
+it. This value can be communicated to a specific implementation of Sentinel to
+provide information about what the parameter is for during configuration.
+
+```sentinel
+// An example parameter. Must be supplied or the policy will fail.
+param foo
+```
+
+## Supplying Parameter Values Using the Sentinel CLI
+
+In a production implementation, supplying parameters to a policy is an
+implementation-specific detail - see the documentation for your particular
+implementation for details.
+
+Using the [Sentinel CLI](/sentinel/commands), you can supply parameters one of
+four ways.
+
+### Supplying Parameter Values Using the Configuration File
+
+You can supply parameters using the
+[`param`](/sentinel/configuration#parameters) section of the
+[configuration file](/sentinel/configuration).
+
+```json
+{
+ "param": {
+ "foo": "bar"
+ }
+}
+```
+
+This method works for both `sentinel apply` and `sentinel test`.
+
+### Supplying Parameter Values Using the Environment
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can supply a value using environment variables - prefix the parameter with
+`SENTINEL_PARAM_`, using the name of the parameter to supply.
+
+```plaintext
+SENTINEL_PARAM_foo=bar sentinel apply policy.sentinel
+```
+
+### Supplying Parameter Values Using the CLI
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can also use the `-param` CLI argument to supply parameter in a `key=value`
+pair.
+
+```plaintext
+sentinel apply -param foo=bar policy.sentinel
+```
+
+### Interactive CLI Prompting
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+If a required value has not been supplied when a policy is run with `sentinel apply`, it will be prompted for, along with its description:
+
+
+
+ $ sentinel apply policy.sentinel
+
+ policy.sentinel:2:7: requires value for parameter foo
+
+ An example parameter. Must be supplied or the policy will fail.
+
+
+ Values can be strings, floats, or JSON array or object values. To
+ force
+
+ strings, use quotes.
+
+
+ Enter a value: bar
+
+
+
+ Pass
+
+
+
+
+### CLI Value Format
+
+-> **NOTE:** This section contains details for the parameter features supported by `sentinel apply`.
+
+The CLI takes either strings, or JSON numbers, arrays, or maps. If you need a
+literal string value, quote the value.
+
+```sentinel
+foo // string
+42 // number (float)
+"42" // string ("42", without quotes)
+[1, 2] // array (list)
+{"a": "b"} // object (map)
+```
+
+-> **NOTE:** Boolean values are not supported by this method.
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/language/rules.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/language/rules.mdx
new file mode 100644
index 0000000000..a2e998bd79
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/language/rules.mdx
@@ -0,0 +1,165 @@
+---
+page_title: Sentinel Language - Rules
+sidebar_title: Rules
+sidebar_current: docs-language-rules
+description: >-
+ Rules form the basis of a policy by representing behavior that is either
+ passing or failing (true or false).
+layout: docs
+---
+
+# Language: Rules
+
+Rules form the basis of a policy by representing behavior that is either
+passing or failing (true or false). A policy can be broken down into a
+set of rules. Breaking down a policy into a set of rules can make it more
+understandable and aids with testing.
+
+An example is shown below:
+
+```sentinel
+is_sunny = rule { weather is "sunny" }
+is_wednesday = rule { day is "wednesday" }
+
+main = rule {
+ is_sunny and
+ is_wednesday
+}
+```
+
+A rule contains a single [boolean expression](/sentinel/language/boolexpr).
+This boolean expression can be split into multiple lines for readability
+as shown in the example above. Boolean expressions should only be split
+into multiple lines after the operator ("and", "or", etc.).
+
+A rule is also _lazy_ and _memoized_. _Lazy_ means that the rule is only
+evaluated when it is actually required, not at the point that it is
+created. This is covered in more detail in the [lazy](#lazy-and-memoized) section.
+_Memoized_ means that the value is only computed once and then saved. Once
+a rule is evaluated, the result of it is reused anytime the rule is referenced
+again.
+
+## Easing Understandability
+
+Rules are an abstraction that make policies much more understandable
+by making it easier for humans to see what the policy is trying to do.
+
+For example, consider the policy before which doesn't abstract into rules:
+
+```sentinel
+main = rule {
+ ((day is "saturday" or day is "sunday") and homework is "") or
+ (day in ["monday", "tuesday", "wednesday", "thursday", "friday"] and
+ not school_today and homework is "")
+}
+```
+
+Assume that `day`, `homework`, and `school_day` are available.
+
+In plain english, this policy is trying to say: you can play with your friends
+only on the weekend as long as there is no homework, or during the week if
+there is no school and no homework.
+
+Despite the relatively simple nature of this policy (a few lines, about 5
+logical expressions), it is difficult to read and understand, especially if
+you've not seen it before.
+
+The same policy using rules as an abstraction:
+
+```sentinel
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+is_valid_weekend = rule { is_weekend and homework is "" }
+is_valid_weekday = rule { not is_weekend and not school_today and homework is "" }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+By reading the names of the rules, its much clearer to see what each individual
+part of the policy is trying to achieve. The readability is improved even
+further when adding comments to the rules to explain them further, which is
+a recommended practice:
+
+```sentinel
+// A weekend is Sat or Sun
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+// A valid weekend is a weekend without homework
+is_valid_weekend = rule { is_weekend and homework is "" }
+
+// A valid weekday is a weekday without school
+is_valid_weekday = rule { not is_weekend and not school_today and homework is "" }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+## "When" Predicates
+
+A rule may have an optional `when` predicate attached to it. When this is
+present, the rule is only evaluated when the predicate results in `true`.
+Otherwise, the rule is not evaluated and the result of the rule is always
+`true`.
+
+"When" predicates should be used to define the context in which the rule
+is semantically meaningful. It is common when translating human language
+policy into Sentinel to have scenarios such as "when the key has a prefix
+of `/account/`, the remainder must be numeric." In that example, when the
+key _does not_ have the specified prefix, the policy doesn't apply.
+
+Assume you have rules `is_prefix` and `is_numeric` to check both the
+conditions in the example above. An example is shown with and without
+the "when" predicate:
+
+```sentinel
+example_no_when = rule { (is_prefix and is_numeric) or not is_prefix }
+
+example_when = rule when is_prefix { is_numeric }
+```
+
+The rules are equivalent in behavior for all values of `is_prefix` and
+`is_numeric`, but the second rule is more succint and easier to understand.
+
+## Testing
+
+To verify a policy works correct, the
+[built-in Sentinel test framework](/sentinel/writing/testing)
+uses rules as the point where you can assert behavior. You say that
+you expect certain rules to be true or false. By doing so, you ensure that
+the policy results in the value you expect using the rule flow that you
+expect.
+
+Therefore, in addition to readability, we recommend splitting policies into
+rules to aid testability.
+
+## Lazy and Memoized
+
+A rule is _lazy_ and _memoized_.
+
+_Lazy_ means that the rule is only evaluated when it is actually required,
+not at the point that it is created. And _memoized_ means that the value is
+only computed once and then saved. Once a rule is evaluated, the result of it
+is reused anytime the rule is referenced again.
+
+Both of these properties have important implications for Sentinel
+policies:
+
+**Performance:** Rules are a way to improve the performance of a Sentinel
+policy. If you have a boolean expression that is reused a lot, a rule will
+only be evaluated once. In the example shown above, `is_weekend` is used
+multiple times, but will only have to be evaluated once.
+
+**Behavior:** Rules accessing variables see the value of those variables
+at _the time they are evaluated_. This can lead to surprising behavior
+in some cases. For example:
+
+```sentinel
+a = 1
+b = rule { a == 1 }
+a = 2
+main = b
+```
+
+In this example, `main` will actually result to `false`. It is evaluated
+when it is needed, which is when the policy executes `main`. At this point,
+`a` is now 2 and `b` has not been evaluated yet. Therefore, `b` becomes `false`.
+All future references to `b` will return `false` since a rule is memoized.
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/language/scope.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/language/scope.mdx
new file mode 100644
index 0000000000..2fb562a75d
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/language/scope.mdx
@@ -0,0 +1,42 @@
+---
+page_title: Sentinel Language - Scope
+sidebar_title: Scope
+sidebar_current: docs-language-scope
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Scope
+
+Scope is the context in which variables are created and accessed. If a
+variable exists in a parent scope, it is accessed and can be modified.
+Otherwise, the variable is created in your current scope.
+
+Scopes are created with _blocks_. A block is a possibly empty sequence
+of statements within matching brace brackets `{}`. You may nest blocks
+to create nested scopes.
+
+In addition to explicitly created blocks, there are implicit blocks:
+
+1. The policy block encapsulates an entire policy.
+2. Each `any`, `all`, and `for` statement is considered to be in
+ its own block. Note that `if` statements _do not_ create their
+ own block.
+
+The example policy below shows various effects of scopes:
+
+```sentinel
+a = 1
+print(a) // 1
+
+f = func() {
+ print(a) // 1
+ a = 12
+ b = 1
+ return undefined
+}
+f()
+
+print(a) // 12
+print(b) // undefined
+```
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/language/slices.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/language/slices.mdx
new file mode 100644
index 0000000000..a8dafd5ec2
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/language/slices.mdx
@@ -0,0 +1,46 @@
+---
+page_title: Sentinel Language - Slices
+sidebar_title: Slices
+sidebar_current: docs-language-slices
+description: >-
+ Slices are an efficient way to create a substring or sublist from an existing
+ string or list, respectively.
+layout: docs
+---
+
+# Language: Slices
+
+Slices are an efficient way to create a substring or sublist from an
+existing string or list, respectively.
+
+The syntax for creating a slice is:
+
+```sentinel
+a[low : high]
+```
+
+`low` is the lowest index to slice from. The resulting slice includes
+the value at `low`. `high` is the index to slice to. The resulting slice
+will not include the value at `high`. The length of the resulting list
+or string is always `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+
+a = "hello"
+b = a[1:4] // "ell"
+```
+
+## Convenience Shorthands
+
+Some convenience shorthands are available by omitting the value for either the
+low or high index in the slice expression: omitting `low` implies a low index of
+0, and omitting `high` implies a high index of the length of the list.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+
+b = a[:2] // [1, 2] (same as a[0:2])
+b = a[2:] // [3, 4, 5] (same as a[2:length(a)])
+```
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/language/spec.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/language/spec.mdx
new file mode 100644
index 0000000000..0c7a1b9133
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/language/spec.mdx
@@ -0,0 +1,1297 @@
+---
+page_title: Sentinel Language Specification
+sidebar_title: Specification
+sidebar_current: docs-language-spec
+description: This is the specification for the Sentinel policy language.
+layout: docs
+---
+
+# Sentinel Language Specification
+
+This is the specification for the Sentinel policy language.
+
+The Sentinel language is designed with policy enforcement in mind. It is dynamically typed and garbage collected and has explicit support for _rule_ construction representing boolean logic.
+
+The language is designed to be easy to learn and use by non-programmers. It is expected to be embedded within applications.
+
+## Table of Contents
+
+
+
+
+- [Source code representation](#source-code-representation)
+- [Declarations and Scope](#declarations-and-scope)
+- [Blocks](#blocks)
+- [Lexical Elements](#lexical-elements)
+ - [Comments](#comments)
+ - [Identifiers](#identifiers)
+ - [Keywords](#keywords)
+ - [Operators and Delimiters](#operators-and-delimiters)
+ - [Integer Literals](#integer-literals)
+ - [Floating-point Literals](#floating-point-literals)
+ - [String Literals](#string-literals)
+ - [Implicit Line Joining](#implicit-line-joining)
+ - [Whitespace](#whitespace)
+ - [Semicolons](#semicolons)
+- [Variables](#variables)
+- [Undefined](#undefined)
+- [Expressions](#expressions)
+ - [Operand](#operand)
+ - [Primary Expressions](#primary-expressions)
+ - [Null](#null)
+ - [Booleans](#booleans)
+ - [Boolean Literals](#boolean-literals)
+ - [Boolean Expressions](#boolean-expressions)
+ - [List Literals](#list-literals)
+ - [Map Literals](#map-literals)
+ - [Function Literals](#function-literals)
+ - [Rule Expressions](#rule-expressions)
+ - [Index Expressions](#index-expressions)
+ - [Selectors](#selectors)
+ - [Slice Expressions](#slice-expressions)
+ - [Calls](#calls)
+ - [Operators](#operators)
+ - [Operator Precedence](#operator-precedence)
+ - [Arithmetic Operators](#arithmetic-operators)
+ - [Integer operators](#integer-operators)
+ - [Integer overflow](#integer-overflow)
+ - [Floating-point operators](#floating-point-operators)
+ - [String Concatenation](#string-concatenation)
+ - [List Concatenation](#list-concatenation)
+ - [Comparison Operators](#comparison-operators)
+ - [Logical Operators](#logical-operators)
+ - [Set Operators](#set-operators)
+ - [Matches Operator](#matches-operator)
+ - [Else Operator](#else-operator)
+ - [Quantifier Expressions (any, all, filter)](#quantifier-expressions-any-all-filter)
+- [Statements](#statements)
+ - [Expression Statements](#expression-statements)
+ - [Assignments](#assignments)
+ - [If Statements](#if-statements)
+ - [Case Statements](#case-statements)
+ - [For Statements](#for-statements)
+ - [Break Statements](#break-statements)
+ - [Continue Statements](#continue-statements)
+ - [Return Statements](#return-statements)
+- [Imports](#imports)
+- [Parameters](#parameters)
+- [Built-in Functions](#built-in-functions)
+ - [Length](#length)
+ - [Collections](#collections)
+ - [List Append](#list-append)
+ - [Map Delete](#map-delete)
+ - [Keys and Values](#keys-and-values)
+ - [Range](#range)
+ - [Type Conversion](#type-conversion)
+ - [Printing](#printing)
+ - [Errors](#errors)
+- [Program Execution](#program-execution)
+
+
+
+## Source code representation
+
+Source code is Unicode text encoded in UTF-8. A "character" referenced in this document refers to a Unicode code point. Each code point is distinct: upper and lower case letters are different characters.
+
+The underscore character \_ (U+005F) is considered a "letter".
+
+```ebnf
+letter = unicode_letter | "_" .
+decimal_digit = "0" … "9" .
+octal_digit = "0" … "7" .
+hex_digit = "0" … "9" | "A" … "F" | "a" … "f" .
+```
+
+## Declarations and Scope
+
+A declaration binds an identifier to a value. An identifier is declared at the point it is first assigned. An assignment is considered a first assignment if the identifier hasn't already been previously declared in the current scope or any parent scopes.
+
+The scope of an identifier is the extent of source text in which the identifier denotes the specified value. Sentinel is lexically scoped using blocks.
+
+An identifier declared in a block may be redeclared in an inner block. While the identifier of the inner declaration is in scope, it denotes the value assigned in the inner declaration.
+
+## Blocks
+
+A block is a possibly empty sequence of statements within matching brace brackets.
+
+```ebnf
+Block = "{" StatementList "}" .
+StatementList = { Statement ";" } .
+```
+
+Blocks nest and affect scoping.
+
+In addition to explicit blocks in the source code, there are implicit blocks:
+
+1. The universe block encompasses all source text.
+2. Each program has a program block containing all source text for that program.
+3. Each "any", "all", and "for" statements is considered to be in its own implicit block. "if" does not create an implicit block.
+
+## Lexical Elements
+
+### Comments
+
+Comments are sections of source text used for documentation.
+
+```plaintext
+# Single line comment
+// Single line comment
+/* multi
+line
+ comment */
+```
+
+Three forms of comments are supported: two single-line forms and one multi-line form. A single-line comment begins with the token `//` or `#`. Everything between the starting token and the end of the line is ignored.
+
+A multi-line comment begins with the token `/*` and ends with the token `*/`. Everything between `/*` and `*/` is ignored.
+
+A comment may not start inside a string literal or inside another comment.
+
+A multi-line comment containing no newlines acts like a space.
+
+### Identifiers
+
+Identifiers name program entities such as rules, variables, and functions. An identifier is a sequence of one or more letters and digits. The first character in an identifier must be a letter.
+
+```ebnf
+identifier = letter { letter | unicode_digit } .
+```
+
+```sentinel
+a
+_a
+_A
+αβ
+```
+
+#### Pre-Declared Identifiers
+
+The following identifiers are implicitly declared in the [universe block](#blocks):
+
+```plaintext
+Constants:
+ false null true undefined
+
+Functions:
+ append bool delete error float int keys length print range string values
+```
+
+### Keywords
+
+The following keywords are reserved and may not be used as identifiers:
+
+```plaintext
+all
+any
+as
+break
+case
+continue
+default
+else
+for
+func
+if
+import
+param
+return
+rule
+when
+```
+
+### Operators and Delimiters
+
+The following character sequences represent operators, delimiters, and other special tokens:
+
+```plaintext
++
+-
+*
+/
+%
++=
+-=
+*=
+/=
+%=
+==
+<
+>
+=
+!
+!=
+<=
+>=
+( )
+[ ]
+{ }
+,
+.
+:
+;
+and
+contains
+else
+in
+is
+matches
+not
+or
+xor
+```
+
+As a special case, `else` is both a keyword and operator, depending on the context. See [Else Operator](#else-operator) for more details.
+
+### Integer Literals
+
+An integer literal is a sequence of digits representing an integer constant. An optional prefix sets a non-decimal base: `0` for octal, `0x` or `0X` for hexadecimal. In hexadecimal literals, letters `a-f` and `A-F` represents values 10 through 15.
+
+Integers are signed 64-bit values (-9223372036854775808 to 9223372036854775807).
+
+```ebnf
+int_lit = decimal_lit | octal_lit | hex_lit .
+decimal_lit = ( "1" … "9" ) { decimal_digit } .
+octal_lit = "0" { octal_digit } .
+hex_lit = "0" ( "x" | "X" ) hex_digit { hex_digit } .
+```
+
+```
+42
+0600
+0xBadFace
+170141183460469231731687303715884105727
+```
+
+### Floating-point Literals
+
+A floating-point literal is a decimal representation of a floating-point constant. It has an integer part, a decimal point, a fractional part, and an exponent part. The integer and fractional part comprise decimal digits; the exponent part is an e or E followed by an optionally signed decimal exponent. One of the integer part or the fractional part may be elided; one of the decimal point or the exponent may be elided.
+
+Floating-point numbers are IEEE-754 64-bit floating numbers.
+
+```ebnf
+float_lit = decimals "." [ decimals ] [ exponent ] |
+ decimals exponent |
+ "." decimals [ exponent ] .
+decimals = decimal_digit { decimal_digit } .
+exponent = ( "e" | "E" ) [ "+" | "-" ] decimals .
+```
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+### String Literals
+
+String literals are character sequences between double quotes, as in "bar". Within the quotes, any character may appear except newline and unescaped double quote. The text between the quotes forms the value of the literal.
+
+A multi-character sequence beginning with a backslash encode values in various formats.
+
+Backslash escapes allow arbitrary values to be encoded as ASCII text. There are four ways to represent the integer value as a numeric constant: `\x` followed by exactly two hexadecimal digits; `\u` followed by exactly four hexadecimal digits; `\U` followed by exactly eight hexadecimal digits, and a plain backslash `\` followed by exactly three octal digits. In each case the value of the literal is the value represented by the digits in the corresponding base.
+
+After a backslash, certain single-character escapes represent special values:
+
+```plaintext
+\a U+0007 alert or bell
+\b U+0008 backspace
+\f U+000C form feed
+\n U+000A line feed or newline
+\r U+000D carriage return
+\t U+0009 horizontal tab
+\v U+000b vertical tab
+\\ U+005c backslash
+\" U+0022 double quote
+```
+
+The three-digit octal (\nnn) and two-digit hexadecimal (\xnn) escapes represent individual bytes of the resulting string; all other escapes represent the (possibly multi-byte) UTF-8 encoding of individual characters. Thus inside a string literal \377 and \xFF represent a single byte of value 0xFF=255, while ÿ, \u00FF, \U000000FF and \xc3\xbf represent the two bytes 0xc3 0xbf of the UTF-8 encoding of character U+00FF.
+
+A string value is a (possibly empty) sequence of bytes. Strings are immutable: once created, it is impossible to change the contents of a string.
+
+The length of a string s (its size in bytes) can be discovered using the built-in function `length`. An individual character (of type string) can be accessed by integer indices 0 through `length(s)-1`.
+
+```ebnf
+string_lit = `"` { unicode_value | byte_value } `"` .
+unicode_value = unicode_char | little_u_value | big_u_value | escaped_char .
+byte_value = octal_byte_value | hex_byte_value .
+octal_byte_value = `\` octal_digit octal_digit octal_digit .
+hex_byte_value = `\` "x" hex_digit hex_digit .
+little_u_value = `\` "u" hex_digit hex_digit hex_digit hex_digit .
+big_u_value = `\` "U" hex_digit hex_digit hex_digit hex_digit
+ hex_digit hex_digit hex_digit hex_digit .
+escaped_char = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | `"` ) .
+```
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+### Implicit Line Joining
+
+Expressions can be split over more than one line. For example:
+
+```sentinel
+a or
+b or # This is a comment
+c
+```
+
+Implicitly continued lines can have trailing comments. Blank continued lines are allowed.
+
+### Whitespace
+
+Whitespace is needed to separate tokens, but no distinction is made between the number and combination of whitespace characters. Whitespace characters can be space (U+0020), horizontal tabs (U+0009), carriage returns (U+000D), and newlines (U+000A).
+
+```sentinel
+a or b
+
+a or b
+
+a or
+ b
+
+rule { a or b }
+
+rule {
+ a or b }
+
+rule {
+ a or
+ b
+}
+```
+
+### Semicolons
+
+The formal grammar uses semicolons ";" as terminators in a number of productions.
+It is idiomatic Sentinel source to omit semicolons in most cases.
+
+A semicolon is automatically inserted into the token stream immediately after
+a line's final token if that token is:
+
+- An identifier
+- An integer, float, or string literal
+- The keyword `break`, `continue`, or `return`
+- The delimiter `)`, `]`, or `}`
+
+## Variables
+
+A variable is a storage location for holding a value.
+
+A variable has a dynamic type, which is the concrete type of the value assigned to the variable at run time. The dynamic type may vary during execution and change as a result of further assignments.
+
+A variable's value is retrieved by referring to the variable in an expression; it is the most recent value assigned to the variable. If a variable has not yet been declared (initially assigned), it is an error.
+
+```sentinel
+x = 7 // x is type int
+x = "foo" // x is now type string
+
+x = y // error if y is not previously assigned
+```
+
+## Undefined
+
+The value denoted by the keyword `undefined` represents undefined behavior or values. It can be created directly using the keyword `undefined`. It is also returned as a result of expressions in specified cases.
+
+`undefined` is a valid operand for any operations. Only `undefined or true` will result in true. All other operations result in `undefined`. An exception is if `undefined` is not reached as a result of short-circuit operations.
+
+```sentinel
+undefined OR true = true
+undefined OR false = undefined
+undefined OR undefined = undefined
+undefined AND true = undefined
+undefined AND false = undefined
+undefined AND undefined = undefined
+undefined XOR true = undefined
+undefined XOR false = undefined
+undefined XOR undefined = undefined
+
+// Short-circuit examples
+false OR true OR undefined = true
+false OR undefined OR true = true
+true AND false AND undefined = false
+true AND undefined AND false = undefined
+
+// Non-logical operators
+undefined + 5 = undefined
+-undefined = undefined
+!undefined = undefined
+```
+
+If the result of the main rule is `undefined`, it is treated as `false` but is indicative of erroneous logic.
+
+## Expressions
+
+### Operand
+
+Operands denote the elementary values in an expression. An operand may be a literal, an identifier denoting a variable, rule, or function, or a parenthesized expression.
+
+```ebnf
+Operand = Literal | OperandName | MethodExpr | "(" Expression ")" .
+Literal = BasicLit | CompositeLit | FunctionLit | RuleLit .
+BasicLit = int_lit | float_lit | string_lit .
+OperandName = identifier | QualifiedIdent.
+```
+
+### Primary Expressions
+
+Primary expressions are the operands for unary and binary expressions.
+
+```ebnf
+PrimaryExpr =
+ Operand |
+ PrimaryExpr Selector |
+ PrimaryExpr Index |
+ PrimaryExpr Slice |
+ PrimaryExpr Arguments .
+
+Selector = "." identifier .
+Index = "[" Expression "]" .
+Slice = "[" [ Expression ] ":" [ Expression ] "]" |
+ "[" [ Expression ] ":" Expression ":" Expression "]" .
+Arguments = "(" [ ( ExpressionList | Type [ "," ExpressionList ] ) [ "..." ] [ "," ] ] ")" .
+```
+
+```sentinel
+x
+2
+(s + ".txt")
+f(3.1415, true)
+m["foo"]
+s[i : j + 1]
+obj.color
+f.p[i].x()
+```
+
+### Null
+
+The reserved word `null` denote the singleton value `null`. Null represents the explicit absense of a value. Behavior of `null` within expressions is specified explicitly for each expression.
+
+### Booleans
+
+### Boolean Literals
+
+The reserved words `true` and `false` denote objects that represent the boolean values `true` and `false`, respectively. These are boolean literals.
+
+```ebnf
+BoolLit = "true" | "false" .
+```
+
+#### Boolean Expressions
+
+Boolean expressions are expressions that must result in a boolean value. Any other value type becomes the `undefined` value.
+
+```ebnf
+BoolExpr = Expression .
+```
+
+### List Literals
+
+A list literal denotes a list, which is an integer indexed collection of values.
+
+```ebnf
+ListLit = "[" [ ElementList [ "," ] ] "]" .
+ElementList = Element { "," Element } .
+Element = Expression | LiteralValue .
+```
+
+A list may contain zero or more values. The number of values in a list is its length.
+
+A list has a set of indices. An empty list has an empty set of indices. A non-empty list has the index set `{0...n - 1}` where `n` is the length of the list. Attempting to access a list using an index that is not a member of its set of indices results in the `undefined` value.
+
+### Map Literals
+
+A map literal denotes a map, which is an unordered group of elements indexed by a set of unique keys.
+
+```ebnf
+MapLit = "{" [ KeyedElementList [ "," ] ] "}" .
+KeyedElementList = KeyedElement { "," KeyedElement } .
+KeyedElement = Element ":" Element .
+```
+
+Keys can only be a boolean, numeric, or string type.
+
+The value of a non-existent key is the `undefined` value.
+
+### Function Literals
+
+A function literal represents a function.
+
+```ebnf
+FunctionLit = "func" Function .
+Function = Parameters FunctionBody .
+FunctionBody = Block .
+Parameters = "(" [ IdentifierList [ "," ] ] ")" .
+IdentifierList = identifier { "," identifier } .
+```
+
+```sentinel
+func(a, b) { return b }
+```
+
+Function literals are only allowed in the file scope. They may refer to variables defined in surrounding blocks.
+
+A function must terminate with a return statement. If a function has no meaningful return value, it should return `undefined`.
+
+### Rule Expressions
+
+A rule is a boolean expression that is evaluated lazily and the result is memoized.
+
+If the optional "when" predicate is present, the rule is evaluated only when
+the "when" boolean expression results in true. If the predicate is false,
+the rule is not evaluated and returns true. The predicate is evaluated when
+the rule would be evaluated; it is also lazy and memoized in the same way.
+
+```ebnf
+RuleExpr = "rule" [ "when" BoolExpr ] "{" BoolExpr "}" .
+```
+
+```sentinel
+rule { x is y }
+
+rule {
+ x == "value" or
+ y == "other"
+}
+
+rule when x is y { y > 42 }
+```
+
+### Index Expressions
+
+A primary expression of the form `a[x]` denotes the element of a list, map, or string indexed by x. The value x is called the index or key.
+
+For `a` of type map:
+
+- If `a` contains a key `x`, `a[x]` is the map value with key `x`.
+- If `a` does not contain key `x`, `a[x]` is the `undefined` value.
+
+For `a` of type list:
+
+- `x` must be an integer
+- `x` must be in the range `[-1 * length(a), length(a)-1]`
+- If `x` is contained in the set of indices of `a`, `a[x]` is the list element at index `x`. If `x` is negative, `a[x]` is equivalent to `a[length(a)+x]`.
+- If `x` is not contained in the set of indices of `a`, `a[x]` is the `undefined` value.
+
+For `a` of type string:
+
+- `x` must be an integer
+- `x` must be in the range `[-1 * length(a), length(a)-1]`
+- If `x` is in range, `a[x]` is the byte value at index `x` as a string. If `x` is negative, `a[x]` is equivalent to `a[length(a)+x]`.
+- If `x` is out of range, `a[x]` is the `undefined` value.
+
+For `a` of value `null`:
+
+- `a[x]` is the `undefined` value.
+
+Otherwise `a[x]` is an error.
+
+### Selectors
+
+For a primary expression x, the selector expression `x.f` denotes the field `f` of the value `x`. The identifier `f` is called the selector. The type of the selector expression is the type of the selector.
+
+As a special case, selectors can be [reserved words](#keywords) and keyword [operators](#operators-and-delimiters), but cannot be any other non-identifier element.
+
+Selectors are used to access data from [imports](#imports). The first primary expression `x` in `x.f` denotes the import name. The field `f` is the selector to access data from the import.
+
+Selectors may also be used to access map data with static keys. They are syntactic sugar over index expressions. `math.pi` is equivalent to `math["pi"]` and exhibit the same limitations and behavior as an index expression. Selectors cannot, however, be used to assign values to map data.
+
+Selectors on undefined result in undefined.
+
+```sentinel
+math.pi
+time.pst.hour
+```
+
+### Slice Expressions
+
+Slice expressions construct a substring or list from a string or list.
+
+The primary expression
+
+```sentinel
+a[low : high]
+```
+
+constructs a substring or list. The indices `low` and `high` select which elements of operand `a` appear in the result. The result has indices starting at 0 and length equal to `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+```
+
+For convenience, any of the indices may be omitted. A missing `low` index defaults to zero; a missing `high` index defaults to the length of the sliced operand:
+
+```sentinel
+a[2:] // same as a[2 : length(a)]
+a[:3] // same as a[0 : 3]
+a[:] // same as a[0 : length(a)]
+```
+
+The indices are in range if `0 <= low <= high <= length(a)`, otherwise they are out of range. If the indices are out of range at run time, the result is the `undefined` value.
+
+If `a` is the value `null`, the result is the `undefined` value.
+
+If `a` is any other value type, it is an error.
+
+### Calls
+
+Given an expression `f` where `f` is a function value:
+
+```sentinel
+f(a1, a2, … an)
+```
+
+calls `f` with arguments `a1, a2, ... an`. The type of the expression is the result of `f`. The arguments are evaluated left to right. Arguments are passed by value.
+
+### Operators
+
+```ebnf
+Expression = UnaryExpr | Expression binary_op Expression .
+UnaryExpr = PrimaryExpr | unary_op UnaryExpr .
+
+binary_op = logical_op | set_op | rel_op | add_op | mul_op | else_op .
+logical_op = "and" | "or" | "xor" .
+set_op = ["not"] ( "contains" | "in" ).
+rel_op = "==" | "!=" | "<" | "<=" | ">" | ">=" |
+ "is" | "is not" | "matches" | "not matches" .
+add_op = "+" | "-" .
+mul_op = "*" | "/" | "%" .
+else_op = "else" .
+unary_op = "+" | "-" | "!" | "not" .
+```
+
+#### Operator Precedence
+
+Unary operators have the highest precedence.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Binary operators of the same precedence associate from left to right. For instance, x / y _ z is the same as (x / y) _ z.
+
+### Arithmetic Operators
+
+Arithmetic operators apply to numeric values and yield a result of the same type when both operands are the same.
+
+Arithmetic operations between integer and floating-point types result in a floating-point value with the integer operand treated as a floating-point value for purposes of calculation.
+
+Arithmetic operations between numeric values (integer, floating-point) and non-numeric values (strings, lists) are not permitted.
+
+All five supported arithmetic operators (+, -, \*, /, %) apply to both integer and floating-point types; + also applies to strings.
+
+```plaintext
++ sum integers, floats, strings, lists
+* difference integers, floats
+* product integers, floats
+/ quotient integers, floats
+% remainder integers, floats
+```
+
+#### Integer operators
+
+For two integer values x and y, the integer quotient `q = x / y` and remainder `r = x % y` satisfy the following relationships:
+
+```sentinel
+x = q*y + r and |r| < |y|
+```
+
+with x / y truncated towards zero.
+
+```plaintext
+ x y x / y x % y
+ 5 3 1 2
+-5 3 -1 -2
+ 5 -3 -1 2
+-5 -3 1 -2
+```
+
+As an exception to this rule, if the dividend x is the most negative value for the int type of x (-9223372036854775808), the quotient `q = x / -1` is equal to x (and r = 0).
+
+If the divisor is a constant, it must not be zero. If the divisor is zero at run time, an error occurs.
+
+#### Integer overflow
+
+For signed integers, the operations `+`, `-`, and `*` may legally overflow and the resulting value exists and is deterministically defined by the signed integer representation, the operation, and its operands. No exception is raised as a result of overflow.
+
+#### Floating-point operators
+
+For floating-point numbers, +x is the same as x, while -x is the negation of x. The result of a floating-point division by zero is not specified beyond the IEEE-754 standard.
+
+#### String Concatenation
+
+Strings can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = "hi"
+y = "hello"
+x = x + ", " + y // "hi, hello"
+x += " and good bye" // "hi, hello and good bye"
+```
+
+String addition creates a new string by concatenating the operands.
+
+#### List Concatenation
+
+Lists can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = [1, 2]
+x = x + [2, 3] // [1, 2, 2, 3]
+x += [4] // [1, 2, 2, 3, 4]
+```
+
+List addition creates a new list by concatenating the operands.
+
+### Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+In any comparison, the two operands must be equivalent types. The only exception are integers and floats, which are considered numeric and may be compared. Any comparison between other non-matching types results in the `undefined` value.
+
+The equality operators `==`, `!=`, `is`, and `is not` apply to operands that are comparable. The ordering operators `<`, `<=`, `>`, and `>=` apply to operands that are ordered. The behavior of `is` with `==` and `is not` with `!=` is identical. These terms and the result of the comparisons are defined as follows:
+
+- Boolean values are comparable. Two boolean values are equal if they are either both true or both false.
+- Integer values are comparable and ordered, in the usual way.
+- Floating point values are comparable and ordered, as defined by the IEEE-754 standard.
+- An integer compared with floating point value treats the integer as the converted floating point value.
+- String values are comparable and ordered, lexically byte-wise.
+- Lists are comparable. Lists are equal if they are of equal length and their
+ corresponding elements are comparable and equal.
+
+Maps are not comparable. As a special case, maps may be compared to the `null` value for equality or inequality.
+
+If either operand is the `undefined` value, the result of the expression is the `undefined` value.
+
+### Logical Operators
+
+Logical operators apply to boolean values and yield a boolean result.
+
+Logical operators are evaluated left-to-right and perform short-circuit logic. The right-hand side is not guaranteed to be evaluated if a short-circuit can be performed.
+
+```plaintext
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+### Set Operators
+
+The set operators `contains` and `in` test for set inclusion for lists
+and maps, and substring inclusion for strings.
+
+Set operators may be negated by prefixing the operator with `not`: `not contains` and `not in`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+`contains` tests if the left-hand collection contains the right-hand value. For lists, this tests equality of any value. For maps, this tests equality of any key. For strings, this tests for substring existence.
+
+`in` tests if the right-hand collection contains the left-hand value. The behavior is equivalent to `contains`.
+
+The collection must be a list, map, or string. If it is the `undefined` value, the result is the `undefined` value. For any other value, the result is an error.
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+
+"test" contains "est" // true
+"test" contains "best" // false
+"test" in "testing" // true
+"best" in "testing" // false
+```
+
+### Matches Operator
+
+The matches operator tests if a string matches a regular expression.
+
+The matches operators may be negated by prefixing the operator with `not`: `not matches`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+The left-hand value is the string to test. The right-hand value is a string value representing a regular expression.
+
+The syntax of the regular expression is the same general syntax used by Python, Ruby, and other languages. More precisely, it is the syntax accepted by RE2. The regular expression is not anchored by default; any anchoring must be explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+If either operand is `undefined`, the result is the `undefined` value. For any other non-string value, the result is an error.
+
+### Else Operator
+
+Else operators can be used to provide a default value for an expression that may evaluate to the `undefined` value. Else operators return their left-hand value unless it is `undefined`, otherwise they return their right-hand value.
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else null
+```
+
+### Quantifier Expressions (any, all, filter)
+
+Quantifiers are expressions that apply a predicate to a collection (list or map). The purpose of the quantifier is to produce a result based on its particular type.
+
+`any` and `all` expressions are existential and universal quantifiers, respectively. `any` expressions are equivalent to a chain of `or` and `all` expressions are equivalent to a chain of `and`. Both expressions implement short-circuiting equivalent to logical operators.
+
+The `filter` expression is a subset quantifier and returns the subset of all values in the input collection for which the supplied predicate applies. This will be zero or more elements, up to the length of the input.
+
+The body of a quantifier expression is a boolean expression.
+
+`any` returns the boolean value `true` if any value in the collection expression results in the body expression evaluating to `true`. If the body expression evaluates to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression result in the body expression evaluating to `true`. If any value in the collection expression result in the body expression evaluating to `false`, the all expression returns `false`.
+
+For empty collections, `any` returns false and `all` returns `true`.
+
+`filter` returns a list or map, the same type as its input collection. Only the elements for which the expression body evaluated to `true` will be returned. If an iteration of the expression body results in `undefined`, the entire result set is `undefined`.
+
+When the collection is a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When the collection is a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+QuantExpr = QuantOp Expression "as" [ identifier "," ] identifier "{" BoolExpr "}" .
+QuantOp = "all" | "any" | "filter" .
+```
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" } // true if every iterations is true
+any group.tasks as t { t.driver is "vmware" } // true if any iteration is true
+filter group.tasks as t { t.driver is "vmware" } // subset of tasks that is true
+```
+
+## Statements
+
+### Expression Statements
+
+Function call expressions may exist in the statement context.
+
+```ebnf
+ExpressionStmt = Expression .
+```
+
+### Assignments
+
+Assignments set the value of an expression to the value of another expression.
+
+```ebnf
+Assignment = AssignExpr assign_op Expression .
+AssignExpr = identifier | IndexExpr .
+assign_op = [ add_op | mul_op ] "=" .
+```
+
+The assignment `x op= y` is equivalent to `x = x op (y)` for supported values of `op`.
+
+```sentinel
+a = 1
+b[12] = 42
+c["key"] = "value"
+
+a *= 12
+b[12] += 8
+```
+
+Assignments to lists and maps can be carried out using [index expressions](#index-expressions), with the operands of the index expression being evaluated alongside the right hand side expression in a right-to-left (RHS, LHS) order.
+
+In addition to the rules detailed in the section describing their use, the following rules apply when assigning to maps or lists using index expressions:
+
+- The [variable](#variables) must exist and be a list or map. Attempts to use an index assignment on an unknown variable, or a variable that not a list or map, results in a runtime error.
+- Assigning to a list or map index that already exists overwrites that index's data with evaluated right hand side's value.
+- Assigning to an unknown key in a map creates a key for that map with the evaluated right hand side's value assigned to that key.
+- Attempting to assign a value to a list index that is out of range results in a runtime error.
+
+### If Statements
+
+If statements specify the conditional execution of two branches according to the value of a boolean expression. If the expression evaluates to true, the "if" branch is executed, otherwise, if present, the "else" branch is executed.
+
+```ebnf
+IfStmt = "if" BoolExpr Block [ "else" ( IfStmt | Block ) ] .
+```
+
+```sentinel
+if x < y {
+ return x
+} else if x > z {
+ return z
+} else {
+ return y
+}
+```
+
+### Case Statements
+
+Case statements specify a selection control mechanism to conditionally execute a branch based on expression equality.
+
+Each clause that wishes to provide an expression must use the "when" keyword. Multiple expressions can be provided, separated with a comma (",").
+
+There can be one, optional, "else" clause within a `case` statement. The "else" clause is executed if all other clauses failed to run.
+
+The clauses are evaluated in the order they are listed, with the first successful evaluation being executed.
+
+A `case` statement can optionally provide an expression. If no expression is provided, it is the equivalent of writing `case true {`.
+
+```ebnf
+CaseStmt = "case" [ Expression ] "{" { CaseWhenClause } "}" .
+CaseWhenClause = CaseWhenCase ":" StatementList .
+CaseWhenCase = "when" Expression { "," Expression } | "else" .
+```
+
+```sentinel
+case x {
+when y, z:
+ return true
+else:
+ return false
+}
+
+case {
+when x > 42:
+ return true
+else:
+ return false
+}
+```
+
+### For Statements
+
+A `for` statement specifies repeated execution of a block.
+
+The expression must evaluate to a list or a map.
+
+When iterating over a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When iterating over a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+ForStmt = "for" Expressions "as" [ identifier "," ] identifier Block .
+```
+
+```sentinel
+for [1, 2, 3] as v { count += v } // basic sum
+
+for [1, 2, 3] as idx, v {
+ if idx > 1 { count += v }
+}
+
+data = { "a": 12, "b": 32 }
+for data as k { count += data[k] } // sum map values
+for data as k, v { count += v }
+```
+
+### Break Statements
+
+A `break` statement terminates execution of the innermost `for` statement
+within the same function.
+
+```ebnf
+BreakStmt = "break" .
+```
+
+```sentinel
+// Will only print "1"
+for [1, 2, 3] as v {
+ print(v)
+ break
+}
+```
+
+### Continue Statements
+
+A `continue` statement begins the next iteration of the innermost `for` loop.
+The `for` loop must be within the same function.
+
+```ebnf
+ContinueStmt = "continue" .
+```
+
+```sentinel
+// Will print 1, 3
+for [1, 2, 3] as v {
+ if v == 2 {
+ continue
+ }
+
+ print(v)
+ break
+}
+```
+
+### Return Statements
+
+A return statement in a function F terminates the execution of F and provides the result value.
+
+```ebnf
+ReturnStmt = "return" Expression .
+```
+
+A function must terminate with a return statement.
+
+The return statement can be invoked at any time during a function to terminate execution.
+
+## Imports
+
+An import adds an assignment to the global scope to external definitions.
+
+The import declarations must only appear at the top of the source file, before any other statements. Comments may appear before imports.
+
+```ebnf
+ImportDecl = "import" Name ["as" identifier] .
+Name = string_lit .
+```
+
+An import name and identifier must be distinct. Two names may not repeated even if they're declared with different identifiers.
+
+If an identifier is not specified, the identifier is the name of the import itself.
+
+An import is not a valid value. The identifier representing the import may not be used as an expression, such as in assignment statements or call expressions.
+
+Values in imports are not assignable directly via their selectors. Function calls may be used to alter the state of the external data represented by the import. Whether or not this is supported is specific to the import implementation. The exception to this assignment restriction is the memoized data returned by a call to an import (see below).
+
+Data returned by imports can be memoized, meaning that data is returned by the import in the form of a map which is then used as much as possible without having to return to the import for more data. For example, `t = time.now` returns a map so that `t.hour` will be able to be looked up without having to go back to the import for that data.
+
+Data returned by imports may be callable, meaning that methods may be called on the memoized data returned by an import. For example, given `t = time.now`, `t.after(some_previous_time)` is valid, with the receiver data being set to the local memoized data stored in `t`. It is a valid case to accept modifications to the receiver data (example: assignment to the memoized data via index expressions), as long as all data in in the receiver continues to be string-keyed. It is an invalid case to accept receiver data with non-string-typed keys. Methods may also alter the contents of receiver data so that it is different after the call is finished.
+
+As with methods, imports may also have callable keys where the data may not be memoized. Example: in the event of `v = foo.bar`, if `v.baz` is not included in the memoized data, a call will be made to the import to fetch it. The same rules and restrictions to receiver data apply to callable keys as do to method calls.
+
+The support for callable methods and non-memoized keys on data returned by imports is specific to the import implementation.
+
+The handling of import loading is specific to the runtime implementation.
+
+Implicit imports may be added by the runtime, for example for standard library definitions. An explicit import of the same name should override a matching implicit import. For example, if "time" is an implicit import and the program specifies `import "time" as cal`, then only `cal` is defined.
+
+```sentinel
+import "time"
+import "math" as m
+```
+
+## Parameters
+
+```ebnf
+ParamDecl = "param" identifier [ "default" Expression ]
+```
+
+A parameter is a way for a policy to describe variables that are expected to be supplied by the calling application, in addition to any default value.
+
+Parameter declarations must appear at the top of the source file, following imports.
+
+Like imports, comments may appear before parameters; this is mainly pertinent when the policy is not importing anything, which would make parameters the first declarations that appear in a policy.
+
+A parameter declaration describes a valid variable that is added to the scope of a policy at runtime. This variable has the same properties and rules as other variables assigned during the course of policy evaluation as defined by the specification. This includes having a dynamic type and being able to be re-assigned during execution.
+
+Parameters may only be strings, integers, floating point numbers, booleans, lists, or maps. More complex types such as rules or functions are not allowed.
+
+A parameter can specify a default value by adding one in the declaration via use of the "default" keyword, and supplying a literal for the default value. The literal for a default is a very limited expression, comprising of:
+
+- For strings, a simple string literal;
+- For integers and floating-point numbers, either a literal denoting the value, or a unary expression with the literal, and the operators - or +;
+- For booleans, the pre-declared identifiers true or false;
+- For lists and maps, their respective literals composed of values and keys (for maps) of the above values only.
+
+Any other type of expression or identifier is not allowed.
+
+A parameter that does not have a default value defined is required by the policy. Evaluating a policy with a required parameter that has not been supplied at execution results in a runtime error.
+
+Parameters must not conflict with imports or any other identifiers currently defined in the global scope, including any reserved or pre-declared identifiers. Attempting to assign a parameter to an existing value results in a runtime error.
+
+```sentinel
+import "time"
+
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+param time default "11:00" // error, conflicts with "time" import
+param undefined // error, reserved identifier
+```
+
+## Built-in Functions
+
+Built-in functions are predeclared. They are called like any other function.
+
+### Length
+
+The built-in function `length` returns the length of a collection of string.
+
+The length of a string is the number of bytes in the string. It is not necessarilly the number of characters.
+
+The length of a collection is the number of elements in that collection.
+
+The length of `undefined` is `undefined`.
+
+### Collections
+
+#### List Append
+
+The built-in function `append` appends a value to the end of a list in-place.
+
+Appending to a non-list value results in an immediate `fail`.
+
+The return value of `append` is always the `undefined` value.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+#### Map Delete
+
+The built-in function `delete` deletes elements from a map by key. The map is modified in-place.
+
+Deleting a key that does not exist does nothing.
+
+Calling delete for a non-map value results in an immediate `fail`.
+
+The return value of `delete` is always the `undefined` value.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
+
+#### Keys and Values
+
+The built-in function `keys` and `values` return the keys and values of a map, respectively. The return value is a list. Both functions return values in an unspecified order.
+
+The `keys` or `values` of `undefined` is `undefined`.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+### Range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it defaults to `1`.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4]
+range(1, 5, 2) // [1,3]
+range(0, -3, -1) // [0,-1,-2]
+```
+
+### Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a value of that type according to the rules below.
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
+
+### Printing
+
+The built-in function `print` can be used to output formatted debug information. The runtime may omit print function calls depending on its execution mode. The runtime may choose where the output of a print function goes.
+
+`print` is a variadic function, accepting one or more values to be printed; values are separated by a single whitespace character (`" "`).
+
+The return value of `print` is always `true` to allow it to be used in boolean expressions.
+
+```sentinel
+print("hello") // "hello"
+print("hello", "world") // "hello world"
+print("The", "number", "is", 42) // "The number is 42"
+print([1, 2, 3]) // "[1, 2, 3]"
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // "false"
+```
+
+### Errors
+
+The built-in function `error` is equivalent to `print` but immediately causes execution to halt and the main rule to evaluate to `false`. The program execution is considered to have failed.
+
+## Program Execution
+
+Program execution begins by evaluating the source top to bottom. If evaluation is successful, the `main` rule is evaluated and its result is returned. Execution can be interrupted at any time by an error, which can be called explicitly or implicitly through illegal or undefined behavior.
+
+---
+
+> The content of this page is licensed under the [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
+>
+> Based on [The Go Programming Language Specification](https://golang.org/ref/spec) licensed under [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/language/undefined.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/language/undefined.mdx
new file mode 100644
index 0000000000..fa8dd50cc4
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/language/undefined.mdx
@@ -0,0 +1,98 @@
+---
+page_title: Sentinel Language - Undefined
+sidebar_title: Undefined
+sidebar_current: docs-language-undefined
+description: >-
+ The value `undefined` is a special value representing undefined behavior or an
+ unknown value. Any operation on an `undefined` value results in `undefined`.
+layout: docs
+---
+
+# Language: Undefined
+
+The value `undefined` is a special value representing undefined behavior
+or an unknown value.
+
+Undefined is not an error because there are situations where the undefined
+value can be safely ignored and the language also provides construts for
+recovering from an undefined value.
+
+The undefined value can be created manually with `undefined`. In most cases,
+undefined is created by accessing a non-existent list element or value.
+The undefine value is created in a number of other circumstances. The documentation
+will note when an undefined value may be created.
+
+Almost any operation with `undefined` results in `undefined`. For example,
+performing math on undefined, attempting to call a function that is undefined,
+etc.
+
+## Main and Undefined
+
+If the value `undefined` is returned from the top-level `main` rule, then
+the policy is considered `false`. However, the runtime provides mechanisms
+for the host application to determine the policy failure was caused by
+an undefined value and report that to the user.
+
+The `main` rule returning the undefined value should be considered a logical
+error in most cases and should probably be corrected by the policy writer.
+
+## Debugging Undefined
+
+When an `undefined` value is first created (either explicitly or implicitly),
+the runtime will record the position where the undefined was created. This
+location can be used to find logic that could be erroneous.
+
+## Boolean Logic and Undefined
+
+Boolean logic is one way to safely recover from `undefined`. If boolean
+logic can safely determine a result despite an undefined value, that result
+will be returned.
+
+For example: `undefined or true` will result in `true`, because no matter
+what actual value `undefined` could've been if the policy behaved properly,
+the boolean expression would still be true.
+
+Another scenario where `undefined` can be safely ignored is short-circuit
+logic with `and`. `false and undefined` will result in `false`.
+
+The full table of logical operations on `undefined` is below:
+
+```sentinel
+undefined or true = true
+undefined or false = undefined
+undefined or undefined = undefined
+undefined and true = undefined
+undefined and false = undefined
+undefined and undefined = undefined
+undefined xor true = undefined
+undefined xor false = undefined
+undefined xor undefined = undefined
+
+// Short-circuit examples
+false or true or undefined = true
+false or undefined or true = true
+true and false and undefined = false
+true and undefined and false = undefined
+```
+
+## Else Expressions
+
+The `else` expression allows explicit recovery from undefined and is useful
+for setting default values.
+
+`else` is an operator where the left and right hand side are values. If the
+left-hand value is `undefined`, the right-hand value is returned. Otherwise,
+the left-hand value is returned.
+
+Examples:
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else "default"
+
+// In more complex scenarios
+
+foo() else 42 == 12
+a = config.value else "default"
+```
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/language/values.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/language/values.mdx
new file mode 100644
index 0000000000..d2969549b9
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/language/values.mdx
@@ -0,0 +1,195 @@
+---
+page_title: Sentinel Language - Values
+sidebar_title: Values
+sidebar_current: docs-language-values
+description: Values are the data that Sentinel policies operate on.
+layout: docs
+---
+
+# Language: Values
+
+Values are the _data_ that Sentinel policies operate on. You can create this
+data yourself, for example by just typing the number `42`, or you may access
+this data from an external source to make policy decisions.
+
+Values have a _type_, which determines what kind of operatins can be performed
+on the data. Example types are booleans (true and false), numbers,
+and strings (text).
+
+This page documents all the available value types. You can also
+[convert between types](#type-conversion).
+
+## Boolean
+
+A boolean is a value that is either true or false.
+
+A boolean value is created literally with `true` or `false`.
+
+As a policy language, booleans are central to the behavior of Sentinel.
+Booleans are used as conditions in [if statements](/sentinel/language/conditionals),
+are the result of [rules](/sentinel/language/rules), and more. The ultimate
+result of a Sentinel policy is true or false.
+
+## Integer
+
+An integer is a whole number.
+
+An integer is a 64-bit value. This means it can represent numbers from
+-9,223,372,036,854,775,808 to 9,223,372,036,854,775,808.
+
+Integers are created by typing them out literally with no
+separators (such as a comma). An optional prefix can set a non-decimal base:
+`0` for octal, and `0x` for hexadecimal. In hexadecimal literals, letters
+`a-f` and `A-F` represent values 10 to 15.
+
+Example integers are shown below:
+
+```sentinel
+42
+0600
+0xBadFace
+170141183460469
+```
+
+Integers are used for math and numerical comparison.
+
+## Float
+
+A float is a number with a decimal point. It has an integer part, a decimal
+point, a fractional part, and optionally an exponent part. Example
+floats are shown below:
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+Floats are IEEE-754 64-bit floating point numbers.
+
+## String
+
+Strings are text values.
+
+Strings are created by wrapping text in double quotes, such as `"hello"`.
+Within the quotes, any character may appear except newline and an unescaped
+double quote. The text between the quotes is the value.
+
+Because Sentinel policies must be UTF-8 encoded text, strings themselves are
+UTF-8 encoded text. This means you can put any value UTF-8 character into
+a string, such as `"日本語"`.
+
+You can have a string with a literal double-quote by _escaping_ it with
+a backslash. For example: `"they said \"hello\""` turns into the value
+`they said "hello"`.
+
+Backslash escapes can be used for much more than only escaping a double quote.
+Newlines are `\n`, a literal backslash is `\\`, and many more. The full list
+of available backslash escapes can be found
+[in the language specification](/sentinel/language/spec#string-literals).
+
+Example strings:
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+Strings support indexing. Indexing a string will access that _byte_
+in the string as if the string were a byte array. This is an important
+distinction: it _will not_ access the character at that position if you're
+using multi-byte characters.
+
+Strings support [slicing](/sentinel/language/slices) to efficiently create
+substrings.
+
+## List
+
+See [Lists](/sentinel/language/lists).
+
+## Map
+
+See [Maps](/sentinel/language/maps).
+
+## Rule
+
+See [Rules](/sentinel/language/rules).
+
+## Function
+
+See [Functions](/sentinel/language/functions).
+
+## Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a
+value of that type. Some examples are shown below followed by a list of exact
+rules for type conversion.
+
+```sentinel
+int(42) // 42
+int("42") // 42
+int(42.8) // 42
+int(true) // 1
+
+float(1.2) // 1.2
+float(1) // 1.0
+float("4.2") // 4.2
+float(true) // 1.0
+
+string("foo") // "foo"
+string(88) // "88"
+string(0xF) // "15"
+string(true) // "true"
+
+bool("true") // true
+bool(1) // true
+bool(-1) // true
+bool(0.1) // true
+bool("false") // false
+bool(0) // false
+```
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/language/variables.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/language/variables.mdx
new file mode 100644
index 0000000000..69655b0c63
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/language/variables.mdx
@@ -0,0 +1,100 @@
+---
+page_title: Sentinel Language - Variables
+sidebar_title: Variables
+sidebar_current: docs-language-vars
+description: Variables store values that can be used later.
+layout: docs
+---
+
+# Language: Variables
+
+Variables store values that can be used later.
+
+Most notably, a variable assignment of `main` is required for all
+Sentinel policies. This is the main [rule](/sentinel/language/rules) that
+is executed to determine the result of a policy. The `main` rule may
+use other variables that are assigned in the policy.
+
+Example:
+
+```sentinel
+a = 1
+b = a + 1
+```
+
+In this example, two variables are assigned: `a` and `b`. `a` is given
+the value of "1" and `b` uses the `a` to calculate its own value.
+
+## Syntax
+
+The syntax for assigning a variable is:
+
+```sentinel
+IDENTIFIER = VALUE
+```
+
+On the left of the equal sign is an _identifier_. This is a name for your
+variable and will be how you reference it later. An identifier is any
+combination of letters and digits, but must start with a letter. An
+underscore `_` is also a valid letter.
+
+On the right is any valid Sentinel value or expression. A value is a
+literal value such as a number or string. An expression is some computed
+value such as doing math, calling a function, etc.
+
+## Assignment and Reassignment
+
+A variable is _assigned_ when it is given a value. You can also _reassign_
+variables at any time by setting it to a new value. The new value takes
+effect for any subsequent use of that variable. A variable can be reassigned
+to a different type.
+
+For example:
+
+```sentinel
+a = 1 // a = 1
+b = a // b = 1
+a = "value" // a = "value", b = 1
+c = a // c = "value", b = 1
+```
+
+In the above example you can see that the variables are set and reassigned.
+Notice that the value of a variable is the _current_ value, and that reassigning
+a variable only affects _future_ uses of that variable. You can see this with
+`c` and `b` being different values.
+
+## Unassigned Variables
+
+Using a variable that is _unassigned_ is an error.
+
+In the example below, the first line would result in the policy erroring.
+Sentinel is executed top-down and the value of `c` is not available yet
+on the first line.
+
+```sentinel
+a = c // Error!
+c = 1
+```
+
+## Type Conversion
+
+While a topic more related to [values][lang-values], variables can be assigned
+(or-reassigned) a different value type via type conversion.
+
+[lang-values]: /sentinel/language/values
+
+```sentinel
+s = "1.1"
+a = int(s) // a = 1
+a = float(s) // a = 1.1
+
+a = 1
+s = string(a) // s = "1"
+```
+
+For more details, see the type conversion sections in the
+[values][lang-values-type-conversion] page, or the [Sentinel Language
+Specification][lang-spec-type-conversion].
+
+[lang-values-type-conversion]: /sentinel/language/values#type-conversion
+[lang-spec-type-conversion]: /sentinel/language/spec#type-conversion
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/nomad.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/nomad.mdx
new file mode 100644
index 0000000000..b519c1bf29
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/nomad.mdx
@@ -0,0 +1,50 @@
+---
+page_title: Nomad and Sentinel
+sidebar_title: Nomad
+sidebar_current: docs-app-nomad
+description: >-
+ Nomad Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies can currently execute on job
+ submission (creation, update).
+layout: docs
+---
+
+# Nomad
+
+[Nomad Enterprise](https://www.hashicorp.com/products/nomad/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies can currently execute on job submission (creation, update).
+
+Sentinel policies have full access to the job structure. This allows the
+Sentinel policy to control behavior based on any attribute within a job,
+such as the driver, resource requests, network configuration, volume
+configuration, and more. The information that Sentinel policies have access
+to will expand over time.
+
+Nomad fully supports all [enforcement levels](/sentinel/concepts/enforcement-levels).
+For soft mandatory policies, the `sentinel-override` capability must be
+available on the user's ACL policy to allow override. Overrides are always
+logged.
+
+The Nomad integration with Sentinel is documented in depth in the
+[Nomad Enterprise documentation](https://www.nomadproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Only allow Docker-based jobs.**
+
+```sentinel
+# Test policy only allows Docker based tasks
+main = rule { all_drivers_docker }
+
+# all_drivers_docker checks that all the drivers in use are Docker
+all_drivers_docker = rule {
+ all job.task_groups as tg {
+ all tg.tasks as task {
+ task.driver is "docker"
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/terraform.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/terraform.mdx
new file mode 100644
index 0000000000..4825ad24fe
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/terraform.mdx
@@ -0,0 +1,60 @@
+---
+page_title: Terraform and Sentinel
+sidebar_title: Terraform
+sidebar_current: docs-app-terraform
+description: >-
+ Sentinel can be used with Terraform and Terraform Enterprise to enforce policy
+ on Terraform configurations, states, and plans.
+layout: docs
+---
+
+# Terraform
+
+[Terraform Enterprise](https://www.hashicorp.com/products/terraform/) uses Sentinel to enforce
+policy on [Terraform](https://www.terraform.io) configurations, states, and plans.
+
+The Sentinel integration with Terraform runs within
+[Terraform Enterprise](https://www.hashicorp.com/products/terraform/)
+after a `terraform plan` and before a `terraform apply`. The policies
+have access to the created plan, the state at the time of the plan,
+and the configuration at the time of the plan.
+
+The Terraform integration with Sentinel is documented in depth in the [Terraform Enterprise documentation](https://www.terraform.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show basic examples.
+
+### Examples
+
+**Example: All AWS instances must have a tag**
+
+```sentinel
+import "tfplan"
+
+main = rule {
+ all tfplan.resources.aws_instance as _, instances {
+ all instances as _, r {
+ (length(r.applied.tags) else 0) > 0
+ }
+ }
+}
+```
+
+**Example: Only allow GCP instance sizes smaller than `n1-standard-16`**
+
+```sentinel
+import "tfplan"
+
+allowed_machine_types = [
+ "n1-standard-1",
+ "n1-standard-2",
+ "n1-standard-4",
+ "n1-standard-8",
+]
+
+main = rule {
+ all tfplan.resources.google_compute_instance as _, instances {
+ all instances as _, r {
+ r.applied.machine_type in allowed_machine_types
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/vault.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/vault.mdx
new file mode 100644
index 0000000000..9c37e0eedb
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/vault.mdx
@@ -0,0 +1,65 @@
+---
+page_title: Vault and Sentinel
+sidebar_title: Vault
+sidebar_current: docs-app-vault
+description: >-
+ Vault Enterprise uses Sentinel to augment the built-in policy system to
+ provide Role Governing Policies (RGPs) and Endpoint Governing Policies (EGPs)
+ to enable complex, flexible policies across identities and endpoints.
+layout: docs
+---
+
+# Vault
+
+[Vault Enterprise](https://www.hashicorp.com/products/vault/) uses Sentinel
+to augment the built-in policy system to provide Role Governing Policies (RGPs)
+and Endpoint Governing Policies (EGPs) to enable complex, flexible policies
+across identities and endpoints.
+
+**Role Governing Policies (RGPs)** are Sentinel policies that are tied to
+particular tokens, Identity entities, or Identity groups. They have access to
+a rich set of controls across various aspects of Vault. These are evaluated
+whenever a token they're attached to is used.
+
+**Endpoint Governing Policies (EGPs)** are Sentinel policies that are tied to
+particular paths instead of tokens. They have access to as much request
+information as possible, but they can take effect even on unauthenticated
+paths, such as login paths.
+
+The Vault integration with Sentinel is documented in depth in the
+[Vault Enterprise documentation](https://www.vaultproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Endpoint policy that requires MFA authentication from a corporate network.**
+
+```sentinel
+import "sockaddr"
+
+# We expect logins to come only from our private IP range
+cidrcheck = rule {
+ sockaddr.is_contained(request.connection.remote_addr, "10.20.0.0/16")
+}
+
+# Require Ping MFA validation to succeed
+ping_valid = rule {
+ mfa.methods.ping.valid
+}
+
+main = rule when request.path is "auth/ldap/login" {
+ ping_valid and cidrcheck
+}
+```
+
+**Example: Endpoint policy that disallows tokens created before a certain time.**
+
+```sentinel
+import "time"
+import "strings"
+
+main = rule when not strings.has_prefix(request.path, "auth/ldap/login") {
+ time.load(token.creation_time).unix > time.load("2017-09-17T13:25:29Z").unix
+}
+```
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/writing/basic.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/writing/basic.mdx
new file mode 100644
index 0000000000..1a881476a4
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/writing/basic.mdx
@@ -0,0 +1,107 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Basics
+sidebar_current: docs-writing-basic
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Policy Basics
+
+Sentinel policies are easy to write while still supporting advanced constructs
+for creating complex policies. This page will explain the basics of writing
+Sentinel policies to get started. You don't need any prior Sentinel knowledge,
+but we do recommend reading the
+[getting started guide](/sentinel/intro/getting-started/first-policy) and
+[language guide](/sentinel/language) after this.
+
+Sentinel policies are text files written using the [Sentinel language](/sentinel/language).
+The policies are evaluated top-to-bottom. The value of `main` after execution
+determines whether a policy passes or fails.
+
+## The Simplest Policy
+
+Sentinel only requires that a policy have a `main` variable that evaluates to
+a boolean value.
+
+A valid example is shown below:
+
+```sentinel
+main = 10 > 5
+```
+
+This type of minimal policy is not purely academic. In practice, simple
+policies can often be reduced to a single line logical statement resulting
+in `true` or `false`. However, the expression is usually wrapped in a
+[rule](/sentinel/language/rules) for testing reasons.
+
+You can verify Sentinel will execute this minimal policy using the CLI:
+
+```
+$ sentinel apply minimal.sentinel
+Pass
+```
+
+## Logical Expressions
+
+Policy is at its core a set of logic: you can or can not perform some action
+under a certain set of circumstances. Those circumstances are logical
+expressions. Therefore, Sentinel policies primarily translate into logical
+expressions.
+
+Detailed documentation on boolean expressions is
+[available in the language guide](/sentinel/language/boolexpr).
+
+A simple numerical comparison was seen in the first example on this page.
+Sentinel also provides inclusion operators such as `contains`, `any`, `all`, and
+more. Sentinel allows some operators to have aliases to promote readability
+while remaining programmer-familiar, such as `==` which can equivalently be `is`.
+
+The example below verifies that all numbers in a list are even:
+
+```sentinel
+numbers = [6, 22, 8, 4, 12]
+main = all numbers as n { n % 2 is 0 }
+```
+
+## Variables
+
+A policy will very often use variables. Applications such as
+[Nomad](https://www.nomadproject.io) inject variables into the global
+scope of a policy for making policy decisions. For example, Nomad injects
+the job that is being run into the policy scope. Knowing how to use
+variables is critical to effectively using Sentinel.
+
+Detailed documentation on how to define and access variables is
+[available in the language guide](/sentinel/language/variables).
+
+Variables can be defined and used explicitly. For example:
+
+```sentinel
+value = 10
+main = value > 5
+```
+
+But they may also be introduced implictly by the host system. Nomad
+injects `job` into policies to describe the job that is being run. The
+policy below is a valid policy that requires a job have two task groups.
+Notice that `job` is not defined anywhere. It is implicitly inserted by
+the host application (in this case Nomad). Refer to the application you're
+writing policy for to determine if it implicitly inserts values.
+
+```sentinel
+main = length(job.task_groups) is 2
+```
+
+## And more!
+
+The Sentinel language supports many more features such as functions,
+loops, and more. You can learn about all of this in the
+[complete language guide](/sentinel/language).
+
+The other pages in the [writing policy](/sentinel/writing)
+will cover other information you need to know about writing Sentinel
+policies that isn't simply a language reference.
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/writing/debugging.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/writing/debugging.mdx
new file mode 100644
index 0000000000..7311de7be3
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/writing/debugging.mdx
@@ -0,0 +1,46 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Debugging
+sidebar_current: docs-writing-debugging
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Debugging
+
+As policies become more complex, it becomes harder to understand exactly why
+a policy may be behaving differently than you expect.
+
+Prior to actively debugging, there are multiple best practices we recommend
+following. We recommend breaking your policy down into a set of
+[rules](/sentinel/writing/rules). This will make it easier to understand
+[traces](/sentinel/writing/tracing) and make it easier to
+[test](/sentinel/writing/testing) your policies.
+This should help to debug policies up to a significant complexity.
+
+## Print Logging
+
+The built-in [print function](/sentinel/language/logging-errors#print)
+can be used to log data. The print output is only shown during failures
+or when tracing is explicitly enabled.
+
+The print function is [variadic](https://en.wikipedia.org/wiki/Variadic_function).
+Each argument specifies a value to print together, concatenated with a space
+in between each. You can put any type as an argument and Sentinel will
+convert that to a human-friendly string format.
+
+Example:
+
+```sentinel
+value = 42
+print("the value is", value) // the value is 42
+
+map = { "foo": false }
+print(map) // { "foo": false }
+```
+
+The `print` function returns the value `true` so that it can also be
+used within rule expressions. Note that if short-circuiting is occuring
+within the boolean logic, the `print` function may never be reached.
+The runtime contains a special case where `print` will not short-circuit
+its own logic, so `print() or expr` will always evaluate `expr`.
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/writing/imports.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/writing/imports.mdx
new file mode 100644
index 0000000000..1bb8763c32
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/writing/imports.mdx
@@ -0,0 +1,124 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Imports
+sidebar_current: docs-writing-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom import](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only local
+context for making policy decisions.
+
+Sentinel also comes with a set of [standard imports](/sentinel/imports).
+Standard imports are available to every Sentinel policy to help policy writers
+with common tasks such as working with the time, network addresses, and more.
+
+-> **This page is about writing policies that _use_ imports.** If you're
+interested in _creating_ a new import, please see the section on [extending
+Sentinel](/sentinel/extending) for information on how to write modules and
+import plugins.
+
+## Using Imports
+
+To use an import, you use the `import` keyword at the top of your policy. This
+specifies the name of the import you want to use. The application you're writing
+the policy for must already be
+[configured](/sentinel/extending/plugins#installing-plugins) to provide that
+import.
+
+Details on imports can be found in the
+[import section in the language reference](/sentinel/language/imports).
+
+In the example below, we use the [time](/sentinel/imports/time) import:
+
+```sentinel
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+There are two options to develop a policy locally when using imports:
+configure Sentinel to launch the import on `apply`, or mock the import
+using `mock`. The former requires access to the import plugin while the
+latter is faster (doesn't have to launch a process) and doesn't require
+the plugin.
+
+## Mocking Imports
+
+The first option to developing policies locally is to mock the import values.
+When mocking an import, you don't need the import plugin available. This can be
+useful since some imports may not be available as a plugin and may only be
+available to the application the policy runs in.
+
+Mocks are specified via the [configuration
+file](/sentinel/configuration). Mocks can also be used for
+[testing](/sentinel/writing/testing).
+
+You can supply mock configuration one of two ways, depending on your use case:
+
+- **[Using static data](/sentinel/configuration#mocking-static-data):**
+ Use this method when you can accurately represent your mock data in JSON and
+ do not need to mock complex Sentinel features such as functions.
+- **[Using Sentinel code](/sentinel/configuration#mocking-with-sentinel-code):**
+ Use this method when using a static JSON object is insufficient, such as when
+ you need to mock functions or other complex Sentinel features.
+
+Our example does not require complex data to be mocked, so a static JSON object
+is sufficient:
+
+```json
+{
+ "mock": {
+ "time": {
+ "now": {
+ "hour": 12,
+ "weekday_name": "Tuesday"
+ }
+ }
+ }
+}
+```
+
+This can be used via the CLI:
+
+```
+$ sentinel apply -config=config.json policy.sentinel
+Pass
+```
+
+## Launching Imports
+
+If you have access to the plugin binary, you can launch the import. The
+benefit of this is that it is really using the import to test your
+policy. If the import changes, your policies may start failing. If you
+only use mock data and the import changes, your policies will still
+appear to work.
+
+Imports are configured in the configuration file:
+
+```json
+{
+ "imports": {
+ "time": {
+ "path": "/path/to/sentinel-time-import"
+ }
+ }
+}
+```
+
+This would require the `sentinel-time-import` binary. For this example
+this doesn't currently exist. We plan on writing one to provide for this
+section of the documentation.
+
+## Custom Imports
+
+You can also [create your own imports](/sentinel/extending).
+
+If your policy decisions could benefit from accessing external information,
+then you can use custom imports as a way to do this.
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/writing/index.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/writing/index.mdx
new file mode 100644
index 0000000000..e7743c0011
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/writing/index.mdx
@@ -0,0 +1,36 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Writing Policy
+sidebar_current: docs-writing
+description: >-
+ Sentinel provides a language and workflow for building policy across any
+ system that embeds Sentinel. By learning Sentinel once, you are able to
+ effectively control access to many systems using Sentinel's powerful features.
+ Basic concepts that are important to understand for Vault usage.
+layout: docs
+---
+
+# Writing Sentinel Policy
+
+This section covers how to write Sentinel policies.
+
+It is recommended that you complete the
+[getting started guide](/sentinel/intro/getting-started/first-policy) prior to
+reading this section of the documentation. The getting started guide will
+explain all the basics of writing and testing policies. This section of the
+documentation will serve as more of a reference guide to all available
+features of Sentinel.
+
+Sentinel provides a language and workflow for building policy across any system
+that embeds Sentinel. By learning Sentinel once, you are able to effectively
+control access to many systems using Sentinel's powerful features.
+
+Sentinel also provides a [local CLI](/sentinel/commands) for developing and testing
+Sentinel policies. This CLI can be integrated into a daily developer's workflow
+along with CI to treat [policy as code](/sentinel/concepts/policy-as-code).
+
+Sentinel uses its own [language](/sentinel/language) for writing
+policies. You can view a [language reference](/sentinel/language)
+as well as the [specification](/sentinel/language/spec) for details. You
+don't have to read those documents immediately, since the language should
+be easy enough to pick up throughout this section.
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/writing/rules.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/writing/rules.mdx
new file mode 100644
index 0000000000..b4239afb5b
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/writing/rules.mdx
@@ -0,0 +1,49 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Rules
+sidebar_current: docs-writing-rules
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Rules
+
+Rules form the basis of a policy by representing behavior that is either
+passing or failing (true or false). Rules are a first class language construct
+in Sentinel. A policy can and should be broken down into rules to aid with
+readability, testability, and performance.
+
+Rules provide:
+
+- **Readability:** A policy that is broken down into rules can be read
+ more easily. The logic becomes clearer to see for policy writers when
+ they come back to it.
+
+- **Debuggability:** When [tracing](/sentinel/writing/tracing) is enabled,
+ the trace is formatted by rule names. A policy that is broken down into
+ more rules is more easily debugging by noticing unexpected rule values.
+
+- **Testability:** [Policy testing](/sentinel/writing/testing) is
+ built on asserting the values of rules. By breaking logic down into
+ rules, you're able to more effectively test your policies.
+
+- **Performance:** As explained in the next section, rules are only
+ evaluated once on demand. This means that a rule referenced multiple
+ time only has a one-time performance cost. For complex logic, this
+ could result in improved performance.
+
+An example usage of rules is shown below:
+
+```sentinel
+is_sunny = rule { weather is "sunny" }
+is_wednesday = rule { day is "wednesday" }
+main = rule { is_sunny and is_wednesday }
+```
+
+Details about rules and their behavior can be found in
+[the language reference](/sentinel/language/rules). Rules have important
+behavior so we recommend reading the language reference on rules.
+Rules will be used throughout the remainder of the documentation.
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/writing/testing.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/writing/testing.mdx
new file mode 100644
index 0000000000..71b37522ec
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/writing/testing.mdx
@@ -0,0 +1,340 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Testing
+sidebar_current: docs-writing-testing
+description: >-
+ Sentinel has a built-in test framework to validate a policy behaves as
+ expected.
+layout: docs
+---
+
+# Testing
+
+Sentinel has a built-in test framework to validate a policy behaves as expected.
+
+With an ever-increasing amount of automation surrounding technology,
+the guardrails provided by policies are a critical piece towards ensuring
+expected behavior. As a reliance on correct policy increases, it is important
+to test and verify policies.
+
+Testing is a necessary step to fully realize
+[policy as code](/sentinel/concepts/policy-as-code). Just as good software
+is well tested, a good set of policies should be equally well tested.
+
+## Test Folder Structure
+
+Policies are tested by asserting that [rules](/sentinel/writing/rules)
+are the expected values given a pre-configured input. Tests are run
+by executing the [test command](/sentinel/commands/test).
+
+Sentinel is opinionated about the folder structure required for tests.
+This opinionated structure allows testing to be as simple as running
+`sentinel test` with no arguments. Additionally, it becomes simple to test
+in a CI or add new policies.
+
+The structure Sentinel expects is `test//*.json` where ``
+is the name of your policy file without the file extension. Within
+that folder is a list of JSON files. Each JSON file represents a single
+test case. Therefore, each policy can have multiple tests associated with
+it.
+
+## Test Case Format
+
+Each JSON file within the test folder for a policy is a single test case.
+
+The JSON file is the same configuration format as the [CLI configuration
+file](/sentinel/configuration). The format lets you define mock data, imports
+to use, and more. This mock data is the key piece in being able to test
+policies: you craft a specific scenario and assert your policy behaves as you
+expect.
+
+Test cases also use the special key `test` within the configuration file
+to assert the boolean value of [rules](/sentinel/writing/rules). If
+the `test` key is omitted, the policy is expected to pass (the same
+as asserting that the `main` rule is true). If the test key is specified,
+only the rules specified in the map will be asserted. This means if you
+omit `main`, then the final policy result is not asserted.
+
+Example with assertions:
+
+```json
+{
+ "param": {
+ "day": "monday",
+ "hour": 7
+ },
+
+ "test": {
+ "main": false,
+ "is_open_hours": false,
+ "is_weekday": true
+ }
+}
+```
+
+The configuration above specifies some parameter data, and asserts the result of
+some rules. This is the same configuration used in the example section below.
+
+## Example
+
+Lets use the following file as an example. Save this file to a directory
+and name it `policy.sentinel`. It can be named anything with the `sentinel`
+extension, but by naming it `policy.sentinel` your output should match
+the example output on this page.
+
+```sentinel
+// The day of the week.
+param day
+
+// The hour of the day.
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+### A Passing Test
+
+Next, let's define a single test case. Relative to where you saved
+the policy, create a file at the path `test/policy/good.json`.
+
+```json
+{
+ "param": {
+ "day": "monday",
+ "hour": 14
+ }
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/good.json
+```
+
+This passed because the policy passed. We didn't assert any specific
+rules. By not specifying any assertions, `test` expects the policy itself
+to fully pass.
+
+### A Failing Test
+
+Define another test case to fail. We want to verify our policy fails when expected, too.
+
+Save the following as `test/policy/7-am.json`:
+
+```json
+{
+ "param": {
+ "day": "monday",
+ "hour": 7
+ }
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+FAIL - policy.sentinel
+ FAIL - test/policy/7-am.json
+ expected "main" to be true, got: false
+
+ trace:
+ FALSE - policy.sentinel:9:1 - Rule "main"
+ FALSE - policy.sentinel:9:15 - is_open_hours
+ FALSE - policy.sentinel:8:24 - hour > 8 and hour < 17
+ FALSE - policy.sentinel:8:24 - hour > 8
+
+ FALSE - policy.sentinel:8:1 - Rule "is_open_hours"
+ FALSE - policy.sentinel:8:24 - hour > 8
+ PASS - test/policy/good.json
+```
+
+As you can see, the test fails because "main" is false. This is good
+because the policy _should_ have failed since we specified an invalid
+hour. But, we expect main to be false and don't want our test to fail!
+Update `7-am.json` to add test assertions:
+
+```json
+{
+ "param": {
+ "day": "monday",
+ "hour": 7
+ },
+
+ "test": {
+ "main": false,
+ "is_open_hours": false,
+ "is_weekday": true
+ }
+}
+```
+
+And when we run the tests:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/7-am.json
+ PASS - test/policy/good.json
+```
+
+The test passes. We asserted that we expect the `main` rule to be false,
+the `is_open_hours` rule to be false, and the `is_weekday` rule to be
+true. By asserting some rules are true, we can verify that our policy
+is failing for reasons we expect.
+
+## Mocking
+
+The above example demonstrates how to test by supplying different
+[parameters](/sentinel/language/parameters). Parameters in a policy can be
+specifically useful when you want to control user-defined input values to a
+policy.
+
+However, generally, when testing, you will need mimic the conditions you will
+see in production. Production implementations of Sentinel will supply data using
+one of two methods:
+
+- **Global data:** Data is injected directly into the policy's scope and is
+ accessible using normal identifiers, similar to variables.
+- **Imports:** Data is stored behind an [import](/sentinel/language/imports)
+ and loaded on demand as needed by the policy author.
+
+Proper testing of a policy requires that these values be able to be _mocked_ -
+or, in other words, simulated in a way that allows the accurate testing of the
+scenarios that a policy could reasonably pass or fail under.
+
+Mocking both globals and imports can be done by setting various parts of the
+configuration file.
+
+### Mocking Globals
+
+Demonstrating the mocking of globals can be seen by making a few modifications to
+our example policy, removing the `param` declarations:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Then, change the `param` section in the configuration file to
+[`global`](/sentinel/configuration#global).
+
+```json
+{
+ "global": {
+ "day": "monday",
+ "hour": 14
+ }
+}
+```
+
+This test should still pass, as if nothing had happened, although what we've
+done is shifted our parameters to globals, simulating an environment where `day`
+and `hour` are already defined for us.
+
+### Mocking Imports
+
+To mock imports, we need to use the
+[`mock`](/sentinel/configuration#mock-imports) section of the
+configuration file.
+
+Let's say the above example is behind an import named `time`.
+
+-> **NOTE:** [`time`](/sentinel/imports/time) is a valid standard import.
+This example may not be accurate to the
+import's syntax.
+
+The code now looks like this:
+
+```sentinel
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To mock this import, we can [mock it as static
+data](/sentinel/configuration#mocking-static-data). The configuration
+file now looks like, without assertions:
+
+```json
+{
+ "mock": {
+ "time": {
+ "now": {
+ "weekday_name": "Monday",
+ "hour": 14
+ }
+ }
+ }
+}
+```
+
+The policy will now pass, with the `time` import mocked.
+
+Data can also be [mocked as Sentinel
+code](/sentinel/configuration#mocking-with-sentinel-code). In this case,
+the above configuration file would look like:
+
+```json
+{
+ "mock": {
+ "time": "mock-time.sentinel"
+ }
+}
+```
+
+And a file named `mock-time.sentinel` would now hold your mock values:
+
+```sentinel
+day = "monday"
+hour = 14
+```
+
+Mocking as Sentinel code allows more complex details to be mocked as well, such
+as functions. Say we wanted to mock the `time.load()` function. To mock this,
+just add it to the `mock-time.sentinel` file:
+
+```sentinel
+load = func(_) {
+ return {
+ "weekday_name": "Monday",
+ "hour": 14,
+ }
+}
+```
+
+Your code can now be written as:
+
+```sentinel
+import "time"
+
+t = time.load("a_mock_timestamp")
+
+is_weekday = rule { t.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { t.hour > 8 and t.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To see more details, see the [Mock
+Imports](/sentinel/configuration#mock-imports) section in the
+configuration file.
+
+## Test Automation
+
+The `sentinel test` command was designed to be automation friendly.
+We encourage you to enable a CI such as [Travis CI](https://travis-ci.com/)
+on your policy repositories to continuously run tests. An example
+`.travis.yml` configuration file is shown below:
+
+```yaml
+language: bash
+script: sentinel test
+```
diff --git a/content/sentinel/v0.16.x/content/sentinel/docs/writing/tracing.mdx b/content/sentinel/v0.16.x/content/sentinel/docs/writing/tracing.mdx
new file mode 100644
index 0000000000..c7261a1e5c
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/docs/writing/tracing.mdx
@@ -0,0 +1,124 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Traces
+sidebar_current: docs-writing-tracing
+description: >-
+ Sentinel provides execution traces to better understand the execution of a
+ policy.
+layout: docs
+---
+
+# Traces
+
+Sentinel provides execution traces to better understand the execution of a policy.
+
+When using the Sentinel [CLI](/sentinel/commands), traces are shown
+on policy failure during `apply` or `test`, or when the `-trace` flag is
+explicitly specified. For Sentinel-enabled applications, traces are optional
+and may require special configuration to enable. Most Sentinel-enabled
+applications log traces on failure.
+
+-> **Note:** We're actively improving Sentinel tracing in each release
+to provide better data and improve readability. The exact format of a
+trace may not match the Sentinel version embedded in the application you're
+using, but the data should be similar.
+
+## Analyzing a Trace
+
+To show traces, let's use the example policy below with the CLI:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Let's cause the policy to fail so the trace is more interesting.
+If you're on a terminal that supports it, the trace output will be colored
+so you can more clearly see passes, failures, and rules.
+
+```
+$ sentinel apply -global 'day=sunday' -global 'hour=14' policy.sentinel
+Fail
+
+FALSE - policy.sentinel:6:1 - Rule "main"
+ TRUE - policy.sentinel:6:15 - is_open_hours
+ TRUE - policy.sentinel:5:24 - hour > 8 and hour < 17
+ TRUE - policy.sentinel:5:24 - hour > 8
+ TRUE - policy.sentinel:5:37 - hour < 17
+ FALSE - policy.sentinel:6:33 - is_weekday
+ FALSE - policy.sentinel:4:21 - day not in ["saturday", "sunday"]
+
+TRUE - policy.sentinel:5:1 - Rule "is_open_hours"
+ TRUE - policy.sentinel:5:24 - hour > 8
+ TRUE - policy.sentinel:5:37 - hour < 17
+
+FALSE - policy.sentinel:4:1 - Rule "is_weekday"
+```
+
+The least-nested values are all rules. It begins with the
+value of that rule (`TRUE` or `FALSE`) followed by the location and name
+of the rule. Notice that the `main` rule is false, the `is_open_hours`
+rule is true, and the `is_weekday` rule is false.
+
+We know that main requires _both_ `is_open_hours` and `is_weekday` to be
+true, so based on the trace, we can tell that main failed because
+`is_weekday` was false. We can then go to the definition of `is_weekday`
+in the source and determine the logical failure.
+
+The trace also breaks down more complex logical expressions. Notice that
+`is_open_hours` is split into the two sides of the `and`, but `is_weekday`
+has no values under it. Because `is_weekday` is itself a single boolean
+expression, the failure isn't repeated. But for `is_open_hours`, you can also
+see the result of each side of the boolean expression.
+
+## Missing Rules or Expressions
+
+The Sentinel runtime sometimes optimizes away executions completely. This
+can result in a trace being incomplete. If you don't see a rule or boolean
+expression within the trace, it means the runtime didn't execute it at all.
+
+Consider the example policy:
+
+```sentinel
+main = rule { 42 > 40 or 10 < 5 }
+```
+
+If we run it with trace:
+
+```
+$ sentinel apply -trace policy.sentinel
+TRUE - policy.sentinel:1:1 - Rule "main"
+ TRUE - policy.sentinel:1:15 - 42 > 40
+```
+
+Notice that the expression `10 < 5` is not present in the trace.
+Since `42 > 40` is true, the entire `or` can be short-circuited and
+the result is known to be true. Remaining parts of the expression are not
+executed and are therefore not present in the trace.
+
+## Policy Description
+
+An additional feature that can be utilised within a trace is the output of
+a policy description. By placing a comment at the top of a policy file,
+ensuring that there is a space between itself and any following content,
+a description will be added to the trace output.
+
+The following policy:
+
+```sentinel
+// Ensure 42 is greater than 40, or 10 is less than 5
+
+main = rule { 42 > 40 or 10 < 5 }
+```
+
+Will output:
+
+```
+$ sentinel apply -trace policy.sentinel
+Description:
+ Ensure 42 is greater than 40, or 10 is less than 5
+
+TRUE - policy.sentinel:1:1 - Rule "main"
+ TRUE - policy.sentinel:1:15 - 42 > 40
+```
diff --git a/content/sentinel/v0.16.x/content/sentinel/intro/getting-started/first-policy.mdx b/content/sentinel/v0.16.x/content/sentinel/intro/getting-started/first-policy.mdx
new file mode 100644
index 0000000000..e0cc4cef50
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/intro/getting-started/first-policy.mdx
@@ -0,0 +1,54 @@
+---
+page_title: Your First Sentinel Policy
+sidebar_title: Your First Policy
+sidebar_current: intro-getting-started-first
+description: Let's begin by writing a working Sentinel policy.
+layout: intro
+---
+
+# Your First Sentinel Policy
+
+Sentinel is a system to enforce complex policies on an integrated application.
+
+Writing Sentinel policy requires minimal programming experience. The Sentinel
+language is designed to be approachable and learned quickly and easily. Whether
+you're a professional programmer or someone who uses SQL and Excel, you can
+learn to write Sentinel policies.
+
+Let's begin by writing a simple, working Sentinel policy:
+
+```sentinel
+hour = 4
+main = rule { hour >= 0 and hour < 12 }
+```
+
+This is a valid Sentinel policy. It will pass since we hardcoded the
+`hour` to be 4. In a real system, `hour` may be something that is provided
+to us and actually set to the current hour. We'll learn more about that later.
+
+For now, try running this policy locally. Save the above example to a file
+named `policy.sentinel` and execute it. Then, modify the policy to make it
+fail. Play around more if you'd like.
+
+```
+$ sentinel apply policy.sentinel
+Pass
+```
+
+## Main
+
+Every Sentinel policy must have a `main` rule. This is the rule that
+is evaluated to determine the result of a policy. A rule describes a
+condition that must be true for the rule to be true. Within the rule,
+a single boolean expression describes the condition. In the example above,
+it is a straightforward check that the time is greater than zero (midnight)
+and less than 12.
+
+It is easy to imagine that such a rule might be used in a system such
+as [Nomad](https://www.nomadproject.io) to restrict the times when a
+deploy can occur. The power of arbitrary logical statements within Sentinel
+allows Sentinel policies to restrict almost any behavior.
+
+Next, we'll
+[introduce and explain rules](/sentinel/intro/getting-started/rules)
+so we can use them in our policies.
diff --git a/content/sentinel/v0.16.x/content/sentinel/intro/getting-started/imports.mdx b/content/sentinel/v0.16.x/content/sentinel/intro/getting-started/imports.mdx
new file mode 100644
index 0000000000..ba26b7be62
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/intro/getting-started/imports.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Imports
+sidebar_title: Imports
+sidebar_current: intro-getting-started-imports
+description: Imports enable Sentinel to access external data and functions.
+layout: intro
+---
+
+# Imports
+
+Imports enable Sentinel to access external data and functions.
+
+Sentinel ships with a set of [standard imports](/sentinel/imports).
+Standard imports are available by default to every Sentinel policy. Note that
+the application embedding Sentinel may allow or deny the available
+set of imports.
+
+To use an import, use the `import` statement. Then, access data and
+functions on the import using the import name followed by a period and
+the field you want to access. The example below checks whether the request
+path starts with a prefix. Assume that `request_path` is available.
+
+```sentinel
+import "strings"
+
+main = rule { strings.has_prefix(request_path, "/admin") }
+```
+
+## External Data
+
+The true power in imports is their ability to reference external data.
+Imports can be added to a Sentinel-enabled application through
+[plugins](/sentinel/extending). This enables any Sentinel-enabled
+application to make policy decision based on any source of data.
+
+This ability allows the policy system to enforce almost any necessary
+organizational policy, since the ability of a policy isn't restricted
+purely to the embedding application's data model.
+
+For example, policies in [Nomad](https://www.nomadproject.io) can
+access data in [Consul](https://www.consul.io) to determine attributes
+of a policy. In the example below, we use a hypothetical Consul import:
+
+```sentinel
+import "consul"
+
+main = rule {
+ job.tasks[0].resources.memory <= int(consul.get("policy/nomad/max-memory"))
+}
+```
+
+In this example, a Nomad job's memory usage is limited to the value of
+a Consul key/value item. By simply changing a Consul KV entry, policy
+can be changed. Imports can do anything, you can [write your own import plugins](/sentinel/extending)
+to extend Sentinel.
+
+Next, we'll learn how to
+[test policies](/sentinel/intro/getting-started/testing)
+to ensure our policy logic is correct.
diff --git a/content/sentinel/v0.16.x/content/sentinel/intro/getting-started/index.mdx b/content/sentinel/v0.16.x/content/sentinel/intro/getting-started/index.mdx
new file mode 100644
index 0000000000..0942cdb637
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/intro/getting-started/index.mdx
@@ -0,0 +1,17 @@
+---
+page_title: Install Sentinel CLI - Getting Started
+sidebar_title: Getting Started
+sidebar_current: intro-getting-started-overview
+description: The first step to using Sentinel is to get the CLI installed.
+layout: intro
+---
+
+# Getting Started With Sentinel
+
+This section covers getting started with Sentinel. Here, we will take you
+through the first steps that you will need to do to get started with installing
+the Sentinel CLI, writing your first policy, and getting familiar with rules,
+boolean logic, imports, and testing.
+
+You can select a topic on the menu to get started. The first step is to [install
+the Sentinel CLI](/sentinel/intro/getting-started/install).
diff --git a/content/sentinel/v0.16.x/content/sentinel/intro/getting-started/install.mdx b/content/sentinel/v0.16.x/content/sentinel/intro/getting-started/install.mdx
new file mode 100644
index 0000000000..4c3959d617
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/intro/getting-started/install.mdx
@@ -0,0 +1,57 @@
+---
+page_title: Install Sentinel CLI - Getting Started
+sidebar_title: Installing the CLI
+sidebar_current: intro-getting-started-install
+description: The first step to using Sentinel is to get the CLI installed.
+layout: intro
+---
+
+# Install Sentinel CLI
+
+Sentinel is a policy framework that is embedded in the enterprise versions of
+HashiCorp tools. The policies you write are deployed to these applications and
+enforced there.
+
+The Sentinel CLI is a command-line interface for local development and testing.
+For the getting started guide, we'll use the CLI to learn how to write policies
+for Sentinel-enabled applications. The Sentinel CLI is distributed as a [binary
+package](/sentinel/downloads) for all supported platforms and architectures.
+
+## Installation Instructions
+
+To install the Sentinel CLI, find the [appropriate package](/sentinel/downloads) for your
+system and download it. The CLI is packaged as a zip archive.
+
+After downloading Sentinel, unzip the package. The CLI runs as a single binary
+named `sentinel`. Any other files in the package can be safely removed and
+Sentinel will still function.
+
+The final step is to make sure that the `sentinel` binary is available on the `PATH`.
+See [this page](https://stackoverflow.com/questions/14637979/how-to-permanently-set-path-on-linux)
+for instructions on setting the PATH on Linux and Mac.
+[This page](https://stackoverflow.com/questions/1618280/where-can-i-set-path-to-make-exe-on-windows)
+contains instructions for setting the PATH on Windows.
+
+## Verifying the Installation
+
+After installing the Sentinel CLI, verify the installation worked by opening a
+new terminal session and checking that the `sentinel` binary is available. By
+executing `sentinel`, you should see help output similar to the following:
+
+```
+$ sentinel
+Usage: sentinel [--version] [--help] []
+
+Available commands are:
+ apply Execute a policy and output the result
+ fmt Format Sentinel policy to a canonical format
+ test Test policies
+ version Prints the Sentinel version
+```
+
+If you get an error that the binary could not be found, then your `PATH`
+environment variable was not setup properly. Please go back and ensure that your
+`PATH` variable contains the directory where Sentinel was installed.
+
+Otherwise, the CLI is installed and ready to go. Let's celebrate by [writing our
+first policy!](/sentinel/intro/getting-started/first-policy)
diff --git a/content/sentinel/v0.16.x/content/sentinel/intro/getting-started/logic.mdx b/content/sentinel/v0.16.x/content/sentinel/intro/getting-started/logic.mdx
new file mode 100644
index 0000000000..893d63c851
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/intro/getting-started/logic.mdx
@@ -0,0 +1,55 @@
+---
+page_title: Logic
+sidebar_title: Logic
+sidebar_current: intro-getting-started-logic
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Logic
+
+The core of a policy is a set of logical expressions to determine whether
+a behavior is allowed or not.
+Sentinel makes it easy to write readable logical expressions to express
+the intended policy.
+
+The logical expression syntax will be familiar to anyone who has programmed
+before. Sentinel also supports a couple more unique constructs to assist
+with common policy requirements. Detailed documentation on how to write
+logical expressions and the supported operators is available in
+[the boolean expression reference](/sentinel/language/boolexpr).
+
+Example logic:
+
+```sentinel
+a is b // Equality, you can also use ==
+a is not b // Inequality, you can also use !=
+
+a > b // Relational operators, comparison
+a < b
+a >= b
+a <= b
+
+a contains b // Inclusion check, substrings, etc.
+a not contains b // Negated version of above
+```
+
+The full list of available operators can be found in
+[the boolean expression reference](/sentinel/language/boolexpr).
+
+Logic can be combined with `and` or `or`. When combined with `and`, both
+sides must result in `true`. When combined with `or`, only one side needs
+to be true to result in `true`.
+
+With these building blocks, basic rules can be built up:
+
+```sentinel
+main = rule {
+ hour >= 8 and
+ hour < 17
+}
+```
+
+Next, we'll introduce
+[imports](/sentinel/intro/getting-started/imports)
+so that we can write rules that interact with external data.
diff --git a/content/sentinel/v0.16.x/content/sentinel/intro/getting-started/next-steps.mdx b/content/sentinel/v0.16.x/content/sentinel/intro/getting-started/next-steps.mdx
new file mode 100644
index 0000000000..23b470be59
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/intro/getting-started/next-steps.mdx
@@ -0,0 +1,21 @@
+---
+page_title: Next Steps
+sidebar_current: intro-getting-started-nextsteps
+description: That concludes the getting started guide for Sentinel.
+layout: intro
+---
+
+# Next Steps
+
+That concludes the getting started guide for Sentinel. Hopefully you're excited
+about the possibilities of Sentinel and ready to put this knowledge to use to
+improve the safety of your environments.
+
+We've covered the basics of the core features of Sentinel in this guide.
+We recommend the following as next steps:
+
+- [Documentation](/sentinel) - The documentation is an in-depth
+ reference guide of all the features of Sentinel.
+
+- [Language Reference](/sentinel/language) - The language reference
+ is a complete reference to the features of the Sentine policy language.
diff --git a/content/sentinel/v0.16.x/content/sentinel/intro/getting-started/rules.mdx b/content/sentinel/v0.16.x/content/sentinel/intro/getting-started/rules.mdx
new file mode 100644
index 0000000000..6a72af190e
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/intro/getting-started/rules.mdx
@@ -0,0 +1,73 @@
+---
+page_title: Rules
+sidebar_title: Rules
+sidebar_current: intro-getting-started-rules
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Rules
+
+A rule describes a set of conditions that result in either true or false.
+
+Rules in Sentinel are a first-class concept. They are used for making complex
+logic more understandable by breaking it down into a set of rules, for
+testing policies by asserting certain rules are passed, and more.
+
+## Writing Rules
+
+Let's look at the Sentinel policy we wrote in the previous section:
+
+```sentinel
+hour = 4
+main = rule { hour >= 0 and hour < 12 }
+```
+
+The logic in this policy is straightforward and easy to understand. However,
+it is common for policy logic to become much more complicated. Rules are the
+key abstraction for making complex logic understandable. We can turn the
+policy above into a set of rules:
+
+```sentinel
+hour = 4
+
+past_midnight = rule { hour >= 0 }
+before_noon = rule { hour < 12 }
+
+main = rule {
+ past_midnight and
+ before_noon
+}
+```
+
+This policy is easier to read. Our original policy was already quite
+simple, but it is easy to imagine a more complex policy becoming much
+more readable by extracting logical expressions into a set of rules.
+
+Rules don't just exist to make a policy more readable. They're also a
+testing and debugging point. For testing, you can assert that certain rules
+are certain values given a specific input to verify that your policy works
+as you expect. Testing and debugging are covered in later sections.
+
+## Conditional Rules
+
+In many cases, a rule is only valid when something else is also true.
+
+Consider the human language statement "before you eat, you must wash your hands."
+The rule "you must wash your hands" is only valid "before you eat". In any
+other scenario, the rule is meaningless.
+This is also true in a technical context. Imagine the rule "if the driver
+is Docker, you must not expose any ports." For this example, assume
+rules `is_docker` and `has_no_exposed_ports` exist. You can write this rule
+like this:
+
+```sentinel
+main = rule when is_docker { has_no_exposed_ports }
+```
+
+When `is_docker` is true, the rule is evaluated as normal. However,
+when `is_docker` is false, the rule always returns `true`, because the
+logic of the rule is meaningless.
+
+Let's learn how to create more complex rules with
+[logical expressions](/sentinel/intro/getting-started/logic).
diff --git a/content/sentinel/v0.16.x/content/sentinel/intro/getting-started/testing.mdx b/content/sentinel/v0.16.x/content/sentinel/intro/getting-started/testing.mdx
new file mode 100644
index 0000000000..ee245f1c0e
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/intro/getting-started/testing.mdx
@@ -0,0 +1,70 @@
+---
+page_title: Testing
+sidebar_current: intro-getting-started-testing
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Testing
+
+It is important to ensure the policies you write are correct. Sentinel
+includes a built-in test framework that can be run locally and in CI
+environments to test that policies behave as expected.
+
+A common pitfall with many simple ACL systems is that they provide no easy
+way to verify their correctness. You basically have to set the ACL and try
+the behaviors against a real system to verify it is working as expected.
+This requires a lot of setup and is unique to each system.
+
+[Sentinel's built-in test framework](/sentinel/writing/testing) has
+zero dependencies. It is a single binary that can mock the data that real
+systems are exposing to the policy. It is designed to be CI-friendly and
+enables continuous testing of your policies. This is necessary for [policy
+as code](/sentinel/concepts/policy-as-code).
+
+Detailed documentation on testing policies is available in [the Sentinel Testing reference](/sentinel/writing/testing).
+
+## Writing Tests
+
+For this example, save the following policy as `policy.sentinel`:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Next, let's write a passing test case. This will test that the policy tests
+when we expect it to pass. Save the following in `test/policy/good.json`:
+
+```json
+{
+ "global": {
+ "day": "monday",
+ "hour": 14
+ }
+}
+```
+
+And run `sentinel test`:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/good.json
+```
+
+The `sentinel test` command will automatically find all Sentinel policies
+and their associated test cases and run them all. The test framework will
+run all JSON files as individual test cases, allowing you to test a variety
+of scenarios for your policies.
+
+Try adding another test case to force the policy to fail. This test case can
+be saved at `test/policy/fail.json`. For test cases with intentional
+failures, you'll need to use the [test assertions described in the testing
+reference](/sentinel/writing/testing#a-failing-test).
+
+Now that you have a good grasp of what Sentinel is and how to use it, please feel
+free to look through the
+[next steps](/sentinel/intro/getting-started/next-steps)
+you can take to further your Sentinel knowledge.
diff --git a/content/sentinel/v0.16.x/content/sentinel/intro/index.mdx b/content/sentinel/v0.16.x/content/sentinel/intro/index.mdx
new file mode 100644
index 0000000000..785a442fb8
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/intro/index.mdx
@@ -0,0 +1,32 @@
+---
+layout: intro
+page_title: Documentation
+sidebar_current: docs-home
+description: >-
+ Sentinel is a language and framework for policy built to be embedded in
+ existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+[introduction](/sentinel/intro) to understand what Sentinel is, how it
+compares to other software, and more.
+
+
diff --git a/content/sentinel/v0.16.x/content/sentinel/intro/what.mdx b/content/sentinel/v0.16.x/content/sentinel/intro/what.mdx
new file mode 100644
index 0000000000..284fa5a82e
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/intro/what.mdx
@@ -0,0 +1,65 @@
+---
+page_title: Introduction
+sidebar_title: What is Sentinel?
+sidebar_current: intro-what
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: intro
+---
+
+# Introduction to Sentinel
+
+Welcome to the intro guide to Sentinel! This guide is the best
+place to start with Sentinel.
+
+If you are already familiar with the basics of Sentinel, the
+[documentation](/sentinel) provides a better reference
+guide for all available features as well as internals.
+
+## What is Sentinel?
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+Sentinel provides a language for writing policy and a workflow for developing
+and testing policies independent of the software they'll be written to. We
+also provide a framework for developers to build plugins that allow a
+Sentinel-enabled system to access external information to make policy
+decisions.
+
+Most systems today have some degree of access control. You are able to define
+identities and what they have access to. These ACL systems solve an immediate
+and necessary problem of locking down a system in very broad strokes. Sentinel
+is a reusable system for more advanced software policy decisions. Sentinel
+enables:
+
+- **Fine-Grained Policy**: Most ACL systems only enable coarse-grained
+ behaviors: "read", "write", etc. Sentinel enables fine-grained behavior such
+ as disallowing a certain API call when specific parameters are present.
+
+- **Logic-Based Policy**: You can write policy using full
+ conditional logic. For example, you may only allow a certain application behavior
+ on Monday to Thursday unless there is a manager override.
+
+- **Accessing External Information**: Sentinel can source external information
+ to be used in policy decisions. For example, a policy that restricts the size
+ of a payload may read data from [Consul](https://www.consul.io) to determine
+ the payload size limit.
+
+- **Enforcement levels**: Sentinel allows policies to be defined along
+ with an "enforcement level" that dictates the pass/fail behavior of a policy.
+ Advisory policies warn if they fail, soft mandatory policies can have their
+ failures overridden, and hard mandatory policies must pass under all
+ circumstances. Having this as a built-in concept enables you to model
+ policy more accurately and completely for your organization.
+
+## Next Steps
+
+See the page on [Why Sentinel?](/sentinel/intro/why) to learn more
+about the origins of the product. Then, continue onwards with
+the [getting started guide](/sentinel/intro/getting-started/install) to write
+your first policies and see how Sentinel works in practice.
diff --git a/content/sentinel/v0.16.x/content/sentinel/intro/why.mdx b/content/sentinel/v0.16.x/content/sentinel/intro/why.mdx
new file mode 100644
index 0000000000..37e01c6fc3
--- /dev/null
+++ b/content/sentinel/v0.16.x/content/sentinel/intro/why.mdx
@@ -0,0 +1,50 @@
+---
+page_title: Why Sentinel?
+sidebar_title: Why Sentinel?
+sidebar_current: intro-why
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: intro
+---
+
+# Why Sentinel?
+
+The growth of infrastructure and applications has been enabled in part
+by an increasing trend towards automation everywhere.
+Configuration as code (such as Chef or Puppet with [Packer](https://www.packer.io))
+has enabled automated machine configuration. Infrastructure
+as code (such as [Terraform](https://www.terraform.io)) has enabled
+automatic infrastructure creation. And schedulers (such as
+[Nomad](https://www.nomadproject.io) and Kubernetes) have enabled
+automatic application deployment.
+Sentinel enables guardrails to be put in place
+on automation while allowing the codification and automatic enforcement
+of business requirements in critical areas of your infrastructure.
+
+Meanwhile, businesses have business requirements and sometimes legal
+requirements which must be expressed in policies. Traditionally, these
+policies are enforced by humans. But in a highly automated world, the
+automation is only as fast as its slowest component. In many cases, this
+is the human verification step.
+
+As an example: before infrastructure as code and autoscaling, if an order
+came through for 5,000 new machines, a human would likely respond to the
+ticket verifying that the user really intended to order 5,000 new machines.
+Today, automation can almost always freely order 5,000 new compute instances
+without any hesitation, which can result in unintended expense or system
+instability.
+
+Sentinel introduces [policy as code](/sentinel/concepts/policy-as-code)
+and a powerful framework built-in to HashiCorp tooling to allow automation
+guardrails, business requirements, legal compliance, and more to be
+actively enforced by the running systems in realtime.
+
+With Sentinel, you can require an override to create certain numbers of
+infrastructure resources. You can disallow unsafe deployment configurations
+with Nomad. You can enforce certain key/value formats in Consul. You
+can restrict secret access by time in Vault. And more.
+
+Sentinel is available today in HashiCorp enterprise products. You can
+try Sentinel immediately with the [getting started guide](/sentinel/intro/getting-started/install) or learn more about the integrations in the
+[documentation](/sentinel).
diff --git a/content/sentinel/v0.16.x/data/docs-nav-data.json b/content/sentinel/v0.16.x/data/docs-nav-data.json
new file mode 100644
index 0000000000..31636ebfad
--- /dev/null
+++ b/content/sentinel/v0.16.x/data/docs-nav-data.json
@@ -0,0 +1,312 @@
+[
+ {
+ "title": "Release Notes",
+ "path": "changelog"
+ },
+ {
+ "title": "Basic Concepts",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "concepts"
+ },
+ {
+ "title": "Policy as Code",
+ "path": "concepts/policy-as-code"
+ },
+ {
+ "title": "Policy Language",
+ "path": "concepts/language"
+ },
+ {
+ "title": "Imports",
+ "path": "concepts/imports"
+ },
+ {
+ "title": "Enforcement Levels",
+ "path": "concepts/enforcement-levels"
+ }
+ ]
+ },
+ {
+ "title": "Configuration File Syntax",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "configuration"
+ },
+ {
+ "title": "Remote Sources",
+ "path": "configuration/remote-sources"
+ }
+ ]
+ },
+ {
+ "title": "Commands (CLI)",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "commands"
+ },
+ {
+ "title": "apply",
+ "path": "commands/apply"
+ },
+ {
+ "title": "fmt",
+ "path": "commands/fmt"
+ },
+ {
+ "title": "test",
+ "path": "commands/test"
+ }
+ ]
+ },
+ {
+ "title": "Writing Policy",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "writing"
+ },
+ {
+ "title": "Basics",
+ "path": "writing/basic"
+ },
+ {
+ "title": "Rules",
+ "path": "writing/rules"
+ },
+ {
+ "title": "Traces",
+ "path": "writing/tracing"
+ },
+ {
+ "title": "Testing",
+ "path": "writing/testing"
+ },
+ {
+ "title": "Imports",
+ "path": "writing/imports"
+ },
+ {
+ "title": "Debugging",
+ "path": "writing/debugging"
+ }
+ ]
+ },
+ {
+ "title": "Extending Sentinel",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "extending"
+ },
+ {
+ "title": "Modules",
+ "path": "extending/modules"
+ },
+ {
+ "title": "Plugins",
+ "path": "extending/plugins"
+ },
+ {
+ "title": "Internals",
+ "path": "extending/internals"
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Language",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "language"
+ },
+ {
+ "title": "Variables",
+ "path": "language/variables"
+ },
+ {
+ "title": "Values",
+ "path": "language/values"
+ },
+ {
+ "title": "Lists",
+ "path": "language/lists"
+ },
+ {
+ "title": "Maps",
+ "path": "language/maps"
+ },
+ {
+ "title": "Rules",
+ "path": "language/rules"
+ },
+ {
+ "title": "Imports",
+ "path": "language/imports"
+ },
+ {
+ "title": "Parameters",
+ "path": "language/parameters"
+ },
+ {
+ "title": "Boolean Expressions",
+ "path": "language/boolexpr"
+ },
+ {
+ "title": "Arithmetic",
+ "path": "language/arithmetic"
+ },
+ {
+ "title": "Slices",
+ "path": "language/slices"
+ },
+ {
+ "title": "Conditionals",
+ "path": "language/conditionals"
+ },
+ {
+ "title": "Loops",
+ "path": "language/loops"
+ },
+ {
+ "title": "Collection Operations",
+ "path": "language/collection-operations"
+ },
+ {
+ "title": "Functions",
+ "path": "language/functions"
+ },
+ {
+ "title": "Scope",
+ "path": "language/scope"
+ },
+ {
+ "title": "Undefined",
+ "path": "language/undefined"
+ },
+ {
+ "title": "Logging and Errors",
+ "path": "language/logging-errors"
+ },
+ {
+ "title": "Specification",
+ "path": "language/spec"
+ }
+ ]
+ },
+ {
+ "title": "Builtin Functions",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "functions"
+ },
+ {
+ "title": "append",
+ "path": "functions/append"
+ },
+ {
+ "title": "delete",
+ "path": "functions/delete"
+ },
+ {
+ "title": "error",
+ "path": "functions/error"
+ },
+ {
+ "title": "keys",
+ "path": "functions/keys"
+ },
+ {
+ "title": "length",
+ "path": "functions/length"
+ },
+ {
+ "title": "print",
+ "path": "functions/print"
+ },
+ {
+ "title": "range",
+ "path": "functions/range"
+ },
+ {
+ "title": "values",
+ "path": "functions/values"
+ }
+ ]
+ },
+ {
+ "title": "Standard Imports",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "imports"
+ },
+ {
+ "title": "decimal",
+ "path": "imports/decimal"
+ },
+ {
+ "title": "http",
+ "path": "imports/http"
+ },
+ {
+ "title": "json",
+ "path": "imports/json"
+ },
+ {
+ "title": "runtime",
+ "path": "imports/runtime"
+ },
+ {
+ "title": "sockaddr",
+ "path": "imports/sockaddr"
+ },
+ {
+ "title": "strings",
+ "path": "imports/strings"
+ },
+ {
+ "title": "time",
+ "path": "imports/time"
+ },
+ {
+ "title": "types",
+ "path": "imports/types"
+ },
+ {
+ "title": "units",
+ "path": "imports/units"
+ },
+ {
+ "title": "version",
+ "path": "imports/version"
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Consul",
+ "path": "consul"
+ },
+ {
+ "title": "Nomad",
+ "path": "nomad"
+ },
+ {
+ "title": "Terraform",
+ "path": "terraform"
+ },
+ {
+ "title": "Vault",
+ "path": "vault"
+ }
+]
diff --git a/content/sentinel/v0.16.x/data/intro-nav-data.json b/content/sentinel/v0.16.x/data/intro-nav-data.json
new file mode 100644
index 0000000000..c95181af70
--- /dev/null
+++ b/content/sentinel/v0.16.x/data/intro-nav-data.json
@@ -0,0 +1,47 @@
+[
+ {
+ "title": "What is Sentinel?",
+ "path": "what"
+ },
+ {
+ "title": "Why Sentinel?",
+ "path": "why"
+ },
+ {
+ "title": "Getting Started",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "getting-started"
+ },
+ {
+ "title": "Installing the CLI",
+ "path": "getting-started/install"
+ },
+ {
+ "title": "Your First Policy",
+ "path": "getting-started/first-policy"
+ },
+ {
+ "title": "Rules",
+ "path": "getting-started/rules"
+ },
+ {
+ "title": "Logic",
+ "path": "getting-started/logic"
+ },
+ {
+ "title": "Imports",
+ "path": "getting-started/imports"
+ },
+ {
+ "title": "Testing",
+ "path": "getting-started/testing"
+ },
+ {
+ "title": "Next Steps",
+ "path": "getting-started/next-steps"
+ }
+ ]
+ }
+]
diff --git a/content/sentinel/v0.16.x/data/version.js b/content/sentinel/v0.16.x/data/version.js
new file mode 100644
index 0000000000..e45d23dfcf
--- /dev/null
+++ b/content/sentinel/v0.16.x/data/version.js
@@ -0,0 +1,2 @@
+// This file is auto-generated - DO NOT EDIT
+export default "0.16.1"
\ No newline at end of file
diff --git a/content/sentinel/v0.16.x/img/sentinel-import-topology.svg b/content/sentinel/v0.16.x/img/sentinel-import-topology.svg
new file mode 100644
index 0000000000..36f7c79bc9
--- /dev/null
+++ b/content/sentinel/v0.16.x/img/sentinel-import-topology.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/changelog.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/changelog.mdx
new file mode 100644
index 0000000000..e6b8c1121f
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/changelog.mdx
@@ -0,0 +1,548 @@
+---
+page_title: Sentinel Runtime Release Notes
+sidebar_title: Release Notes
+sidebar_current: docs-changelog
+description: >-
+ This are the public release notes for the Sentinel runtime. See this document
+ for the latest updates.
+layout: docs
+---
+
+# Sentinel Runtime Release Notes
+
+These are the release notes for the Sentinel runtime.
+
+Each version of the runtime is released with a corresponding version of
+[Sentinel CLI](/sentinel/commands). To download the CLI, see the [downloads
+page](/sentinel/downloads).
+
+Sentinel integrations and embedded runtimes may not always have the latest
+version installed, depending on the product's individual release cycle. For more
+information, contact the support team for your specific integration.
+
+
+
+## 0.17.2 (Unreleased)
+
+## 0.17.1 (January 7, 2021)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where hard-coded path separator caused `sentinel test` issues in Windows.
+
+## 0.17.0 (January 5, 2021)
+
+LANGUAGE CHANGES:
+
+- **Map Expression** `map`. A new quantifier expression, `map` has been added.
+ `map` takes a collection and applies an operation to each element in the
+ collection, returning a list of results with the resulting values.
+- **Emptiness checks** `is empty` and `is not empty`. Two new expressions,
+ `is empty` and `is not empty` have been added. As they are aliases to the
+ `length` built-in, only `string`, `map`, `list` or `undefined` is
+ supported.
+- **Comparable maps** Maps (the data type) are now comparable. Maps are equal if
+ they are of equal length and both their corresponding keys and values are
+ comparable and equal.
+- **Rich Return Types** Rules can now process and return non-boolean data.
+ Scalar, list, and map types are supported. When non-boolean values are used,
+ the presence of a non-zero or non-zero-length `main` rule determines policy
+ failure. More details can be found on
+ https://docs.hashicorp.com/sentinel/language#main.
+
+FEATURES:
+
+- **CLI**: `sentinel apply` and `sentinel test` now have options for JSON
+ output. For more details, see the CLI documentation.
+- `imports/base64`: Added a new import, `base64`, to assist with encoding and
+ decoding base64 strings.
+
+## 0.16.1 (October 21, 2020)
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where `contains` to ensure any instance of `undefined` would correctly result in `undefined`.
+- `runtime/eval`: Fixed an issue in `any` and `all` expressions to ensure correct boolean logic when dealing with a collection containing `undefined`.
+- `imports`: Fixed an issue where import processes were not killed, which caused non-graceful closures for the clients.
+- `lang/scanner`: Fixed an issue where inline `#` style comments were not being consumed.
+
+## 0.16.0 (October 14, 2020)
+
+BREAKING CHANGES:
+
+- `cmd/doc`: Removal of the deprecated `sentinel doc` command.
+- `sentinel`: Replace `whitelist` and `blacklist` with `allowlist` and `denylist`, as per inclusive language changes.
+
+FEATURES:
+
+- `imports/version`: Added a new import, `version`, which supplies helpers for dealing with semantic versioning.
+- `cmd/apply`: Added an `HCL` based configuration file, which will eventually deprecate the legacy `JSON` configuration.
+- `cmd/apply`: Added support for download remote policies and modules.
+- `cmd/test`: Added support for downloading remote test modules.
+- `cmd/apply`: Added support for evaluating a policy based on it's key within the configuration.
+- `cmd/apply`: Added support for running all policies in a configuration by default.
+
+## 0.15.6 (July 7, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.15.5 (May 20, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel integrations. There are no user-facing changes.
+
+## 0.15.4 (May 13, 2020)
+
+BUG FIXES:
+
+- `imports`: Fixed an issue with the standard imports that was affecting the handling of null data within lists.
+
+## 0.15.3 (April 16, 2020)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where `IndexExpr` were not rewritten, as well as ensuring `SelectorExpr` uses any nested rewrites.
+- `lang/printer`: Fixed issue printing deeply nested structures and/or loops.
+
+IMPROVEMENTS:
+
+- `imports/decimal`: Added alias methods to improve consistency of method names within the `decimal` import. The alias methods are `is_nan` for `isnan`, as well as both `is_inf` and `is_infinite` for `isinfinite`.
+- `command/apply`: `sentinel apply` will now output the policy description when a trace is forced via `-trace`.
+- `sentinel`: Improved `String` output formatting of Policy docstring for `EvalPolicyResult`.
+
+## 0.15.2 (April 2, 2020)
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue with the `printer` that was causing a panic
+ when a `#` line comment was used with no text following it.
+- `imports/types`: Fixed an issue with the `types` import where calls to
+ `type_of` for undefined types were incorrectly returning as map types. These
+ will now be correctly be identified as undefined as expected.
+
+## 0.15.1 (March 6, 2020)
+
+BUG FIXES:
+
+- `sentinel`: Fixed how modules are managed internally in the runtime to correct
+ race conditions in concurrent scenarios and to allow for per-evaluation module
+ restrictions. This functionality is only of interest to embedded applications
+ with long-lived runtimes and is currently not implemented in any HashiCorp
+ integration.
+
+## 0.15.0 (March 5, 2020)
+
+NOTES:
+
+- **Windows Digital Signature**. Our windows releases for this version and up will be signed
+ and verified according to Microsoft's requirements.
+
+FEATURES:
+
+- `cmd/config`: Modules can now be loaded off of local disk using the Sentinel
+ CLI. For information on how to do so, see the Sentinel documentation.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: Changed modules so that they now have singleton scope when
+ loaded. Importing a module under two different aliases, or within a nested
+ module, will now share scopes - modification of state in one will alter the
+ other.
+- `lang/object`: Introduced an interface to allow the duplication of various
+ types, like collections.
+- `runtime/eval`: Changed how collection data is returned from modules. This
+ data is now duplicated to prevent accidental or deliberate circumvention of
+ the prohibition on assignment to import data, and brings behavior to parity
+ with binary imports.
+
+## 0.14.4 (February 6, 2020)
+
+IMPROVEMENTS:
+
+- `imports/time`: Changed the `add` timespace function in the `time` import to
+ allow it to take float types as durations. These values will be truncated to
+ the appropriate integer-value duration.
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where out-of-place right-hand braces were being
+ parsed as empty statements, instead of raising syntax errors.
+
+## 0.14.3 (January 22, 2020)
+
+IMPROVEMENTS:
+
+- `cmd/test`: Fail when an assertion is not found in a trace.
+- `cmd/test`: Added a message to notify when no rules were evaluated in a test.
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue where import aliases (the `as baz` in `import "foo/bar" as baz`) were being removed when formatting with `sentinel fmt`.
+
+- `imports/http`: Fixed an issue with the http import where the client library's
+ debug logs were being printed to standard error.
+
+## 0.14.2 (January 15, 2020)
+
+NOTES:
+
+With this release, we are discontinuing support for MacOS 32-bit. 64-bit builds
+are now the only builds available for MacOS.
+
+IMPROVEMENTS:
+
+- `lang/printer`: A newline is now added to files formatted via `sentinel fmt`.
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue in printing where multi-line expressions would
+ indent infinitely. They will now only indent once at the most. This is mostly
+ seen when using `sentinel fmt`.
+- `runtime/encoding`: Fixed an issue where if a plugin returned a deeply
+ embedded undefined value, the value was instead decoded into a map.
+
+## 0.14.1 (January 6, 2020)
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where reserved words could not be used as
+ selectors when the selector expression was the last lexeme on a line.
+
+## 0.14.0 (December 18, 2019)
+
+LANGUAGE CHANGES:
+
+- **Filter Expression** `filter`. A new quantifier expression, `filter` has been added.
+ `filter` returns a subset of the provided collection based on a boolean condition that
+ is asserted for each element.
+
+NOTES:
+
+- **Apple Notarization**. Our darwin releases for this version and up will be signed
+ and notarized according to Apple's requirements.
+
+## 0.13.1 (November 25, 2019)
+
+BUG FIXES:
+
+- `runtime/eval`: Parameters are no longer allowed in mock files. Adding one to
+ a mock will result in a runtime error.
+- `runtime/eval`: Fixed an issue where import calls from within mocks were
+ failing under certain circumstances.
+- `imports/decimal`: `int` should now correctly provide the truncated integer
+ representation of a decimal number, not the rounded one.
+
+## 0.13.0 (November 15, 2019)
+
+LANGUAGE CHANGES:
+
+- **Policy Parameters**. The new [policy
+ parameters](https://docs.hashicorp.com/sentinel/language/parameters) feature
+ allows authors to use a `param` declaration at the top of a policy to supply
+ values that are expected to come from outside of a policy. See the linked
+ documentation for more details.
+
+FEATURES:
+
+- `imports/http`: The [`http`
+ import](https://docs.hashicorp.com/sentinel/imports/http) is a new addition
+ to the standard library enabling the use of HTTP-accessible data from outside
+ the runtime in policy rules.
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where loading other imports before a mocked
+ import would cause those imports to no longer be visible from within the
+ policy.
+
+## 0.12.0 (October 7, 2019)
+
+LANGUAGE CHANGES:
+
+- **New `case` statement**. This statement is a selection control mechanism to
+ conditionally execute a branch based on expression equality, allowing
+ simplification of complex conditional chains that may otherwise need to be
+ written with `else if`. See the [Case
+ Statements](https://docs.hashicorp.com/sentinel/language/spec#case-statements)
+ section of the Sentinel language specification for more details.
+
+IMPROVEMENTS:
+
+- `lang/semantic`: Added a semantic check to ensure usage of append is not using
+ a return value.
+
+BUG FIXES:
+
+- `runtime/eval`: Corrected an issue where calling a method on an import object
+ value that was the result of a method call on another import object value
+ would have erroneously tried to call an import of the name of the "parent"
+ import object value. Example: in `a = subject.new(); b = a.call(); c = b.call()`, `b.call()` would attempt to call a method named `call` on the root
+ namespace for an import named `a`. This has now been corrected so that
+ `b.call()` will now correctly call the `call` method for the respective object
+ namespace residing in the import named `subject`.
+- `imports`: Some standard imports may have been returning null for some unknown
+ keys in objects when they should have been returning undefined. This was due
+ to an SDK issue which was corrected in SDK version 0.3.2, which has now been
+ corrected.
+- `cmd/test`: `sentinel test` will now correctly fail a policy if it encounters
+ an error.
+- `cmd/test`: `sentinel test` will now correctly display errors and other output
+ that were missing due to a formatting issue.
+- `runtime/eval`: The `append` builtin now correctly returns undefined for all
+ calls, as called for by the Specification. Note that in most cases, the
+ semantic check outlined above will trigger an error if the return value is
+ used.
+
+## 0.11.0 (September 5, 2019)
+
+LANGUAGE CHANGES:
+
+- **New builtin function:** `range()`. This function existed in the spec in
+ earlier versions but was removed as it lacked an implementation. This has now
+ been implemented and re-added to the spec. See the
+ [Range](https://docs.hashicorp.com/sentinel/language/spec#range) section of
+ the Sentinel Specification for more details.
+- Lists are now comparable. Lists are equal if their corresponding elements are
+ comparable and equal.
+- Method calls on values returned by imports are now supported. See the
+ [Imports](https://docs.hashicorp.com/sentinel/language/spec#imports) section
+ of the Sentinel Specification for more details.
+
+FEATURES:
+
+- `imports/decimal`: This is a new import designed to do exact precision
+ mathematical calculations.
+- `runtime/eval`: Compound call expressions that refer to imports (example:
+ foo.bar().baz() when `foo` is a loaded import) will now function as expected.
+ Previously, this was only supported up to the first call (example:
+ `foo.bar()`).
+- `imports/time`: Timespaces returned by calls such as `time.now` are now
+ callable. Example: `t = time.now; t.after(some_previous_time)` will now
+ function.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: The implementation of comparison of non-comparable types has
+ now changed. Rather than triggering a runtime error, non-comparable types will
+ now return false when attempting to compare.
+
+## 0.10.4 (August 15, 2019)
+
+BUG FIXES:
+
+- `runtime/encoding`: Fixed an issue with conversion of null values that could
+ lead to crashes in imports.
+
+## 0.10.3 (July 15, 2019)
+
+BUG FIXES:
+
+- `command/config`: Parsing a configuration file where malformed data is
+ encountered after apparently properly-formed data will now report an error. An
+ example would be a situation where misplaced braces would cause a JSON object
+ to only parse part of the configuration file.
+
+## 0.10.2 (June 25, 2019)
+
+BUG FIXES:
+
+- `lang/parser`: Corrected an issue where parsing certain compound binary
+ expressions where the negation predicate (`not`) was in use would cause the
+ negation to have no effect. Example: `foo else "bar" not in "baz"` would have
+ been parsed and evaluated as `foo else "bar" in "baz"`, effectively producing
+ the opposite result.
+
+## 0.10.1 (May 9, 2019)
+
+BUG FIXES:
+
+- `command/test`: `sentinel test` now displays policies without
+ tests as an unknown result with [no test files], instead of the somewhat
+ erroneous behavior of displaying it as a PASS. A full test run with a mixture
+ of passing tests and no tests still results in an overall successful result.
+
+## 0.10.0 (April 18, 2019)
+
+LANGUAGE CHANGES:
+
+- Mixed-number arithmetic operations are now allowed. Addition (`+`),
+ subtraction (`-`), multiplication (`*`), division (`/`), and remainder
+ operations (`%`) are no longer restricted to number values of the same
+ type, and can mix integer and floating point. The result of these operations
+ is always a floating-point number.
+- Remainder (modulo, `%`) operations are now allowed on floating-point numbers.
+
+BUG FIXES:
+
+- `lang/parser`: Fixed a bug that affected the use of `not` with `contains`,
+ `matches`, or `in` in a compound expression.
+- `runtime/eval`: Fixed error messages on evaluation errors with `contains` and
+ `in` to indicate that strings are an allowed type along with lists and maps.
+
+## 0.9.2 (March 15, 2019)
+
+This is a dependency update related to the changes mentioned in 0.9.1. No other
+changes have been made.
+
+## 0.9.1 (March 14, 2019)
+
+This is a patch release that is required to integrate with the latest versions
+of the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). No other
+changes have been made.
+
+## 0.9.0 (January 28, 2019)
+
+FEATURES:
+
+- `imports/strings`: Added a `join` function to the `strings` import. This can
+ be used to join a list into a string with a specific separator.
+ Multi-dimensional lists and all Sentinel primitives are supported.
+
+## 0.8.1 (January 17, 2019)
+
+BUG FIXES:
+
+- `lang/eval`: Fixed a bug that prevents the effective use of `return`
+ statements in `for` loops.
+
+## 0.8.0 (January 14, 2019)
+
+FEATURES:
+
+- Mocks can now be represented by Sentinel code. This allows for the mocking of
+ functions and other complex data structures that cannot be represented in
+ JSON. For more information on using this feature via mocks, click
+ [here](https://docs.hashicorp.com/sentinel/commands/config#mocking-with-sentinel-code).
+
+IMPROVEMENTS:
+
+- Import validation has now been moved to the semantic checking phase. This
+ should result in better reporting of validation errors. In addition, import
+ validation will now enforce the use of an `as` identifier when an import path
+ is not a valid identifier on its own (example: `import "foo/bar"`).
+
+## 0.7.0 (December 12, 2018)
+
+BUG FIXES:
+
+- There have been changes to the runtime in how scope is handled over multiple
+ policy executions. Scope is now correctly unique per single policy execution,
+ and values set or builtins that are overridden in one policy will no longer
+ affect those values within another.
+
+## 0.6.0 (November 30, 2018)
+
+FEATURES:
+
+- `imports/runtime`: This new import allows for one to check various aspects of
+ the Sentinel runtime as it may be embedded in the simulator or a specific
+ implementation. For now, it allows the version to be checked.
+
+## 0.5.1 (November 28, 2018)
+
+IMPROVEMENTS:
+
+- `imports/time`: Added the `zone` and `zone_string` attributes to assist with
+ validation of a timespace's zone.
+- `command/fmt`: Added a new -check flag. This option does not commit changes,
+ but instead checks to see what files need formatting and outputs them on
+ stdout.
+
+BUG FIXES:
+
+- `command/test`: Ensure that passing test results are correctly output one per
+ line. Tests are also now run in a deterministic fashion based on
+ lexicographical (alphabetical) order.
+- `imports/time`: `month_name` and `weekday_name` will now show up correctly in
+ a returned timespace result.
+
+## 0.5.0 (November 5, 2018)
+
+IMPROVEMENTS:
+
+- `spec`: Selectors can now contain any reserved word (example: `rule`) or
+ keyword operator (example: `any`, `all`, `is`, `not`). This only works for the
+ _selector_ part of the expression (after the first period) - the first primary
+ expression (before the first period) still needs to be an identifier that does
+ not conflict with reserved words.
+
+BUG FIXES:
+
+- The simulator should now display import function call names correctly in
+ import errors.
+
+## 0.4.0 (October 1, 2018)
+
+FEATURES:
+
+- `builtin`: Added the `bool` built-in type conversion function. Booleans will
+ also now accepted as conversion into other values as well, with the full list
+ of behaviors available in the spec.
+
+## 0.3.2 (September 27, 2018)
+
+FEATURES:
+
+- `command/apply`: `sentinel apply` now prints out messages output by the
+ `print()` function when a trace is output on policy failure, or when a trace
+ is forced with `-trace`.
+- `imports/time`: Added the `month_name` and `weekday_name` keys to the
+ timespace, which return full-English names for the month and day of the week.
+
+BUG FIXES:
+
+- `command/fmt`: `sentinel fmt -` Will no longer print out the filter status
+ message on the output stream when `-write=false` Is not explicitly stated.
+ This brings the behavior of the command in line with the help text.
+- `runtime`: Index operations on the right-hand-side that have negative indexes
+ that go out of range (example: `length(list) * -1 - 1`) now correctly return
+ `undefined`. left-hand-side index assignments with a out-of-range negative
+ index still return runtime errors.
+
+## 0.3.1 (August 3, 2018)
+
+BUG FIXES:
+
+- `runtime`: Basic index assignment has been implemented as per the spec.
+- `runtime`: Index expressions for lists with negative indexes will no longer panic if the
+ list index is less than `length(list) * -1`.
+
+## 0.3.0 (July 20, 2018)
+
+FEATURES:
+
+- **New standard import: `types`.** This can be used to dynamicaly detect the
+ type of some value.
+
+## 0.2.0 (April 11, 2018)
+
+FEATURES:
+
+- **New standard import: `json`.** Marshal and unmarshal JSON documents and
+ access their contents as native Sentinel values.
+- **`break` and `continue`.** These are now both specified and implemented.
+ `break` allows loop exiting and `continue` allows immediate execution of the
+ next iteration.
+
+IMPROVEMENTS:
+
+- runtime: `print()` map values are now ordered alphabetically by keys.
+
+BUG FIXES:
+
+- command/test: If no `test` block exists, test behaves like it is asserting
+ `main: true`.
+- runtime: default maximum stack depth to 500
+- runtime: `print()` map values now appear like more typical maps.
+- runtime: division by zero is an error, not a crash
+- runtime: plugins that send map values with `null` values now decode properly
+ into native Sentinel values.
+
+## 0.1.0 (September 19, 2017)
+
+Initial release.
+
+
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/commands/apply.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/commands/apply.mdx
new file mode 100644
index 0000000000..6aaccde291
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/commands/apply.mdx
@@ -0,0 +1,64 @@
+---
+page_title: 'Command: apply'
+sidebar_title: 'apply'
+sidebar_current: docs-commands-apply
+description: >-
+ The `sentinel apply` command is used to execute a policy locally for
+ development purposes.
+layout: docs
+---
+
+# Command: `apply`
+
+The `sentinel apply` command is used to execute a policy locally for development
+purposes.
+
+## Usage
+
+Usage: `sentinel apply [options] POLICY`
+
+This command executes the policy file at the path specified by POLICY. In
+addition to a path to a policy, POLICY can reference a label of a
+[policy](/sentinel/configuration#policies) entry in the configuration.
+
+Use the exit code of this command to determine the exact status of the policy
+evaluation. `0` is pass, `1` is fail, `2` is undefined (fail, but because the
+result was undefined), and `3` is a runtime error. Errors unrelated to the
+policy status itself are returned with an exit status of `9`.
+
+To control the behavior of the `apply` command, create a [configuration
+file](/sentinel/configuration). With this, you can define available
+[import plugins](/sentinel/concepts/imports), mock data, and global values.
+This can help you simulate a policy embedded within an application.
+
+As of the 0.16 release, POLICY is optional. When it is not supplied,
+`sentinel apply` will run **all** policies in the supplied configuration.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-config=path` - Path to a configuration file specifying available imports,
+ mock data, globals, etc. The default is `sentinel.[hcl|json]`.
+
+- `-json` Enable JSON output. Using this mode, all other output will be
+ suppressed and the full detail of the apply will be output in a
+ machine-readable format. Enabling this implies `-trace`. See the section on
+ [tracing JSON output](/sentinel/writing/tracing#json-output) for more details.
+
+- `-json-rule=RULE` Enable JSON output, outputting only the value of the
+ specified rule. When running against a policy set, the policy must be supplied
+ to enable per-rule filtering. Implies `-json`.
+
+- `-global key=value` - Set global values. This is the same as setting `global`
+ in the configuration file, and will override any of these respective values
+ set in the configuration. The value is either a string, or a JSON number,
+ array, or object. To force strings, use quotes.
+
+- `-param key=value` - Set parameters, the same as setting `param` in the
+ configuration file. Values are handled in the same way they are with the
+ `-global` flag.
+
+- `-trace` - Always show the execution trace. This shows intermediate
+ boolean expression values. This always shows for failed policies.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/commands/fmt.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/commands/fmt.mdx
new file mode 100644
index 0000000000..5c85ce1019
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/commands/fmt.mdx
@@ -0,0 +1,34 @@
+---
+page_title: 'Command: fmt'
+sidebar_title: 'fmt'
+sidebar_current: docs-commands-fmt
+description: The `sentinel fmt` command formats a policy source to a canonical format.
+layout: docs
+---
+
+# Command: `fmt`
+
+The `sentinel fmt` command formats a policy source to a canonical format.
+
+## Usage
+
+Usage: `sentinel fmt [options] FILE ...`
+
+This command formats all the specified policy files to a canonical format.
+
+By default, policy files are overwritten in place. This behavior can be
+changed with the `-write` flag. If a specified FILE is `-` then stdin is
+read and the output is always written to stdout.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-write=true` - Write formatted policy to the named source file. If false,
+ output will go to stdout. If multiple files are specified, the output will
+ be concatenated directly.
+
+- `-check=false` - Don't format, only check if formatting is necessary. Files
+ that require formatting are printed, and a non-zero exit code is returned if
+ changes are required.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/commands/index.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/commands/index.mdx
new file mode 100644
index 0000000000..7177093cec
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/commands/index.mdx
@@ -0,0 +1,23 @@
+---
+page_title: Commands (CLI)
+sidebar_current: docs-commands
+description: >-
+ Sentinel provides a `sentinel` command-line interface (CLI) for developing and
+ testing policies.
+layout: docs
+---
+
+# Sentinel CLI Commands
+
+The Sentinel command-line interface (CLI) allows for the developing and testing
+of policies outside of a particular Sentinel implementation. Having a standard
+workflow to develop policies is critical for our mission of [policy as
+code](/sentinel/concepts/policy-as-code).
+
+The CLI takes a subcommand to execute. The complete list of subcommands is in
+the navigation to the left.
+
+The Sentinel CLI is a well-behaved command line application. In erroneous cases,
+a non-zero exit status will be returned. It also responds to -h and --help as
+you'd expect. To view a list of the available commands at any time, just run
+`sentinel` with no arguments.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/commands/test.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/commands/test.mdx
new file mode 100644
index 0000000000..cc974ffbad
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/commands/test.mdx
@@ -0,0 +1,47 @@
+---
+page_title: 'Command: test'
+sidebar_title: 'test'
+sidebar_current: docs-commands-test
+description: The `sentinel test` command runs policies against the Sentinel test framework.
+layout: docs
+---
+
+# Command: `test`
+
+The `sentinel test` command runs policies against the Sentinel test framework.
+
+To learn more about testing, see the [testing
+policies](/sentinel/writing/testing) page.
+
+## Usage
+
+Usage: `sentinel test [options] [PATH] ...`
+
+This command runs the defined test cases against a set of policies.
+
+The test cases are expected to live at the path `test//*.[hcl|json]` relative
+to the policy being tested. `` should be the name of the policy
+excluding the file extension. The files should be in the [configuration
+file structure](/sentinel/configuration). The `test` key is used for
+assertions.
+
+The PATH arguments specified may be a list of zero or more files or directories.
+When no arguments are given, it defaults to the current directory. When a
+directory is given, all policies ending with the extension `.sentinel` are
+tested.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-json` - Suppress normal output and output test results as a JSON document.
+ See the [testing JSON output](/sentinel/writing/testing#test-json-output)
+ section for more details.
+
+- `-run=regexp` - Run only tests matching the regular expression pattern.
+ A `/` splits policy name and test case name.
+
+- `-verbose` - Show tracing and log output for tests that succeed in addition
+ to tests that fail. By default, traces and logs are only shown for tests that
+ fail.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/concepts/enforcement-levels.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/concepts/enforcement-levels.mdx
new file mode 100644
index 0000000000..673ae3865e
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/concepts/enforcement-levels.mdx
@@ -0,0 +1,45 @@
+---
+page_title: Enforcement Levels
+sidebar_title: Enforcement Levels
+sidebar_current: docs-concepts-levels
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Enforcement Levels
+
+Enforcement levels are a first class concept in Sentinel allowing pass/fail
+behavior to be associated separately from the policy logic. This enables
+any policy to be a warning, allow overrides, or be absolutely mandatory.
+Because this level is not part of the policy body itself, different uses of
+the same policy can have different enforcement levels.
+
+Sentinel has three enforcement levels:
+
+- **Advisory:** The policy is allowed to fail. However, a warning should be
+ shown to the user or logged.
+
+- **Soft Mandatory:** The policy must pass unless an override is specified.
+ The semantics of "override" are specific to each Sentinel-enabled application.
+ The purpose of this level is to provide a level of privilege separation
+ for a behavior. Additionally, the override provides non-repudiation since
+ at least the primary actor was explicitly overriding a failed policy.
+
+- **Hard Mandatory:** The policy must pass no matter what. The only way to
+ override a hard mandatory policy is to explicitly remove the policy.
+ Hard mandatory is the default enforcement level. It should be used in
+ situations where an override is not possible.
+
+## Configuring Enforcement Levels
+
+Enforcement levels are configured when a policy is deployed to a
+Sentinel-enabled application. The exact mechanism that the level is specified
+is determined by each application. Please reference the documentation for
+your Sentinel-enabled application for more information.
+
+Enforcement levels are not configured and are not known by the policy body
+itself. All policies should be written to describe exactly the behavior
+they're attempting to control. For example, a policy that restricts deploys
+to business hours should be written exactly like so. When that policy is
+configured on an application, the operator may specify that it is advisory,
+soft, or hard mandatory.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/concepts/imports.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/concepts/imports.mdx
new file mode 100644
index 0000000000..844a06ed59
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/concepts/imports.mdx
@@ -0,0 +1,34 @@
+---
+page_title: Imports
+sidebar_title: Imports
+sidebar_current: docs-concepts-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom import](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only
+local context for making policy decisions.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that the
+import calendar allows access to engineer calendars. This import only exists
+for the purpose of this example and at the time of writing is not a real
+available import.
+
+```sentinel
+import "calendar"
+
+// Get the calendar for Bob for today
+bob_calendar = calendar.for("bob").today
+
+// Allow this policy to pass if Bob is not on vacation.
+main = rule { not bob_calendar.has_event("vacation") }
+```
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/concepts/index.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/concepts/index.mdx
new file mode 100644
index 0000000000..ce7ac51454
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/concepts/index.mdx
@@ -0,0 +1,15 @@
+---
+page_title: Basic Concepts
+sidebar_current: docs-concepts
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Basic Concepts
+
+This section covers some high level basic concepts that are important
+to understand for day to day Sentinel usage. Every page in
+this section is recommended reading for anyone consuming
+or extending Sentinel.
+
+Please use the navigation to the left to learn more about a topic.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/concepts/language.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/concepts/language.mdx
new file mode 100644
index 0000000000..dd083d0720
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/concepts/language.mdx
@@ -0,0 +1,92 @@
+---
+page_title: Policy Language
+sidebar_title: Policy Language
+sidebar_current: docs-concepts-language
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Policy Language
+
+Sentinel defines and uses its own [policy language](/sentinel/language).
+
+The language was designed to be approachable by non-programmers, since
+there are many use cases where the individual defining policy may not
+be a developer. However, the language includes constructs that
+are familiar to developers to enable powerful policies.
+
+To learn more about the language, please see the
+[writing policy section](/sentinel/writing)
+or the [language reference](/sentinel/language).
+
+An example of the policy language is shown below:
+
+```sentinel
+import "time"
+
+# Validate time is between 8 AM and 4 PM
+valid_time = rule { time.now.hour >= 8 and time.now.hour < 16 }
+
+# Validate day is M - Th
+valid_day = rule {
+ time.now.weekday_name in ["Monday", "Tuesday", "Wednesday", "Thursday"]
+}
+
+main = rule { valid_time and valid_day }
+```
+
+## Why?
+
+To explain why Sentinel defines and uses its own language, the question
+can be more easily split into roughly two types of languages: _configuration_ languages
+and _programming_ languages.
+
+### Configuration Languages
+
+Configuration languages are formats such as JSON, YAML, XML, etc. Many applications
+use configuration languages as their ACL format. Configuration languages
+are good for static, declarative information. ACL systems are a good fit
+for this. ACL systems are focused, providing the limiting options necessary
+to restrict access to a system.
+
+Configuration languages are not good for dynamic or logical rules. They
+typically only contain limited conditions, loops, or functions. Further
+configuration is often declarative, which can introduce complexities to
+logical statements that depend on ordering.
+
+The use cases that Sentinel was built for require the ability to perform
+complex behavior that didn't model well into a configuration format or a
+declarative form.
+
+### Programming Languages
+
+The Sentinel language was designed with the following goals:
+
+- **Non-programmer friendly.** Sentinel was built to be used by non-programmers.
+ For the use cases we discovered, non-programmers needed the ability to
+ enforce certain rules within a system. For example, a person responsible for
+ compliance may need to insert rules into a system.
+
+- **Programmer friendly.** At the same time, the language needed to support
+ programmer-friendly constructs such as conditionals, loops, and functions
+ for complex policies that a programmer may be writing.
+
+- **Embeddable.** Sentinel is embedded in existing software. The language
+ itself needs to be easily embeddedable. Further, Sentinel was designed
+ to be embedded in [HashiCorp](https://www.hashicorp.com) software
+ written in Go, so it needed to be easily embeddable in Go.
+
+- **Safe.** The language is used in highly security-sensitive environments.
+ It must not be able to crash its host system or access system resources
+ without explicit approval.
+
+Prior to building our own language, Sentinel evaluated other languages.
+Some languages worked quite well. As we continued to develop Sentinel, we
+found that the flexibility and control over designing our own language
+outweighed the costs.
+
+Because the language itself doesn't need to be general purpose, building
+a language focused on policy proved to be the best route for Sentinel.
+It allows us to build powerful tracing capabilities for debugging, make
+interesting performance choices, and extend the language as needed in the
+future.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/concepts/policy-as-code.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/concepts/policy-as-code.mdx
new file mode 100644
index 0000000000..9e3a530b31
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/concepts/policy-as-code.mdx
@@ -0,0 +1,73 @@
+---
+page_title: Policy as Code
+sidebar_title: Policy as Code
+sidebar_current: docs-concepts-pac
+description: >-
+ Policy as code is the idea of writing code in a high-level language to manage
+ and automate policies. By representing policies as code in text files, proven
+ software development best practices can be adopted such as version control,
+ automated testing, and automated deployment.
+layout: docs
+---
+
+# Policy as Code
+
+Policy as code is the idea of writing code in a high-level language to
+manage and automate policies. By representing policies as code in text files,
+proven software development best practices can be adopted such as version
+control, automated testing, and automated deployment.
+
+Many existing policy or ACL systems do not practice policy as code. Many
+policies are set by clicking in a GUI, which isn't easily repeatable nor
+versionable. They usually don't provide any system for testing policies
+other than testing an action that would violate the policy. This makes it
+difficult for automated testing. And the policy language itself varies by
+product.
+
+Sentinel is built around the idea and provides all the benefits of policy as code.
+
+## Benefits
+
+Policy as code provides a number of benefits:
+
+- **Sandboxing.** Policies provide the guardrails for other automated systems.
+ As the number of automated systems grow, there is also a growing need to
+ protect those automated systems from performing dangerous actions. Manual
+ verification is too slow; policies need to be represented as code to
+ keep up with other automated systems.
+
+- **Codification.** By representing policy logic as code, the information
+ and logic about a policy is directly represented in code and can be augmented
+ with comments rather than relying on oral tradition to learn about the
+ reason for policies.
+
+- **Version Control.** Policies are encouraged to be stored as simple text
+ files managed by a version control system. This lets you gain all the
+ benefits of a modern VCS such as history, diffs, pull requests, and more.
+
+- **Testing.** Policies are just code. Their syntax and behavior can be
+ [easily validated with Sentinel](/sentinel/commands/test). This also encourages
+ automated testing such as through a CI. Paired with a VCS system, this
+ allows a pull request workflow to verify that a policy keeps the system
+ behavior as expected before merging.
+
+- **Automation.** With all policies as code in simple text files, various
+ automation tools can be used. For example, it is trivial to create tools
+ to automatically deploy the policies into a system.
+
+## Sentinel and Policy as Code
+
+Sentinel fully embraces policy as code in a number of ways:
+
+- **Language.** All Sentinel policies are written using the
+ [Sentinel language](/sentinel/concepts/language). This language is
+ made to inputted directly to text files. As an additional benefit,
+ all Sentinel-enabled applications share the same policy language.
+
+- **Development.** Sentinel provides a [CLI](/sentinel/commands)
+ for development and testing. This local CLI can be used to verify policies
+ before deploying them to a system.
+
+- **Testing.** Sentinel provides a [test framework](/sentinel/commands/test)
+ designed specifically for automation. This allows developers and CI systems
+ to further verify policies.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/configuration/index.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/configuration/index.mdx
new file mode 100644
index 0000000000..26effb7737
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/configuration/index.mdx
@@ -0,0 +1,324 @@
+---
+page_title: Sentinel CLI Configuration File Syntax
+sidebar_title: Configuration File Syntax
+sidebar_current: docs-configuration
+description: >-
+ The Sentinel CLI's configuration file can be used to control the
+ behavior of the simulator during apply and test operations.
+layout: docs
+---
+
+# Sentinel CLI Configuration File Syntax
+
+The Sentinel CLI's configuration file can be used to control the behavior
+of the simulator during [`apply`](/sentinel/commands/apply) and
+[`test`](/sentinel/commands/test) operations.
+
+## Usage
+
+The configuration file is used in different ways depending on what operation you
+are trying to execute.
+
+### Apply
+
+When using `sentinel apply`, supply the configuration file by using the
+`-config=FILE` flag, where `FILE` is the path to the configuration file. The
+default is `sentinel.[hcl|json]`.
+
+See the [apply command](/sentinel/commands/apply) reference for more
+details.
+
+### Test
+
+When using `sentinel test`, each file matching `test//*.[hcl|json]` is a
+configuration file representing a single test case, where `` is the name
+of the policy being tested. Configure assertions for each [test
+case](#test-cases) using the test section.
+
+See the [test command](/sentinel/commands/test) reference for more details.
+
+#### Remote sources
+
+When declaring a `policy` or `module` block within the configuration, a
+`source` attribute must be supplied. This `source` should declare the location
+of the resource, which can be either a local file or a URL pointing to a remote
+file. Examples of local `source` attributes are detailed both in the
+[Policies](#policies) and [Modules](#modules) sections of this page. Below
+are a few examples of remote `source` attributes.
+
+Example:
+
+```hcl
+policy "foo" {
+ source = "git::https://github.com/hashicorp/sentinel-example.git//main.sentinel"
+ enforcement_level = "hard-mandatory"
+}
+```
+
+The above example will fetch the policy from the provided URL and place it into
+the cache ready for use. Be sure to read the
+[Remote Sources](/sentinel/configuration/remote-sources) page for an
+in-depth overview.
+
+## Configuration File Reference
+
+The format of the configuration file is either JSON or HCL. The available
+blocks are:
+
+- `mock` - A mock import specification.
+- `policy` - Configuration for a [policy](/sentinel/writing).
+- `module` - Configuration for a [module](/sentinel/extending/modules).
+- `import` - Configuration for a [plugin](/sentinel/extending/plugins).
+- `global` - Data that is inserted into the global scope.
+- `param` - Values for [parameters](/sentinel/language/parameters) defined in the policy.
+- `test` - Test cases for the [test command](/sentinel/commands/test).
+
+### Mock Imports
+
+Mock imports allow running a policy with an
+[import](/sentinel/concepts/imports) that you may not have access to or is
+too difficult to run. For example, it can be easier to mock data for [Terraform
+Enterprise](https://www.terraform.io/docs/enterprise/sentinel/index.html)
+locally instead of having to run a workspace through a plan/policy check cycle.
+
+Mock imports are specified using the `mock` block, labelled by the import
+name that you want to mock. For example, if you wanted to mock the `time`
+import, you could create an entry with the `time` label pointing to the data
+you want to mock.
+
+The data can take one of two forms:
+
+#### Mocking static data
+
+Static data can be mocked directly via HCl using the `data` attribute on the
+`mock` block.
+
+Example:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ hour = 9
+ minute = 42
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```sentinel
+import "time"
+
+main = rule { time.now.hour is 9 }
+```
+
+#### Mocking with Sentinel code
+
+There are some Sentinel types that raw data cannot mock, such as functions,
+and maps that don't have string keys. To mock this data, you can use
+a Sentinel file itself. In this case, you can set the `module` attribute to
+a file with the Sentinel code in it, relative to the current working directory in
+the event of `apply`, or relative to the configuration file location in the
+event of `test`.
+
+Note that `main` is not required in a mock data file.
+
+Example:
+
+```hcl
+mock "foo" {
+ module = {
+ source = "mock-foo.sentinel"
+ }
+}
+```
+
+`mock-foo.sentinel` would contain:
+
+```sentinel
+bar = func() {
+ return "baz"
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```sentinel
+import "foo"
+
+main = rule { foo.bar() is "baz" }
+```
+
+### Policies
+
+The `policy` block allows for the the configuration of policies to assist
+with integrating into other commands.
+
+Each `policy` block has a label to identify the policy, with the block
+providing configuration of the policy. The available configuration options for
+policy are `source` and `enforcement_level`. The `source` key provides the
+location of the policy, while `enforcement_level` is currently used by
+integrations such as [Terraform Cloud](https://www.terraform.io/docs/cloud/sentinel/manage-policies.html#enforcement-levels). For more information on the `source`
+value, see [Policy and Module Sources](#policy-and-module-sources).
+
+```hcl
+policy "foo" {
+ source = "foo.sentinel"
+ enforcement_level = "hard-mandatory"
+}
+```
+
+### Modules
+
+The `module` block allows you to map a Sentinel import to a
+[module](/sentinel/extending/modules). The Sentinel CLI will parse the module
+source and make it available for evaluation with the policy.
+
+Each `module` block has a label aligning to the name of the import, with
+the block set to the configuration object for the module. Currently, the only
+value within the configuration object is `source`, which points to the
+location of the module. For more information on the `source` value, see
+[Policy and Module Sources](#policy-and-module-sources).
+
+```hcl
+module "foo" {
+ source = "modules/foo.sentinel"
+}
+
+module "bar" {
+ source = "modules/bar.sentinel"
+}
+```
+
+Note when using [`sentinel test`](/sentinel/commands/test), module paths must be
+specified relative to the location of the configuration file.
+
+```hcl
+module "foo" {
+ source = "../../modules/foo.sentinel"
+}
+
+module "bar" {
+ source = "../../modules/bar/sentinel"
+}
+```
+
+See [Writing and Using a
+Module](/sentinel/extending/modules#writing-and-using-a-module) for more details
+on using a module with this configuration.
+
+### Plugins
+
+Plugins allow you to run a real [import plugin](/sentinel/extending/plugins)
+with a policy. The Sentinel CLI will launch the plugin, connect to it, configure
+it, and execute it as needed by the policy.
+
+Import plugins are specified by the `import` key. The value of this is a map
+where the key is the name of the import and the value is another map with
+configuration about the plugin. The configuration map has the following keys:
+
+- `path` (string, required) - Path to the import plugin executable.
+- `args` (list of string, optional) - A list of arguments to pass to the
+ executable when starting it.
+- `env` (map of string to string, optional) - A set of environmental variables
+ to set when launching the plugin.
+- `config` (map, optional) - Configuration for the plugin itself. This is
+ specific to each individual plugin. Please reference the documentation for
+ the plugin for more information.
+
+Example:
+
+```hcl
+import "time" {
+ path = "/path/to/sentinel-import-time"
+ config = { "fixed_time": 1504155600 }
+}
+```
+
+With the above configuration, the following policy would pass assuming the
+plugin configuration is valid:
+
+```sentinel
+import "time"
+
+main = time.now.day == 31
+```
+
+### Global
+
+Global data is injected directly into the global scope of the policy.
+This can be used to simulate global data that may be injected by a
+Sentinel-enabled application.
+
+Global data is specified by the `global` key. The value is a map of
+variables to inject directly into the running policy.
+
+Example:
+
+```hcl
+global "time" {
+ value = {
+ now = {
+ day = 31
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass. Notice
+that we don't have to do any `import`. The values of `global` are
+injected directly into the global scope.
+
+```sentinel
+main = time.now.day == 31
+```
+
+### Parameters
+
+The `param` section allows you to supply values for the
+[parameters](/sentinel/language/parameters) found in a policy. Values
+entered here will satisfy required parameters in a policy, in addition to
+override any defaults. Note that any of the manual parameter value methods
+supported by `sentinel apply` will override the values set here.
+
+```hcl
+param "foo" {
+ value = "bar"
+}
+```
+
+### Test Cases
+
+Test cases specify the test cases for the [test command](/sentinel/commands/test).
+
+Tests are specified by the `test` key. The value of this is a map of
+string to boolean. The key is the name of a rule and the value is the
+expected value of that rule. If a rule is not specified, than any value is
+allowed.
+
+Example:
+
+```hcl
+test {
+ rules = {
+ main = true
+ valid_days = []
+ }
+}
+```
+
+For the policy:
+
+```sentinel
+valid_day = rule {
+ filter days as d {
+ d is "monday"
+ }
+}
+
+main = rule { valid_days is empty }
+```
+
+For more information, read about the [test command](/sentinel/commands/test).
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/configuration/remote-sources.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/configuration/remote-sources.mdx
new file mode 100644
index 0000000000..fbf22b9a03
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/configuration/remote-sources.mdx
@@ -0,0 +1,166 @@
+---
+page_title: Remote Sources
+sidebar_title: Remote Sources
+sidebar_current: docs-configuration-remote-sources
+description: >-
+ There are a number of options for formatting the URL provided to the
+ source attribute on policy and module blocks.
+layout: docs
+---
+
+# Remote Sources
+
+There are a number of options for formatting the URL provided to the
+`source` attribute on `policy` and `module` blocks. This flexibility
+should solve most use cases and allow for reusable policies and modules.
+
+### Supported Protocols
+
+-> **NOTE:** All protocols are supported on the CLI, however each production
+Sentinel integration may not. Be sure to read the appropriate integration
+documentation to check the supported protocols.
+
+- Local filesystem
+- Git
+- Mercurial
+- HTTP
+- Amazon S3
+- Google GCP
+
+### Forced Protocols
+
+Due to the ambiguous nature of URLs, it is possible to force a particular
+protocol to be used during the fetch. To achieve this, simply prefix the url
+with the appropriate protocol as listed below:
+
+- `file` - Local filesystem
+- `git` - Git
+- `hg` - Mercurial
+- `http` or `https` - HTTP
+- `s3` - Amazon S3
+- `gcp` - Google GCP
+
+Example:
+
+```
+git::http://github.com/hashicorp/sentinel.git
+```
+
+The above would download the provided HTTP URL using the Git protocol.
+
+### Protocol Options
+
+In addition to some global options, there are options available to specific
+protocols to perform features such as authentication.
+
+#### Subdirectories / File Pathing
+
+When supplying URLs that point to a directory (eg. `git`), a subdirectory and
+file can be provided by suffixing the URL with `//` and the path. For example,
+if we have a git repository that has a policy, `main.sentinel` in the root, we
+could directly fetch the file by using the following:
+
+```
+git::https://github.com/hashicorp/sentinel-docs-example.git//main.sentinel
+```
+
+Or, if the file was nested in the `policies` folder:
+
+```
+git::https://github.com/hashicorp/sentinel-docs-example.git//policies/main.sentinel
+```
+
+#### Checksumming
+
+For downloads of any protocol, you can automatically verify a checksum. To
+checksum a file, append a `checksum` query parameter to the URL. The parameter
+value can be in the format of type:value or just value, where type is "md5",
+"sha1", "sha256", "sha512" or "file" . The "value" should be the actual
+checksum value or download URL for "file". When type part is omitted, type will
+be guessed based on the length of the checksum string.
+
+```
+./foo.sentinel?checksum=md5:b7d96c89d09d9e204f5fedc4d5d55b21
+
+./foo.sentinel?checksum=b7d96c89d09d9e204f5fedc4d5d55b21
+
+./foo.sentinel?checksum=file:./foo.txt.sha256sum
+```
+
+#### Unarchiving
+
+An `archive` query parameter can be supplied to explicitly state what format
+to attempt to extract, if required. The supported formats are:
+
+- `tar.gz` and `tgz`
+- `tar.bz2` and `tbz2`
+- `tar.xz` and `txz`
+- `zip`
+- `gz`
+- `bz2`
+- `xz`
+
+If a URL has an extension that matches one of the supported formats, it will
+use that format to unarchive.
+
+```
+./file.zip
+```
+
+The above would use the `zip` format to unarchive.
+
+```
+./path/to/file?archive=zip
+```
+
+Would force the `zip` format. If for some reason you required archiving to be
+disabled, this can also be achieved by using the `false` value.
+
+```
+./path/to/file?archive=false
+```
+
+#### Git (`git`)
+
+- `ref` - The Git ref to checkout. This is a ref, so it can point to a commit
+ SHA, a branch name, etc. If it is a named ref such as a branch name, it will
+ be updated to the latest on each get.
+- `sshkey` - An SSH private key to use during clones. The provided key must be
+ a base64-encoded string. For example, to generate a suitable sshkey from a
+ private key file on disk, you would run base64 -w0 \.
+ **Note:** Git 2.3+ is required to use this feature.
+- `depth` - The Git clone depth. The provided number specifies the last `n`
+ revisions to clone from the repository.
+
+The git getter accepts both URL-style SSH addresses like
+`git::ssh://git@example.com/foo/bar`, and "scp-style" addresses like
+`git::git@example.com/foo/bar`. In the latter case, omitting the `git::` forced
+prefix is allowed if the username prefix is exactly git@.
+
+The "scp-style" addresses cannot be used in conjunction with the `ssh://`
+scheme prefix, because in that case the colon is used to mark an optional port
+number to connect on, rather than to delimit the path from the host.
+
+#### Mercurial (`hg`)
+
+- `rev` - The Mercurial revision to checkout.
+
+#### HTTP (`http` or `https`)
+
+Basic Authentication
+
+To use HTTP basic authentication, simply prepend `username:password@` to the
+hostname in the URL such as `https://Aladdin:OpenSesame@www.example.com/index.html`.
+All special characters, including the username and password, must be URL
+encoded.
+
+#### S3 (`s3`)
+
+The following query parameters are available and will take priority when
+authenticating against an S3 bucket.
+
+- `aws_access_key_id` - AWS access key.
+- `aws_access_key_secret` - AWS access key secret.
+- `aws_access_token` - AWS access token if this is being used.
+- `aws_profile` - Use this profile from local ~/.aws/ config. Takes priority
+ over the other three.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/consul.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/consul.mdx
new file mode 100644
index 0000000000..16b3f502a6
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/consul.mdx
@@ -0,0 +1,49 @@
+---
+page_title: Consul and Sentinel
+sidebar_title: Consul
+sidebar_current: docs-app-consul
+description: >-
+ Consul Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies are applied during writes to
+ the KV Store.
+layout: docs
+---
+
+# Consul
+
+[Consul Enterprise](https://www.hashicorp.com/products/consul/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies are applied during writes to the KV Store.
+
+Sentinel policies have access to the key/value being written. They can be used to
+allow or deny the modification. The information that Sentinel policies have access
+to will expand over time.
+
+The Consul integration with Sentinel is documented in depth in the
+[Consul Enterprise documentation](https://www.consul.io/docs/guides/sentinel.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Input validation depending on the name of the key.**
+
+```sentinel
+
+main = rule { valid_key() }
+
+required = [
+ ["port", "\\d+"], # ports must be integers
+ ["name", "\\w+"], # name must be a word
+]
+
+valid_key = func() {
+ for required as v {
+ if key is v[0] {
+ return value matches v[1]
+ }
+ }
+
+ return false
+}
+```
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/extending/index.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/extending/index.mdx
new file mode 100644
index 0000000000..f3843470f8
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/extending/index.mdx
@@ -0,0 +1,44 @@
+---
+page_title: Extending Sentinel
+sidebar_current: docs-extending
+description: Learn how to create your own Sentinel imports to extend Sentinel's functionality.
+layout: docs
+---
+
+# Extending Sentinel
+
+-> **NOTE:** Sentinel's extensibility is currently undergoing large
+improvements - watch this space for future updates!
+
+Sentinel can be extended by writing additional
+[imports](/sentinel/language/imports). These imports can bring new functionality
+to Sentinel by allowing access to new data or by the addition of new functions.
+This section is designed to show you how to accomplish this extensibility and
+describe how it works.
+
+Currently, the only usable way to add additional imports is via
+[modules](#modules). While [plugins](#plugins) currently work under the Sentinel
+CLI, no Sentinel integration currently supports their use.
+
+-> You may also be interested in advanced details on [import
+internals](/sentinel/extending/internals).
+
+## Modules
+
+Modules allow you to re-use Sentinel code as an import. Modules are loaded
+external of policies and mapped to imports. This is usually configured via a
+file such as the [CLI configuration file](/sentinel/configuration).
+
+See the [modules](/sentinel/extending/modules) section for more details.
+
+## Plugins
+
+-> **NOTE:** Plugins are currently only supported on the CLI and not in any
+production Sentinel integration, with the exception of existing configuration
+in [Nomad](https://www.nomadproject.io/docs/configuration/sentinel).
+
+Imports can also be implemented as plugins when Sentinel code itself is too
+restrictive to represent the import, or if you need access to features not
+normally available to the Sentinel runtime.
+
+See the [plugins](/sentinel/extending/plugins) section for more details.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/extending/internals.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/extending/internals.mdx
new file mode 100644
index 0000000000..1a222ad000
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/extending/internals.mdx
@@ -0,0 +1,253 @@
+---
+page_title: >-
+ Extending Sentinel: Internals
+sidebar_title: Internals
+sidebar_current: docs-extending-internals
+description: This Section covers some details about Sentinel's import internals.
+layout: docs
+---
+
+# Extending Sentinel: Internals
+
+-> **Advanced Topic!** This page covers low-level technical details of Sentinel.
+You don't need to understand these details to effectively use Sentinel. The
+details are documented here for those who wish to learn about them.
+
+This section covers some of the internal details of Sentinel's import system.
+The goal of this section is to help remove notion of "magic" from Sentinel, to
+allow you to trust and understand what Sentinel is doing with imports, and also
+to help practitioners and developers alike understand the differences between
+the ways that imports can be loaded into Sentinel.
+
+## Language and Runtime
+
+Understanding the import system in Sentinel generally requires understanding of
+two key concepts within Sentinel: the _language_, and the _runtime
+implementation_ of the language.
+
+The Sentinel language and the rules governing it are detailed in the [Sentinel
+Language Specification](/sentinel/language/spec). A runtime implementation must
+conform to the rules laid out in the specification at a minimum. However, to
+meet the design goals of a particular implementation, the runtime may implement
+features on top of the language. The subtle implementation details within the
+runtime may also vary while still being compliant with the specification.
+
+The core runtime included within the [Sentinel CLI](/sentinel/commands) is
+sometimes referred to as the _reference implementation_ of the Sentinel
+language. This runtime is also the main runtime embedded within each
+Sentinel-enabled HashiCorp product such as Terraform Cloud and Enterprise, Vault
+Enterprise, Nomad Enterprise, and Consul Enterprise. The runtime includes an API
+to allow these integrations implement Sentinel in as robust or as restrictive of
+a way as possible, so depending on the implementation, your experience may vary.
+
+## Sentinel's Import Model
+
+The concept of Imports within Sentinel is a _language_ feature. You can see the
+exact rules governing imports within the
+[Imports](/sentinel/language/spec#imports) section of the specification.
+
+Within the runtime, there are currently two major classifications of imports:
+
+- **Modules:** The mapping of Sentinel code to an import, essentially mapping a
+ scope of values to a particular package.
+- **Binary Imports:** A grouping of embedded and external plugins written using
+ the Sentinel SDK. These are comprised of internal or embedded imports (usually
+ the [standard imports](/sentinel/imports) and imports included by an
+ integration) and import plugins.
+
+Below is a diagram explaining in brief the relationship between the langauge
+and runtime features.
+
+
+
+This kind of topology ultimately minimizes the visibility of implementation
+internals to the actual policy language. This allows us to keep the Sentinel
+language itself simple and keep concerns such as module or plugin management,
+validation, and configuration out of the language. These kinds of things would
+impact the readability of a policy, possibly present security challenges, and
+would ultimately be of no use to more minimal embedded applications.
+
+### Implementation Differences Between Import Types
+
+As one would imagine, both modules and binary imports are loaded in much
+different ways, and the methods that they expose data to Sentinel differ as
+well. The effect is generally the same however across both, and we take care to
+ensure that both modules and binary imports behave as close as possible to each
+other, however there are some subtle differences:
+
+- Modules currently support the exporting of rules, but do not support function
+ calls with object receiver data (as seen in some standard imports such as
+ [`decimal`](/sentinel/imports/decimal), [`http`](/sentinel/imports/http), and
+ [`time`](/sentinel/imports/time)). This will be coming in a later release.
+- Binary imports cannot export rules. However, they do support object receiver
+ data (see [`framework.New`](/sentinel/extending/plugins#framework-new) in
+ [Extending Sentinel: Plugins](/sentinel/extending/plugins)).
+
+## Modules
+
+_Modules_ are essentially Sentinel code that is intended to be re-used in multiple
+policies as an import.
+
+The mechanism of module loading is fairly straightforward: during initialization
+of the Sentinel runtime, modules are parsed along with policies. The parsed
+_abstract syntax tree_ (AST) for each module is loaded into the runtime under
+the specified _import path_. These are then passed to the lower-level
+interpreter during the evaluation of each policy.
+
+As with policy code, during evaluation, a module's AST is walked to execute the
+code within that specific module. The module data is then stored within an
+specific scope mapped to the import name. The module data can then be accessed
+through the import via selector expressions and function calls (e.g.,
+`foo.some_map` or `foo.some_func()` for a module loaded via `import "foo"`).
+
+The AST for a module is only walked if a policy imports it, and it is _only
+walked once_. That means if a policy and a module import the same module, or a
+policy imports the same module twice (using [import
+aliases](/sentinel/language/imports#aliases)), those instances will share state.
+If you call a function that alters a singleton variable within the module, that
+singleton will be modified for all instances of the import.
+
+-> **Fun fact!** When you [mock an import with Sentinel
+code](/sentinel/configuration#mocking-with-sentinel-code), you are actually
+using a module. The module is loaded with a flag that allows it to override an
+existing import, which would normally give a runtime error.
+
+### Map and List Cloning for Module Calls
+
+It's also worthwhile noting that map and list values are cloned for modules.
+
+Consider the case where you have a module with a map singleton.
+
+```sentinel
+// modules/foo.sentinel
+a_map = {"a": "b"}
+```
+
+Normally, assignment to the singleton, even when using an index expression, is
+blocked, so `foo.a_map["c"] = "d"` would not pass semantic checking.
+
+However, even when you try to do assignment via an intermediary, you will still
+be only modifying the copy, and the original will not be affected:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+a_map_copy = foo.a_map
+a_map_copy["c"] = "d" // Only modifies copy, does not modify module singleton
+print(foo.a_map) // Still only prints {"a": "b"}
+```
+
+This is to ensure that modules are consistent with binary imports in how return
+data is handled, and the expectation that most non-object data within an import
+is read-only, with the exception of modification of singleton data via
+functions. As return of complex object and collection data in a binary import is
+always via copy, it's not possible to modify any value elements within the
+import in a similar fashion.
+
+There are also some other implications of this cloning that are of note:
+
+- Rules are not cloned, either within a list or map, or by themselves. This is
+ to ensure that [memoization](/sentinel/language/rules#lazy-and-memoized)
+ functions correctly. As only a module can export rules at this time, there is no
+ parity for this in binary imports.
+- Functions are _not_ cloned. This mainly has implications for scope - for
+ example, if you assign a function to another value, or assign an object to a
+ value that has a method that utilizes a global module variable, those calls will
+ reference and/or modify those values.
+
+Functions, while represented differently in binary imports, generally follow the
+same logic here - as they would be invoked in the same package within the binary
+import, any singleton values they accessed there would be affected in a similar
+fashion. As rules are pseudo-functions, this generally makes sense here too.
+
+## Binary Imports
+
+_Binary imports_ is a grouping referring to all imports that are designed with
+the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). These can be
+either internally embedded, or executed via a plugin.
+
+All imports found in the [standard library](/sentinel/imports) are binary
+imports, embedded internally.
+
+The main difference between internally embedded imports and external plugins is
+whether or not the runtime needs to execute a plugin binary as part of
+[lifecycle management](#lifecycle), and whether or not it needs to
+[communicate](#communication) over RPC. Internal binary imports do not require
+these steps, so their execution model is somewhat simpler; however, technically,
+they still are the same as external plugins in terms of design model and value
+conversion. Most standard imports are even tested using the [SDK test
+suite](/sentinel/extending/plugins#testing), which builds the import as an
+external plugin.
+
+The remainder of this section discusses topics mostly relevant to external
+plugins. A large amount of technical detail regarding binary import development
+(also applicable to embedded binary imports) can be seen on the
+[Plugins](/sentinel/extending/plugins) page.
+
+-> **NOTE:** At this time, plugins can only be written for the Sentinel CLI, and
+cannot be used with any of Sentinel's integrations.
+
+### Plugin Basics
+
+Sentinel plugins are built on top of the HashiCorp [go-plugin
+system](https://github.com/hashicorp/go-plugin). This is the same plugin system
+powering all pluggable HashiCorp tools such as
+[Terraform](https://www.terraform.io), [Vault](https://www.vaultproject.io), and
+more. go-plugin is a system that has been used in production for millions of
+users for over 5 years.
+
+Plugins are executable binaries. Sentinel is responsible for launching and
+managing the lifecycle of plugins. Once a plugin is running, Sentinel
+communicates with the plugin via [gRPC](https://grpc.io/). The [protocol is open
+source](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/import.proto)
+to allow anyone to write a Sentinel plugin.
+
+### Lifecycle
+
+Sentinel launches plugins and is responsible for plugin lifecycle.
+
+The runtime optimizes for latency by launching the plugin as soon as it is
+configured, rather than when a policy requires it. This ensures that the plugin
+is ready to be used immediately. A plugin is only closed when Sentinel is
+reconfigured to no longer allow that plugin or if the Sentinel-enabled
+application is closing.
+
+When a plugin is removed from the configuration, Sentinel may wait to shut down
+the plugin until all currently executing policies that are using that plugin
+complete. New policy executions will not be allowed to use the old plugins.
+
+Plugins are automatically restarted if they shut down unexpectedly. Policies
+that were executing while this happens may fail. The Sentinel system is
+improving to more gracefully understand locations where it is safe to retry an
+execution.
+
+### Communication
+
+An [initial
+handshake](https://github.com/hashicorp/go-plugin/blob/master/docs/internals.md)
+is done via stdout to determine the main communication location. Following the
+handshake, communication will either occur on a local Unix domain socket or via
+a local-only TCP socket. This communication is done mostly over the low-level
+[`Get`](https://github.com/hashicorp/sentinel-sdk/blob/2b4db07dd12b55142f0c016f301af80aa4422520/proto/import.proto#L12)
+RPC call.
+
+#### Value Conversion
+
+As can generally be seen in [Developing an Import
+Plugin](/sentinel/extending/plugins#developing-an-import-plugin), the Sentinel
+type system is not exposed to binary imports. While explicit values for null and
+undefined exist, values are mostly converted over the wire from their Go values
+to their Sentinel equivalents.
+
+This has a few implications:
+
+- As discussed in [Map and List Cloning for Module
+ Calls](#map-and-list-cloning-for-module-calls), Map and list data returned from
+ binary import value lookups or function calls is always cloned. These can be
+ freely modified without affecting anything within the binary import.
+- It's currently impossible to represent certain Sentinel types such as
+ [rules](/sentinel/language/rules) within a binary import. This is not a major
+ issue at this point in time as practitioners generally do not look to binary
+ imports for rules; we may examine this as a later time if this situation
+ changes.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/extending/modules.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/extending/modules.mdx
new file mode 100644
index 0000000000..87a9fbc055
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/extending/modules.mdx
@@ -0,0 +1,137 @@
+---
+page_title: >-
+ Extending Sentinel: Modules
+sidebar_title: Modules
+sidebar_current: docs-extending-modules
+description: >-
+ Modules allow you to re-use Sentinel code as an import.
+layout: docs
+---
+
+# Extending Sentinel: Modules
+
+Modules allow you to re-use Sentinel code as an import. This allows for the
+export of any general construct that Sentinel supports, including values,
+functions, and even rules. As such, it's a great way to package code that is
+useful across multiple policies, reducing boilerplate and making final policy
+code simpler.
+
+You may want to use modules for:
+
+- **Writing a simple re-usable helper library.** If you mostly know Sentinel or
+ have helpers that you have written in Sentinel, moving these helpers to a module
+ will offer a low-friction way of facilitating their re-use.
+- **Writing re-usable rules.** If you have a set of
+ [rules](/sentinel/language/rules) you know you want to use across multiple
+ policies, bundling them into a module is a great way of abstracting the logic
+ away.
+- **Abstracting existing Sentinel imports.** Using a module is a great way to
+ extend existing Sentinel imports by abstracting the common functionality that
+ you use within an import to your own workflow.
+
+This page describes how you can use modules within the context of the Sentinel
+CLI. For instructions for a particular integration, see the documentation for
+that product.
+
+-> **Not all Sentinel-enabled applications support modules.** Please refer
+to the documentation of the specific Sentinel-enabled application. Some
+applications disable modules for security reasons.
+
+## Configuring a Module
+
+A module is configured within the Sentinel CLI by adding an entry for it in
+within the `modules` section of the [CLI configuration
+file](/sentinel/configuration). The key for each entry specifies the path that
+the module is imported as. The `path` value within each entry specifies the path
+to the module file on the local filesystem.
+
+```hcl
+module "foo" {
+ source = "modules/foo.sentinel"
+}
+
+module "bar" {
+ source = "modules/bar.sentinel"
+}
+```
+
+In this example, we have two modules:
+
+- Import path `foo`, being loaded from the code within `modules/foo.sentinel`.
+- Import path `bar`, being loaded from the code within `modules/bar.sentinel`.
+
+See the [modules](/sentinel/configuration#modules) section within the CLI
+configuration file syntax page for more details.
+
+### Remote Modules
+
+In addition to loading from a local source, modules can be loaded from a remote
+location. This can increase reusability and allow for sharing modules across
+multiple configurations.
+
+### Testing Using Modules
+
+As with [mocks](/sentinel/configuration#mocking-with-sentinel-code), modules
+are loaded relative to the configuration file location when using `sentinel test`. As such, you need to account for this in your test configuration files:
+
+```hcl
+module "foo" {
+ source = "../../modules/foo.sentinel"
+}
+
+module "bar" {
+ source = "../../modules/bar.sentinel"
+}
+```
+
+This could be a test file for a policy (example: `policy.sentinel`), residing
+under the correct test file directory for this policy (example:
+`test/policy/pass.json`).
+
+## Writing and Using a Module
+
+Writing a module is nearly the same as writing a Sentinel policy, except for the
+following two exceptions:
+
+- Modules do not require [`main`](/sentinel/writing/basic#the-simplest-policy)
+ to be present. It can be, but it will not carry any extra significance as it
+ does within a policy.
+- [`param`](/sentinel/language/parameters) declarations cannot be present in a module. If they are encountered,
+ a runtime error is given.
+
+Once you have a complete module ready for use and configured, using the module
+is as simple as importing it.
+
+Building on the above [configuration example](#configuring-a-module), we can
+export a simple test function in the module `foo` by adding this code to
+`modules/foo.sentinel`:
+
+```sentinel
+// modules/foo.sentinel
+hello = func() {
+ print("hello world!")
+ return undefined
+}
+```
+
+We can then import it within our policy and use it:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+// prints "hello world" in the trace
+foo.hello()
+
+main = true
+```
+
+Note that modules are considered [imports](/sentinel/language/spec#imports) by
+the Sentinel runtime and as such conform to the specification. This means that
+you cannot directly assign values to anything behind a module scope. You can,
+however, use functions to change state.
+
+-> **NOTE:** At this time, modules do not support object receiver data in the
+way that some imports do, like [`time`](/sentinel/imports/time),
+[`decimal`](/sentinel/imports/decimal), and [`http`](/sentinel/imports/http).
+Support for this will be coming in later versions of Sentinel.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/extending/plugins.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/extending/plugins.mdx
new file mode 100644
index 0000000000..05fe702e54
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/extending/plugins.mdx
@@ -0,0 +1,522 @@
+---
+page_title: >-
+ Extending Sentinel: Plugins
+sidebar_title: Plugins
+sidebar_current: docs-extending-plugins
+description: >-
+ Plugins allow you to write imports as standalone executables, allowing you to
+ extend Sentinel in ways not normally possible with policy code alone.
+layout: docs
+---
+
+# Extending Sentinel: Plugins
+
+-> **NOTE:** Plugins are currently only supported on the CLI and not in any
+production Sentinel integration, with the exception of existing configuration
+in [Nomad](https://www.nomadproject.io/docs/configuration/sentinel).
+
+Plugins allow you to write imports as standalone executables, allowing you to
+extend Sentinel in ways not normally possible with policy code alone.
+
+You might want to write or use a plugin when you need to:
+
+- **Use a provider or API integration for Sentinel.** In this case, you will
+ probably be working with a provider SDK, or other libraries that will be making
+ complex network requests to external services. Sentinel only provides limited
+ capabilities for these kinds of operations in the standard library, so writing
+ this kind of import as a module is not optimal.
+- **Introduce novel functionality not currently supported by Sentinel.**
+ Sentinel's policy language is limited by design to encourage simple policies,
+ reduce its runtime footprint, and to keep it efficient and safe. This will
+ mean that some functionality may be difficult or impossible to express as
+ Sentinel code, and would require a plugin to write.
+- **Extend a complex module.** Additionally, modules are restricted to a single
+ file of Sentinel code, so these will naturally become more and more unwieldy as
+ you continue to add to them. Eventually, there might be a time where a plugin is
+ better suited for the functionality, especially if it's a general-purpose
+ library.
+
+This section details how import plugins are currently configured in the Sentinel
+CLI, and some detail on how they are written. As we continue to build on the
+ability to use import plugins, we will extend this section with more details.
+
+-> **Not all Sentinel-enabled applications will support plugins.** Some
+applications will disable plugins for security reasons. For those that do enable
+plugins, the method to configure plugins will vary.
+
+## Installing Plugins
+
+Sentinel plugins are standalone executables. Sentinel-enabled applications such
+as the CLI launch these plugins and communicate with them via
+[RPC](https://en.wikipedia.org/wiki/Remote_procedure_call). To install a plugin,
+the first step is to download the plugin executable.
+
+After downloading the plugin, you must add it in the CLI configuration file,
+setting its name, path, and any arguments, environment variables, or
+configuration. An example is below:
+
+```json
+{
+ "imports": {
+ "time": {
+ "path": "/path/to/sentinel-import-time",
+ "config": { "fixed_time": 1504155600 }
+ }
+ }
+}
+```
+
+After configuring the plugin, it will be available on the next `sentinel apply`
+or `sentinel test`.
+
+For more details on configuration, see the
+[plugins](/sentinel/configuration#plugins) section of the [CLI configuration
+file syntax](/sentinel/configuration).
+
+## Debugging Plugins
+
+The Sentinel CLI logs when it launches, configures, and closes a plugin. The
+plugin may also log additional information when it is running.
+
+To debug issues with a plugin, you can usually refer to these logs. Depending on
+the plugin configuration, the logs you see may vary - refer to the plugin
+documentation on how to enable logs for a particular plugin.
+
+## Developing an Import Plugin
+
+Anyone can develop a Sentinel import plugin using the [Sentinel
+SDK](https://github.com/hashicorp/sentinel-sdk). The SDK contains a high-level
+framework for writing plugins in [Go](https://golang.org) including a test
+framework. Additionally, the SDK contains the low-level [gRPC protocol buffers
+definition](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/import.proto)
+for writing plugins in other languages.
+
+The rest of this page will document how to write a new Sentinel import in Go
+using the Sentinel SDK and the high-level framework provided. You are expected
+to already know the basics of Go and have a properly configured Go installation.
+
+Throughout this page, we'll be developing a simple import to access time values.
+
+-> **NOTE:** The example here is not reflective of the actual implementation of
+the [`time`](/sentinel/imports/time) standard import, so make sure to not
+get the two confused.
+
+### Import Framework
+
+The Sentinel SDK provides a high-level
+[framework](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework)
+for writing imports. We recommend using this framework.
+
+#### Basic Concepts
+
+This framework works by implementing the correct Go interfaces.
+
+As a general overview:
+[`framework.Import`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Import)
+implements the
+[`sdk.Import`](https://godoc.org/github.com/hashicorp/sentinel-sdk#Import)
+interface and therefore is a valid import plugin. You must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface to configure the import. The root may then delegate nested access to
+various
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+implementations. Other interfaces are implemented to provide functionality past
+what is provided by the basic `framework.Namespace` implementation - these are
+documented below.
+
+This all may sound like a lot of interfaces, but each interface typically only
+requires a single function implementation and you're only required to implement
+a single namespace (`framework.Namespace`).
+
+As we move on, we'll use real examples to make this clear.
+
+#### Implementing framework.Root
+
+To begin, you must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface. This is the interface representing the root of your import. The root
+itself must be implement
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace),
+which allows values to be accessed. Note that the root can optionally implement
+other interfaces, but they're more advanced and omitted here for simplicity.
+
+For our time example, our root may look like this:
+
+```sentinel
+type root struct {
+ time time.Time
+}
+
+// framework.Root impl.
+func (m *root) Configure(raw map[string]interface{}) error {
+ if _, ok := raw["timestamp"]; !ok {
+ raw["timestamp"] = time.Now().Unix()
+ }
+
+ v := raw["timestamp"]
+ timestamp, ok := v.(int)
+ if !ok {
+ return fmt.Errorf("invalid timestamp type %T", v)
+ }
+
+ m.time = time.Unix(timestamp, 0).UTC()
+ return nil
+}
+
+// framework.Namespace impl.
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "minute":
+ return m.time.Minute(), nil
+ }
+
+ return nil, nil
+}
+```
+
+This example implements `framework.Root` and `framework.Namespace` and would
+enable the following within a Sentinel policy once the completed import is
+installed.
+
+```sentinel
+import "time"
+
+print(time.minute)
+main = true
+```
+
+#### framework.Namespace
+
+The
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+interface implements a namespace of values. You saw above that
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+must itself be a namespace. However, a namespace `Get` implementation may
+further return namespaces to access nested values.
+
+This enables behavior such as `time.month.string` vs. `time.month.index`.
+Notice each of these examples accesses a nested value within `month`. This can
+be modeled as namespaces within the framework.
+
+In the example below, we return a new namespace for `month` to do just this:
+
+```sentinel
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "month":
+ return &namespaceMonth{Month: m.time.Month()}, nil
+ }
+
+ // ...
+}
+
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil nil
+}
+```
+
+#### Primitive Types and Structs
+
+A namespace may also return any primitive Go type as well as structs. The
+framework automatically exposes primitive values as you would expect. For
+structs, exported fields are lowercased and exposed to the policies. The
+`sentinel` struct tag can be used to control how Sentinel can access the field.
+
+In the example below, we expose a struct directly from the root namespace:
+
+```sentinel
+type Location struct {
+ Name string
+ TimeZone string `sentinel:"time_zone"`
+}
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "location":
+ return &Location{Name: "somewhere", TimeZone: "some zone"}, nil
+ }
+
+ // ...
+}
+```
+
+This makes the following values work within a Sentinel policy:
+`time.location.name`, `time.location.time_zone`.
+
+If a field has an empty `sentinel` struct tag (example: `sentinel:""`),
+then that field will not be accessible from a policy.
+
+#### Optional Interfaces
+
+The following are optional interfaces that can be implemented on top of
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace).
+All of these interfaces can be implemented at any level, except for
+[`framework.New`](#framework-new), which only works at the root level.
+
+##### `framework.Call`
+
+The
+[`framework.Call`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Call)
+interface can be implemented to support function calls.
+
+Implementing this interface is very similar to attribute access, except instead
+of returning the attribute value, you return a function. The framework uses Go
+reflection to determine the argument and result types and calls it. If the
+argument types do not match or the signature is otherwise invalid, an error is
+returned.
+
+For example, let's implement a function to add months to the current month:
+
+```sentinel
+func (m *root) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *root) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.time.AddDate(0, n, 0).Month()}
+}
+```
+
+You can now call the function. If today was in the month of September,
+the policy below would pass:
+
+```sentinel
+import "time"
+
+main = time.add_month(4).string == "January"
+```
+
+##### `framework.Map`
+
+The optional
+[`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+interface allows an alternative method to to present a namespace back to a
+Sentinel policy as a map.
+
+`framework.Map` is best paired with
+[`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys),
+which allows you to quickly fetch the necessary data via `Get` calls on the
+namespace:
+
+```
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil, nil
+}
+
+func (m *namespaceMonth) Map() (map[string]interface{}, error) {
+ return framework.MapFromKeys(m, []string{"string", "index"})
+}
+```
+
+##### `framework.New`
+
+The optional
+[`framework.New`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#New)
+interface can be implemented on a root namespace to add _methods_ to your
+namespaces.
+
+Data returned from a namespace is memoized and returned as a map, and is
+normally not callable - to call a function to operate on the data, you would
+need to create a new namespace from the top-level of the import, and then make a
+function call on the result within the same expression.
+
+Let's consider the [root example](#implementing-framework-root). Let's say,
+instead of hard-coding the time value at configuration, we wanted to load it
+on-demand using a `time.now` key, and allow `add_month` to be callable on that
+value. Our root and de-coupled time namespace would now look like:
+
+```
+type root struct{}
+
+func (m *root) Configure(raw map[string]interface{}) error { return nil }
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "now":
+ return &namespaceTime{Time: time.Now()}
+ }
+
+ return nil
+}
+
+func (m *root) New(data map[string]interface{}) (framework.Namespace, error) {
+ if v, ok := data["unix"]; ok {
+ if t, ok := v.(int64); !ok {
+ return &namespaceTime{Time: time.Unix(t, 0)}
+ }
+
+ return nil, fmt.Errorf("expected timestamp to be int64, got %T", v)
+ }
+
+ return nil, nil
+}
+
+type namespaceTime struct {
+ Time time.Time
+}
+
+func (m *namespaceTime) Get(key string) interface{} {
+ switch key {
+ case "unix":
+ return m.Time.Unix()
+
+ // case ...
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) Get(key string) (interface{}, error) {
+ return framework.MapFromKeys(m, []string{"unix", "..."})
+}
+
+func (m *namespaceTime) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.Time.AddDate(0, n, 0).Month()}
+}
+```
+
+Via this example, we can now create and assign a `namespaceTime` using `t = time.now`, and then call `t.add_month(months_to_add)` to return a
+`namespaceMonth` for the corresponding month.
+
+Methods on a namespace can also _modfiy_ the receiver data. So you can, for
+example, add a method named `increment_month` that takes no arguments, but
+increments the time stored in `namespaceTime.Time` by a month. Subsequent
+statements using the receiver would see the new value.
+
+Note that there are some restrictions and guidelines that you should take into
+account when using `framework.New`:
+
+- Only data that makes it back to a policy can be used as receiver data to
+ instantiate new namespaces. As such it's recommended to make use of
+ [`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+ and
+ [`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys)
+ to return all of the attribute data you need to construct new objects.
+- `framework.New` is only supported on the root namespace and has no effect on
+ namespaces below the root. Check all possible cases of receiver data within
+ the top-level `New` function and return the appropriate namespace based on the
+ input data.
+- Do not return general errors from your `New` method. When an unknown lookup is
+ made off of memoized data, it will hit your `New` method for possible
+ instantiation and key calls. This will ensure `undefined` is correctly
+ returned for these cases.
+- `framework.New` is designed to add utility to imports where calling methods on
+ a value is more intuitive than just making a function call, or just returning
+ all of a data set as a memoized map. Use it sparingly and avoid using it on
+ recursively complex data sets.
+
+### Testing
+
+The SDK exposes a testing framework to verify your plugin works as
+expected. This test framework integrates directly into `go test` so it
+is part of a familiar workflow.
+
+The test framework works by dynamically building your import and
+running it against the `sentinel` CLI to verify it behaves as expected.
+This ensures that your plugin builds, your plugin communicates via RPC
+correctly, and that the behavior within a policy is also correct.
+
+The example below shows a complete ready table-driven test for our time plugin.
+You can run this with a normal `go test`.
+
+```sentinel
+package main
+
+import (
+ "os"
+ "testing"
+
+ "github.com/hashicorp/sentinel-sdk"
+ plugintesting "github.com/hashicorp/sentinel-sdk/testing"
+)
+
+func TestMain(m *testing.M) {
+ exitCode := m.Run()
+ plugintesting.Clean()
+ os.Exit(exitCode)
+}
+
+func TestImport(t *testing.T) {
+ cases := []struct {
+ Name string
+ Data map[string]interface{}
+ Source string
+ }{
+ {
+ "month",
+ map[string]interface{}{"timestamp": 1495483674},
+ `main = subject.month.string == "September"`,
+ },
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.Name, func(t *testing.T) {
+ plugintesting.TestImport(t, plugintesting.TestImportCase{
+ Config: tc.Data,
+ Source: tc.Source,
+ })
+ })
+ }
+}
+```
+
+### Building Your Import
+
+To build your import, you must first implement the `main` function.
+The main function should just use the `rpc.Serve` method to serve the
+plugin over the Sentinel RPC layer:
+
+```sentinel
+package main
+
+import (
+ "github.com/hashicorp/sentinel-sdk"
+ "github.com/hashicorp/sentinel-sdk/rpc"
+)
+
+func main() {
+ rpc.Serve(&rpc.ServeOpts{
+ ImportFunc: func() sdk.Import {
+ return &framework.Import{Root: &root{}}
+ },
+ })
+}
+```
+
+You can then build this using `go build`. The output can be named
+anything. Once your plugin is built, [install it](#installing-plugins)
+and try it out!
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/functions/append.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/functions/append.mdx
new file mode 100644
index 0000000000..1b3e54b8e5
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/functions/append.mdx
@@ -0,0 +1,47 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Append'
+sidebar_title: 'append'
+sidebar_current: docs-funcs-append
+description: The built-in function `append` appends a value to the end of a list.
+layout: docs
+---
+
+# Builtin Function: append
+
+The built-in function `append` appends a value to the end of a list. The list
+is modified in-place. The return value will always be [undefined](/sentinel/language/undefined).
+
+`append` called on any value other than a list or undefined will result
+in an immediate error.
+
+Examples:
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+### Why does this function return undefined?
+
+This function returns `undefined` to avoid unintended side effects of the function in the context
+of a [rule](/sentinel/language/rules).
+
+For example, if `append` returned the list value as a result, the following policy would depend
+on the order in which rules are referenced:
+
+```sentinel
+list = []
+a = rule { append(list, 1) contains 1 }
+b = rule { append(list, 2) contains 2 }
+
+// Scenario 1: list becomes [1, 2]
+main = rule { a and b }
+
+// Scenario 2: list becomes [2, 1]
+main = rule { b and a }
+```
+
+This sort of side effect behavior introduces complex and difficult to track logical errors in programs.
+As a result, functions like this return `undefined` and modify values in-place.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/functions/delete.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/functions/delete.mdx
new file mode 100644
index 0000000000..0a6340876d
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/functions/delete.mdx
@@ -0,0 +1,26 @@
+---
+page_title: 'Sentinel Language - Builtin Function: delete'
+sidebar_title: 'delete'
+sidebar_current: docs-funcs-delete
+description: The built-in function `delete` deletes an element from a map.
+layout: docs
+---
+
+# Builtin Function: delete
+
+The built-in function `delete` deletes an element from a map.
+
+If the element to delete does not exist, the map is left unchanged.
+Calling `delete` on a value that is not a map or
+[undefined](/sentinel/language/undefined)
+is an immediate error. The result of `delete` is always `undefined`.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/functions/error.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/functions/error.mdx
new file mode 100644
index 0000000000..1d06dfc432
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/functions/error.mdx
@@ -0,0 +1,16 @@
+---
+page_title: 'Sentinel Language - Builtin Function: error'
+sidebar_title: 'error'
+sidebar_current: docs-funcs-error
+description: >-
+ The built-in function `error` is used to exit immediately with an error
+ message.
+layout: docs
+---
+
+# Builtin Function: error
+
+The built-in function `error` is used to exit immediately with an error message.
+
+Please see the [page on errors](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/functions/index.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/functions/index.mdx
new file mode 100644
index 0000000000..0d900b33d0
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/functions/index.mdx
@@ -0,0 +1,14 @@
+---
+page_title: Sentinel Language - Builtin Functions
+sidebar_title: Builtin Functions
+sidebar_current: docs-funcs
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Language: Builtin Functions
+
+Builtin functions are
+[functions](/sentinel/language/functions)
+that are available in all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in function.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/functions/keys.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/functions/keys.mdx
new file mode 100644
index 0000000000..36d99bc2b7
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/functions/keys.mdx
@@ -0,0 +1,23 @@
+---
+page_title: 'Sentinel Language - Builtin Function: keys'
+sidebar_title: 'keys'
+sidebar_current: docs-funcs-keys
+description: The built-in function `keys` returns the keys of a map.
+layout: docs
+---
+
+# Builtin Function: keys
+
+The built-in function `keys` returns the keys of a map.
+
+`keys` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned keys are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+```
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/functions/length.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/functions/length.mdx
new file mode 100644
index 0000000000..efe184c0f1
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/functions/length.mdx
@@ -0,0 +1,24 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Length'
+sidebar_title: 'length'
+sidebar_current: docs-funcs-length
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Builtin Function: length
+
+The built-in function `length` returns the length of a collection or string.
+
+The length of a string is the number of bytes in the string. It is not
+necessarilly the number of characters. The length of a collection is the
+number of elements in that collection. The length of
+[undefined](/sentinel/language/undefined) is `undefined`.
+
+Examples:
+
+```sentinel
+length("foo") // 3
+length([1, 2, 3, 4]) // 4
+length({ "key": "value" }) // 1
+```
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/functions/print.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/functions/print.mdx
new file mode 100644
index 0000000000..9d714195db
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/functions/print.mdx
@@ -0,0 +1,14 @@
+---
+page_title: 'Sentinel Language - Builtin Function: print'
+sidebar_title: 'print'
+sidebar_current: docs-funcs-print
+description: The built-in function `print` is used for logging and debugging.
+layout: docs
+---
+
+# Builtin Function: print
+
+The built-in function `print` is used for logging and debugging.
+
+Please see the [page on logging](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/functions/range.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/functions/range.mdx
new file mode 100644
index 0000000000..0a9a07aa6f
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/functions/range.mdx
@@ -0,0 +1,50 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Range'
+sidebar_title: 'range'
+sidebar_current: docs-funcs-range
+description: The built-in function `range` returns a list of numbers in a range.
+layout: docs
+---
+
+# Builtin Function: range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it
+defaults to `1`.
+
+Negative steps are permitted and count down from `start` towards `end`, instead
+of up.
+
+The range ends when the next value given by the sum of the last value and `step`
+would exceed `end`, regardless of if it has been reached.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4] - starts at 1 instead of 0
+range(1, 5, 2) // [1,3] - 3+2 does not satisfy r[-1] < end
+range(0, -3, -1) // [0,-1,-2] - example of negative range
+```
+
+Impossible ranges, or ranges where `start` will never reach `end` given `step`,
+yield an empty list.
+
+```
+range(1, 1) // [] - already starting at 1
+range(0, 1, -1) // [] - negative step will never reach 1
+```
+
+Supplying an undefined value to any argument of `range` yields an undefined
+value back.
+
+A range with a zero step is a runtime error.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/functions/values.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/functions/values.mdx
new file mode 100644
index 0000000000..95b448f6c7
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/functions/values.mdx
@@ -0,0 +1,23 @@
+---
+page_title: 'Sentinel Language - Builtin Function: values'
+sidebar_title: 'values'
+sidebar_current: docs-funcs-values
+description: The built-in function `values` returns the values of a map.
+layout: docs
+---
+
+# Builtin Function: values
+
+The built-in function `values` returns the values of a map.
+
+`values` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned values are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+values(data) // [3, 2]
+```
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/guides.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/guides.mdx
new file mode 100644
index 0000000000..8577828033
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/guides.mdx
@@ -0,0 +1,12 @@
+---
+page_title: Documentation
+sidebar_current: guides-index
+description: TODO Guides
+layout: docs
+---
+
+# Sentinel Guides
+
+Welcome to the Sentinel Guides!
+
+TODO
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/imports/base64.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/imports/base64.mdx
new file mode 100644
index 0000000000..9804473445
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/imports/base64.mdx
@@ -0,0 +1,47 @@
+---
+page_title: 'Import: base64'
+sidebar_title: 'base64'
+sidebar_current: docs-imports-base64
+description: The base64 import enables a Sentinel policy to encode and decode Base64 values.
+layout: docs
+---
+
+# Import: base64
+
+The base64 import enables a Sentinel policy to encode and decode Base64 values.
+
+### base64.encode(str)
+
+Encodes the string `str` into a Base64 encoded string, using the standard
+encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-4).
+
+```sentinel
+enc = base64.encode("foo") // "Zm9v"
+```
+
+### base64.decode(str)
+
+Decodes the Base64 encoded string `str`, returning the string result,
+using the standard encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-4).
+
+```sentinel
+dec = base64.decode("Zm9v") // "foo"
+```
+
+### base64.urlencode(str)
+
+Encodes the string `str` into a Base64 encoded string, using the alternate
+encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-5).
+
+```sentinel
+enc = base64.urlencode("foo") // "Zm9v"
+```
+
+### base64.urldecode(str)
+
+Decodes the Base64 encoded string `str`, returning the string result, using the
+alternate encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-5).
+
+```sentinel
+dec = base64.urldecode("Zm9v") // "foo"
+```
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/imports/decimal.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/imports/decimal.mdx
new file mode 100644
index 0000000000..88524e0dd9
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/imports/decimal.mdx
@@ -0,0 +1,319 @@
+---
+page_title: 'Import: decimal'
+sidebar_title: 'decimal'
+sidebar_current: docs-imports-decimal
+description: |-
+ The decimal import provides functions for operating on numbers represented
+ as decimals.
+layout: docs
+---
+
+# Import: decimal
+
+The decimal import provides functions for operating on numbers represented
+as decimals.
+
+Binary floating-point numbers provided by the `float` type are unable to
+fully represent numbers like `1.1` and `2.2`. The decimal import can
+represent these numbers exactly, and so should be used when exactness is
+required.
+
+Decimals are created through the `new` function, which takes any type that
+can represent a number. Arguments to the decimal operators can also take
+a value of any of these types. The full list is:
+
+- float
+- int
+- string
+- existing decimal value
+
+### decimal.infinity(sign)
+
+Creates an infinite-value decimal. Infinite-value decimals are always larger
+or smaller, depending on sign, than any non-infinite decimals.
+
+```sentinel
+decimal.infinity(1) // +Infinity
+decimal.infinity(-1) // -Infinity
+```
+
+Also available as `decimal.inf`.
+
+### decimal.nan
+
+A decimal representing a "not-a-number" value. NaNs are not equal to any
+other number, even themselves.
+
+```sentinel
+decimal.new(-1).sqrt() // NaN
+decimal.new(0).mul(decimal.inf(1)) // NaN
+decimal.nan.is(decimal.nan) // false
+```
+
+### decimal.is_infinite(d)
+
+Evaluates to true if the decimal is infinite.
+
+```sentinel
+decimal.is_infinite(100) // false
+decimal.is_infinite("Infinity") // true
+```
+
+Also available as `decimal.is_inf`, `decimal.isinf` and `decimal.isinfinite`.
+
+### decimal.is_nan(d)
+
+Evaluates to true if the decimal is not-a-number.
+
+```sentinel
+decimal.is_nan("NaN") // true
+```
+
+Also available as `decimal.isnan`.
+
+### decimal.new(v)
+
+Constructs a decimal from another value.
+
+```sentinel
+decimal.new("1.1") // Constructs a decimal from a string.
+decimal.new(2.2) // Constructs a decimal from a float.
+decimal.new(3) // Constructs a decimal from an integer.
+decimal.new("1.1234E+400") // Constructs a decimal from a string using E-notation.
+```
+
+## Type: number
+
+Numbers are represented internally by a sign, coefficient, and exponent.
+The value is calculated with the formula `sign * coefficient * 10^exponent`.
+Exponent is limited to 32 bits, and the coefficient is limited by the total
+precision.
+
+The maximum precision a number can represent is 100 digits. This includes
+both before and after the decimal place.
+
+### number.string
+
+A string representation of the decimal.
+
+```sentinel
+decimal.new(1.5).string // 1.5
+```
+
+### number.sign
+
+A number between `-1` and `+1` representing the sign of the decimal.
+
+```sentinel
+decimal.new(1.5).sign // 1
+```
+
+### number.coefficient
+
+The coefficient component of the decimal.
+
+```sentinel
+decimal.new(1.5).coefficient // 15
+```
+
+### number.exponent
+
+The exponent component of the decimal.
+
+```sentinel
+decimal.new(1.5).exponent // -1
+```
+
+### number.float
+
+A floating-point representation of the decimal.
+
+```sentinel
+decimal.new(1.5).float // 1.500000
+```
+
+### number.int
+
+An integer representation of the decimal. This always rounds down to the nearest integer.
+
+```sentinel
+decimal.new(1.5).int // 1
+```
+
+### number.is(v)
+
+Test for equality with another value.
+
+```sentinel
+decimal.new(1.5).is(1) // false
+```
+
+### number.is_not(v)
+
+Test for inequality with another value.
+
+```sentinel
+decimal.new(1.5).is_not(1) // true
+```
+
+### number.less_than(v)
+
+Test that the decimal is less than another value.
+Can also be called as `number.lt(v)`
+
+```sentinel
+decimal.new(1.5).less_than(1) // false
+```
+
+### number.less_than_or_equals(v)
+
+Test that the decimal is less than or equals to another value.
+Can also be called as `number.lte(v)`
+
+```sentinel
+decimal.new(1.5).less_than_or_equals(1) // false
+```
+
+### number.greater_than(v)
+
+Test that the decimal is greater than another value.
+Can also be called as `number.gt(v)`
+
+```sentinel
+decimal.new(1.5).greater_than(1) // true
+```
+
+### number.greater_than_or_equals(v)
+
+Test that the decimal is greater than or equals to another value.
+Can also be called as `number.gte(v)`
+
+```sentinel
+decimal.new(1.5).greater_than_or_equals(1) // true
+```
+
+### number.add(v)
+
+Add another number to the decimal.
+
+```sentinel
+decimal.new(1.5).add(1) // 2.5
+```
+
+### number.subtract(v)
+
+Subtract another number from the decimal.
+Can also be called as `number.sub(v)`
+
+```sentinel
+decimal.new(1.5).subtract(1) // 0.5
+```
+
+### number.multiply(v)
+
+Multiply the decimal by a number.
+Can also be called as `number.mul(v)`
+
+```sentinel
+decimal.new(1.5).multiply(2) // 3.0
+```
+
+### number.divide(v)
+
+Divide the decimal by a number.
+Can also be called as `number.div(v)`
+
+```sentinel
+decimal.new(3.0).divide(1.5) // 2
+```
+
+### number.modulo(v)
+
+Find the remainder after dividing the decimal by a number.
+Can also be called as `mod`, `remainder`, or `rem`.
+
+```sentinel
+decimal.new(1.5).modulo(1) // 0.5
+```
+
+### number.power(v)
+
+Raise the decimal to a power.
+Can also be called as `number.pow(v)`
+
+```sentinel
+decimal.new(1.5).power(2) // 2.25
+```
+
+### number.exp()
+
+The natural exponent of the decimal. This calculates `e^number`.
+
+```sentinel
+decimal.new(1.5).exp() // 4.4816...
+```
+
+### number.loge()
+
+The natural logarithm of the decimal.
+Can also be called as `number.ln()`
+
+```sentinel
+decimal.new(1.5).loge() // 0.4054...
+```
+
+### number.log10()
+
+The base-10 logarithm of the decimal.
+Can also be called as `number.log()`
+
+```sentinel
+decimal.new(1.5).log10() // 0.1760...
+```
+
+### number.square_root()
+
+The square root of the decimal.
+Can also be called as `number.sqrt()`
+
+```sentinel
+decimal.new(1.5).square_root() // 1.2247...
+```
+
+### number.ceiling()
+
+Round the decimal to the smallest integer greater or equal to itself.
+Can also be called as `number.ceil()`
+
+```sentinel
+decimal.new(1.5).ceiling() // 2
+```
+
+### number.floor()
+
+Round the decimal to the largest integer less than or equal to itself.
+
+```sentinel
+decimal.new(1.5).floor() // 1
+```
+
+### number.absolute()
+
+The absolute value of the decimal.
+Can also be called as `number.abs()`
+
+```sentinel
+decimal.new(1.5).absolute() // 1.5
+decimal.new(-1.5).absolute() // 1.5
+```
+
+### number.negate()
+
+The negated value of the decimal. A positive decimal will become negative,
+and a negative decimal will become positive.
+Can also be called as `number.neg()`
+
+```sentinel
+decimal.new(1.5).negate() // -1.5
+decimal.new(-1.5).negate() // 1.5
+```
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/imports/http.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/imports/http.mdx
new file mode 100644
index 0000000000..ecfc5d2b42
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/imports/http.mdx
@@ -0,0 +1,305 @@
+---
+page_title: 'Import: http'
+sidebar_title: 'http'
+sidebar_current: docs-imports-http
+description: The http import enables the use of HTTP-accessible data from outside the runtime in Sentinel policy rules.
+layout: docs
+---
+
+# Import: http
+
+The http import enables the use of HTTP-accessible data from outside
+the runtime in Sentinel policy rules.
+
+At the center of the import is the [`get()`](#clientgeturl_or_request) function, which issues a GET
+request and returns a `response` type:
+
+```sentinel
+import "http"
+
+resp = http.get("https://example.hashicorp.com")
+main = rule { resp.body contains "something" }
+```
+
+By default, any request response that is not a successful "200 OK" HTTP
+response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information and
+how to customize this behavior.
+
+For more advanced requests which require setting request headers, use a
+[`request` type](#type-request):
+
+```sentinel
+import "http"
+import "json"
+
+param token
+
+req = http.request("https://example.hashicorp.com").with_header("Authorization", "token "+token)
+resp = json.unmarshal(http.get(req).body)
+main = rule { resp["some_key"] is true }
+```
+
+The `with_header` function above returns the `request` object with the
+`Authorization` HTTP header set to value of the variable `some_value`. Many
+of the methods within the http import API are designed around this concept
+of method chaining, allowing for complex building of HTTP requests
+encapsulated in functions. The following is an idiomatic example further
+demonstrating this pattern:
+
+```sentinel
+import "http"
+import "json"
+
+param github_user
+param github_token
+
+client = func(req) {
+ return http.with_timeout(5).with_retries(3)
+}
+
+github_request = func(path) {
+ full_url = "https://api.github.com" + path
+ return http.request(full_url).
+ with_basic_auth(github_user, github_token).
+ with_header("Accept", "application/vnd.github.v3+json").
+ with_header("Custom", "foo"),
+}
+
+default_response = client.get(github_request("/example"))
+
+# Override the Custom header for this single request
+custom_header_response = json.unmarshal(
+ client.get(
+ github_request("/example").with_header("Custom", "bar"),
+ ),
+)
+
+# Override the timeout value for a known slower request
+slow_response = json.unmarshal(
+ client.with_timeout(15).get(github_request("/slow-endpoint")),
+)
+
+some_key_is_true = rule { json.unmarshal(default_response.body)["some_key"] is true }
+body_contains_text = rule { custom_header_response.body contains "something" }
+response_is_ok = rule { slow_response.status_code is 200 }
+
+main = rule { some_key_is_true and body_contains_text and response_is_ok }
+```
+
+### http.client
+
+Retrieve the base HTTP client with default settings. The returned client may be
+configured by calling configuration functions on it; all of these configuration
+functions on are also aliased as top-level functions in this namespace. See
+[`client`](#type-client) below.
+
+### http.request(url)
+
+Create a new [`request`](#type-request) to the specified `url` (string). A
+`request` type enables customization of headers and other concerns before then
+providing the constructed `request` to [`get()`](#clientgeturl_or_request).
+
+```sentinel
+req = http.request("example.hashicorp.com/foo").with_header("Foo", "bar")
+resp = http.get(req)
+```
+
+## Type: client
+
+All of the following configuration functions on the default client are also
+aliased as top-level functions in the import. This allows a short-hand (and
+idiomatic) way of configuring a new client without having to directly access
+`http.client` each time:
+
+```sentinel
+# The following two lines are semantically the same:
+resp = http.client.with_timeout(5).get(url)
+resp = http.with_timeout(5).get(url)
+```
+
+### client.get(url_or_request)
+
+Issue a GET request with a URL string or a `request` type. Returns a `response`
+type.
+
+When `url_or_request` is a string, a GET request is made with the URL
+specified by the string. To customize HTTP headers and other settings, pass
+a request. By default, a request has a timeout value of 10 seconds and 1
+retry. These settings can be adjusted with [`with_timeout()`](#clientwith_timeoutinteger) and
+[`with_retries()`](#clientwith_retriesinteger), respectively.
+
+If the response is one of the following redirect codes, `get()` follows the
+redirect, up to a maximum of 10 redirects:
+
+301 (Moved Permanently)
+302 (Found)
+303 (See Other)
+307 (Temporary Redirect)
+308 (Permanent Redirect)
+
+If the redirect limit is exceeded, the result is a policy error.
+
+By default, after any redirects are followed (up to the maximum), any request
+response that is not a successful "200 OK" response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information
+and how to customize this behavior.
+
+### client.accept_status_codes(list)
+
+Configures a client to not automatically cause a policy failure if
+the resulting response's status code is in `list`. Any status code received
+that is not present in this list will trigger a runtime error.
+
+By default, any request response that is not a successful "200 OK" response
+causes a policy error. This is for convenience, as the most common scenario
+by far is to receive a response and immediately signal a policy error if the
+status code is not 200. Use this scoping to specify a list of non-redirect
+HTTP codes that you wish to accept without error and evaluate the response
+yourself.
+
+Redirects are not considered final status codes in this context. That is,
+redirects are always followed up to the maximum allowed value (see [`get()`](#clientgeturl_or_request))
+and the final response code in the redirect chain is validated against
+`list`.
+
+```sentinel
+resp = http.accept_status_codes([200, 404]).get(url)
+main = rule { resp.status_code is 404 }
+```
+
+### client.accepted_status_codes
+
+Returns a list of the client's accepted status codes.
+
+```sentinel
+client = http.accept_status_codes([200, 404])
+main = rule { client.accepted_status_codes contains 404 }
+```
+
+### client.with_retries(integer)
+
+Sets the maximum number of retries, specified by `integer`, that should be
+attempted on an unsuccessful request. An unsuccessful request is considered
+to be any 5xx response except `501 (Not Implemented)` or a request timeout.
+By default, one retry is attempted.
+
+The maximum number of retries is 10, for a total of 11 request attempts.
+When a request exceeds the maximum number of retries without a response, the
+result is a policy error. Specifying a number larger than this will result
+in a runtime error.
+
+Between each retry, an exponentially increasing delay is observed, allowing
+time for the server to recover. This delay starts at 1 second for the first
+retry and increases each attempt thereafter. The maximum delay for a single
+attempt is 30 seconds.
+
+Retries can be disabled by specifying 0.
+
+### client.retries
+
+Returns the client's configured number of retries as an integer.
+
+### client.with_timeout(integer)
+
+Sets the request timeout to the number of seconds indicated by `integer`.
+
+Each individual request performed by the HTTP client will be limited by the
+given request timeout. This timeout includes connection time, any redirects,
+and reading the response body.
+
+A maximum of 30 seconds is allowed. Specifying a number larger than this
+will result in a runtime error.
+
+By default, requests will time out after 10 seconds.
+
+### client.timeout
+
+Returns the client's configured timeout in whole seconds as an integer.
+
+### client.without_certificate_verification()
+
+Skips verification of TLS certificates during secure HTTP operations,
+allowing for requests to `https://` endpoints which are secured with a TLS
+certificate signed by an untrusted certificate authority. Takes no arguments.
+
+By default, the HTTP client will trigger a runtime error if a TLS certificate
+presented by a server is from an untrusted certificate authority.
+
+Do not change this setting unless you are aware of the risks involved.
+Without verification, TLS accepts any certificate presented by the server
+and any host name in that certificate. In this mode, TLS is susceptible to
+man-in-the-middle attacks. This option should only be used for testing or in
+trusted networks with self-signed certificates.
+
+```sentinel
+http.without_certificate_verification().get(url)
+```
+
+### client.certificate_verification
+
+Returns true if the client requires TLS certificates to be verifiable.
+
+## Type: request
+
+### request.url
+
+Returns the request URL as a string.
+
+### request.headers
+
+Returns the configured request headers as a string-keyed map of strings.
+
+### request.with_header(key, value)
+
+Returns a `request` with the header `key` (string) set to `value` (string).
+
+Values are overridden on subsequent calls to the same `key`. To specify
+multiple values, use the existing value when setting the new one.
+
+```sentinel
+original = http.request("http://example.hashicorp.com").with_header("Foo", "bar")
+overridden = original.with_header("Foo", "baz")
+multi_value = original.with_header("Foo", original.headers["Foo"] + ",baz")
+
+main = rule {
+ original.headers["Foo"] is "bar" and
+ overridden.headers["Foo"] is "baz" and
+ multi_value.headers["Foo"] is "bar,baz"
+}
+```
+
+### request.with_basic_auth(username, password)
+
+Returns a `request` with the `Authorization` header set to use HTTP Basic
+Authentication with the provided username and password.
+
+Note that with HTTP Basic Authentication the provided username and password
+are encoded, but not encrypted.
+
+## Type: response
+
+### response.status_code
+
+The response status code as an integer, e.g. `200`.
+
+### response.headers
+
+The response headers as a map.
+
+### response.body
+
+The response body as a string. Callers should unmarshal the content to a useful
+representation as necessary based on the content type. For example, if the
+response is in JSON:
+
+```sentinel
+import "http"
+import "json"
+
+# This example endpoint returns {"some_key": true}
+req = http.request("https://example.hashicorp.com/some-json")
+
+resp = json.unmarshal(http.get(req).body)
+main = rule { keys(resp) contains "some_key" and resp["some_key"] is true }
+```
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/imports/index.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/imports/index.mdx
new file mode 100644
index 0000000000..0a62cb7b02
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/imports/index.mdx
@@ -0,0 +1,19 @@
+---
+page_title: Sentinel Language - Standard Imports
+sidebar_title: Standard Imports
+sidebar_current: docs-imports
+description: >-
+ Standard Imports are the built-in imports that are available to all Sentinel
+ policies.
+layout: docs
+---
+
+# Standard Imports
+
+Standard Imports are the built-in [imports](/sentinel/language/imports)
+that are available to all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in import.
+
+Sentinel-embedded applications can choose to allow or deny certain
+standard imports. Please reference the documentation for the Sentinel-enabled
+application you're using to determine if all standard imports are available.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/imports/json.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/imports/json.mdx
new file mode 100644
index 0000000000..45a75d30d5
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/imports/json.mdx
@@ -0,0 +1,32 @@
+---
+page_title: 'Import: json'
+sidebar_title: 'json'
+sidebar_current: docs-imports-json
+description: The json import enables a Sentinel policy to parse and access a JSON document.
+layout: docs
+---
+
+# Import: json
+
+The json import enables a Sentinel policy to parse and access a JSON
+document.
+
+### json.unmarshal(obj)
+
+Unmarshals the JSON object `obj` into a native Sentinel structure.
+
+All native JSON types can be represented perfectly as Sentinel native types.
+
+```sentinel
+// Typically the input for this would come from an external source.
+config = json.unmarshal("{ \"foo\": 42 }")
+config.foo // 42
+config.bar // undefined (as usual for accessing a non-existent map key)
+```
+
+### json.marshal(obj)
+
+Marshals the Sentinel object `obj` into a JSON object encoded as a string.
+
+Functions and `undefined` cannot be natively encoded as JSON. If values of
+either type are found in the structure, this will return undefined.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/imports/runtime.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/imports/runtime.mdx
new file mode 100644
index 0000000000..5bfa156825
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/imports/runtime.mdx
@@ -0,0 +1,17 @@
+---
+page_title: 'Import: runtime'
+sidebar_title: 'runtime'
+sidebar_current: docs-imports-runtime
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: runtime
+
+The runtime import contains various information about the Sentinel runtime. It
+can be used to get information about the runtime as it's embedded in the CLI or
+a specific integration, such as validating a specific runtime version.
+
+### runtime.version
+
+Returns the version of Sentinel within this implementation.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/imports/sockaddr.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/imports/sockaddr.mdx
new file mode 100644
index 0000000000..c200e69796
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/imports/sockaddr.mdx
@@ -0,0 +1,42 @@
+---
+page_title: 'Import: sockaddr'
+sidebar_title: 'sockaddr'
+sidebar_current: docs-imports-sockaddr
+description: The sockaddr import makes working with IP addresses easy.
+layout: docs
+---
+
+# Import: sockaddr
+
+The sockaddr import makes working with IP addresses easy.
+
+### sockaddr.is_contained(outer, inner)
+
+The is_contained function returns true if `inner` is an address that
+is contained within `outer`.
+
+```sentinel
+sockaddr.is_contained("192.168.0.0/24", "192.168.0.32") // true
+sockaddr.is_contained("192.168.0.0/24", "192.174.1.1") // false
+```
+
+### sockaddr.is_equal(addr1, addr2)
+
+The is_equal function returns true if addr1 is equal to addr2.
+
+```sentinel
+sockaddr.is_equal("192.168.12.24", "192.168.12.24") // true
+```
+
+### sockaddr.is_ipv4(addr)
+
+The is_ipv4 function returns true if addr is an IPv4 address.
+
+```sentinel
+sockaddr.is_ipv4("192.168.12.24") // true
+sockaddr.is_ipv4("2001:0db8:85a3:0000:0000:8a2e:0370:7334") // false
+```
+
+### sockaddr.is_ipv6(addr)
+
+The is_ipv6 function returns true if addr is an IPv6 address.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/imports/strings.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/imports/strings.mdx
new file mode 100644
index 0000000000..bdacfc68a1
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/imports/strings.mdx
@@ -0,0 +1,92 @@
+---
+page_title: 'Import: strings'
+sidebar_title: 'strings'
+sidebar_current: docs-imports-strings
+description: The strings import exposes common string operations.
+layout: docs
+---
+
+# Import: strings
+
+The strings import exposes common string operations.
+
+### strings.has_prefix(s, prefix)
+
+Returns true if `s` starts with `prefix`.
+
+```sentinel
+strings.has_prefix("billing-id", "billing-") // true
+strings.has_prefix("bill-id", "billing-") // false
+```
+
+### strings.has_suffix(s, suffix)
+
+Returns true if `s` ends with `suffix`.
+
+```sentinel
+strings.has_suffix("billing-id", "id") // true
+strings.has_suffix("billing-name", "id") // false
+```
+
+### strings.join(a, sep)
+
+Joins a list with the string supplied by `sep`.
+
+Multi-dimensional lists are supported, and non-string primitive
+types will be converted to their string equivalents. Maps and
+other complex non-list types are not supported.
+
+```sentinel
+strings.join(["foo", "bar", "baz"], ".") // "foo.bar.baz"
+strings.join([["foo", "bar"], "baz"], ".") // "foo.bar.baz"
+strings.join(["a", 1, 1.01, true], ",") // "a,1,1.01,true"
+```
+
+### strings.trim_prefix(s, prefix)
+
+Trim the prefix from the string `s`. If the string doesn't have the
+prefix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_prefix("billing-id", "billing-") // "id"
+strings.trim_prefix("bill-id", "billing-") // "bill-id"
+```
+
+### strings.trim_suffix(s, suffix)
+
+Trim the suffix from the string `s`. If the string doesn't have the
+suffix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_suffix("billing-id", "-id") // "billing"
+strings.trim_suffix("bill-id", "-foo") // "bill-id"
+```
+
+### strings.to_lower(s)
+
+Lowercase the string s.
+
+```sentinel
+strings.to_lower("FoO") // "foo"
+```
+
+### strings.to_upper(s)
+
+Uppercase the string s.
+
+```sentinel
+strings.to_upper("FoO") // "FOO"
+```
+
+### strings.split(s, sep)
+
+Split the string 's' via a substring 'sep'. If 'sep' is not contained in
+'s', the return value will be a single-element list containing only 's'.
+As a special-case, if the input is already a list, it will be returned
+as-is. This allows calling the split function when not knowing if the input
+is already a list or not.
+
+```sentinel
+strings.split("foo,bar,baz", ",") // ["foo", "bar", "baz"]
+strings.split(["foo", "bar", "baz"], ",") // ["foo", "bar", "baz"]
+```
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/imports/time.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/imports/time.mdx
new file mode 100644
index 0000000000..d4d8d6d089
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/imports/time.mdx
@@ -0,0 +1,258 @@
+---
+page_title: 'Import: time'
+sidebar_title: 'time'
+sidebar_current: docs-imports-time
+description: The time import provides access to the execution time and time functions.
+layout: docs
+---
+
+# Import: time
+
+The time import provides access to the execution time and time functions.
+
+During the execution of a policy the time is fixed to the moment of
+execution. This gives the illusion that a policy instantly executes at a
+single moment of time.
+
+The import uses Coordinated Universal Time (UTC).
+
+As an example of this, if a policy accessed `time.now.second` multiple times,
+for each access the same value would be returned even if they are several seconds apart.
+This is the execution `timespace`, which is mapped to `time.now`.
+
+It is also possible to run functions on a custom time by using the
+`time.load()` function to create a new timespace from the specified value.
+See the documentation for available actions on timespaces.
+
+Times specified as the reference time or via the `time.load()` function can
+be specified in a `timeish` fashion. This is either one of:
+
+- The number of seconds since the Unix epoch (Thursday, 1 January 1970,
+ 00:00:00 UTC),
+- A timestamp matching the format outlined in RFC3339, either in the
+ format `2006-01-02T15:04:05+07:00`, which includes a timezone offset, or
+ `2006-01-02T15:04:05Z`, which indicates UTC.
+
+### time.now
+
+Create a `timespace` locked either to execution time or, if set, the
+reference time. In a given execution, this will always produce the same
+value.
+
+```sentinel
+time.now // {"day": 17, "hour": 23, "minute": 19, "month": 11 ... }
+```
+
+### time.load(timeish)
+
+Create a timespace using the given value. Please see the import
+documentation for valid values for "timeish".
+
+```sentinel
+time.load("2019-11-16T01:20:30Z") // {"day": 16, "hour": 1, "minute": 20, "month": 11 ... }
+```
+
+### time.nanosecond
+
+A nanosecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.nanosecond * 1) // 2019-11-16T01:20:30.000000001Z
+```
+
+### time.microsecond
+
+A microsecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.microsecond * 1) // 2019-11-16T01:20:30.000001Z
+```
+
+### time.millisecond
+
+A millisecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.millisecond * 1) // 2019-11-16T01:20:30.001Z
+```
+
+### time.second
+
+A second duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.second * 1) // 2019-11-16T01:20:31Z
+```
+
+### time.minute
+
+A minute for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.minute * 1) // 2019-11-16T01:21:30Z
+```
+
+### time.hour
+
+An hour duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+## Type: timespace
+
+### timespace.hour
+
+The hour from 0 to 23.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").hour // 1
+```
+
+### timespace.minute
+
+The minute from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").minute // 20
+```
+
+### timespace.second
+
+The second from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").second // 30
+```
+
+### timespace.day
+
+The day of the month, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").day // 16
+```
+
+### timespace.month
+
+The month of the year as an integer, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month // 11
+```
+
+### timespace.month_name
+
+The name of the month of the year, in English. Example: `January`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month_name // November
+```
+
+### timespace.weekday
+
+The weekday as an integer, where 0 is Sunday and 6 is Saturday.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday // 6
+```
+
+### timespace.weekday_name
+
+The name of the day of the week, in English. Example: `Sunday`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday_name // Saturday
+```
+
+### timespace.year
+
+The year as an integer.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").year // 2019
+```
+
+### timespace.unix
+
+The number of seconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix // 1573867230
+```
+
+### timespace.unix_nano
+
+The number of nanoseconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix_nano // 1573867230000000000
+```
+
+### timespace.zone
+
+The timezone offset, in seconds. This can be a negative number to indicate
+time west of UTC.
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone // -28800
+```
+
+### timespace.zone_string
+
+The timezone offset, in the format `+HH:MM` (example: `-08:00` for PST).
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone_string // -08:00
+```
+
+### timespace.before(timeish_or_timespace)
+
+Check if the time in the timespace is before the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").before("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.after(timeish_or_timespace)
+
+Check if the time in the timespace is after the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").after("2019-11-15T01:20:30Z") // true
+```
+
+### timespace.equal(timeish_or_timespace)
+
+Check if two times are equivalent
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").equal("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.add(duration)
+
+Add a duration in nanoseconds to the time in the timespace and return a new timespace. The
+duration can be negative. The duration should be specified as an integer
+multiplied with the correct unit.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+### timespace.sub(timeish_or_timespace)
+
+Subtract a time from the time in the timespace and return a duration in nanoseconds.
+
+```sentinel
+// Gives a duration of 3 hours.
+duration = time.load(
+ "2019-11-16T01:20:30Z",
+).sub("2019-11-16T04:20:30Z")
+
+// Returns 2019-11-16T01:20:30Z
+time.load(
+ "2019-11-16T04:20:30Z",
+).add(duration)
+```
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/imports/types.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/imports/types.mdx
new file mode 100644
index 0000000000..a25e9ef4c1
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/imports/types.mdx
@@ -0,0 +1,36 @@
+---
+page_title: 'Import: types'
+sidebar_title: 'types'
+sidebar_current: docs-imports-types
+description: The types import enables a Sentinel policy to parse an object's type.
+layout: docs
+---
+
+# Import: types
+
+The types import enables a Sentinel policy to parse an object's type.
+
+### types.type_of(obj)
+
+Returns the object's type as a string
+
+```sentinel
+import "types"
+
+// Typically the inputs would come from an external source.
+is_boolean = rule { types.type_of(true) is "bool" }
+is_string = rule { types.type_of("Hello!") is "string" }
+is_integer = rule { types.type_of(42) is "int" }
+is_float = rule { types.type_of(42.123) is "float" }
+is_null = rule { types.type_of(null) is "null" }
+is_undefined = rule { types.type_of(undefined) is "undefined" }
+
+main = rule {
+ is_boolean and
+ is_string and
+ is_integer and
+ is_float and
+ is_null and
+ is_undefined
+}
+```
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/imports/units.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/imports/units.mdx
new file mode 100644
index 0000000000..0838cf0ae8
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/imports/units.mdx
@@ -0,0 +1,40 @@
+---
+page_title: 'Import: units'
+sidebar_title: 'units'
+sidebar_current: docs-imports-units
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: units
+
+The units import provides access to quick calculations for various byte
+units.
+
+Note that this import uses base-2 terminology:
+
+- One kilobyte is 1024 bytes
+- One megabyte is 1024^2 = 1048576 bytes
+- One gigabyte is 1024^3 = 1073741824 bytes
+- One terabyte is 1024^4 = 1099511627776 bytes
+- One petabyte is 1024^5 = 1125899906842624 bytes
+
+### units.kilobyte
+
+The base-2 representation of a kilobyte (1024 bytes).
+
+### units.megabyte
+
+The base-2 representation of a megabyte (1048576 bytes).
+
+### units.gigabyte
+
+The base-2 representation of a gigabyte (1073741824 bytes).
+
+### units.terabyte
+
+The base-2 representation of a terabyte (1099511627776 bytes).
+
+### units.petabyte
+
+The base-2 representation of a petabyte (1125899906842624 bytes).
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/imports/version.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/imports/version.mdx
new file mode 100644
index 0000000000..270cdacb01
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/imports/version.mdx
@@ -0,0 +1,152 @@
+---
+page_title: 'Import: version'
+sidebar_title: 'version'
+sidebar_current: docs-imports-version
+description: |-
+ The version import provides functions for parsing versions and version constraints,
+ and verifying versions against a set of constraints.
+layout: docs
+---
+
+# Import: version
+
+The version import provides functions for parsing versions and version constraints,
+and verifying versions against a set of constraints.
+
+Versions are created through the `new` function, which accepts a string type in dotted-tri format (i.e. 1.0.0-alpha.1+001) that can represent a version.
+
+### version.new(v)
+
+Constructs a version from string value.
+
+```sentinel
+version.new("1.0.0-alpha.1+001")
+```
+
+### version.major
+
+An integer representation of the Major number for a given version .
+
+```sentinel
+version.new("1.0.0-alpha.1+001").major // 1
+```
+
+### version.minor
+
+An integer representation of the Minor number for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").minor // 0
+```
+
+### version.patch
+
+An integer representation of the Patch number for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").patch // 0
+```
+
+### version.prerelease
+
+A string representation of the Prerelease for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").prerelease // "alpha.1"
+```
+
+### version.metadata
+
+A string representation of the Metadata for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").metadata // "001"
+```
+
+### version.version
+
+A string representation of the version including pre-release and metadata information.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").version // "1.0.0-alpha.1+001"
+```
+
+### version.greater_than(v)
+
+Tests that the version is greater than a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").greater_than("0.12.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").gt("1.0.1-alpha.1+001") // False
+```
+
+### version.greater_than_or_equals(v)
+
+Tests that the version is greater than or equal to a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").greater_than_or_equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").gte("1.0.1-alpha.1+001") // False
+```
+
+### version.less_than(v)
+
+Tests that the version is less than a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").less_than("1.0.1-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").lt("0.12.0-alpha.1+001") // False
+```
+
+### version.less_than_or_equals(v)
+
+Tests that the version is less than or equal to a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").less_than_or_equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").lte("0.12.0-alpha.1+001") // False
+```
+
+### version.less_than_or_equals(v)
+
+Tests that the version equals a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").eq("0.12.0-alpha.1+001") // False
+```
+
+### version.compare(v)
+
+Compares one version with a given version. Compare returns -1, 0, or 1 if the version is less, equal, or greater than the other version, respectively.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").compare("0.12.0-alpha.1+001") // -1
+version.new("1.0.0-alpha.1+001").compare("1.0.0-alpha.1+001") // 0
+version.new("0.12.0-alpha.1+001").cmp("1.0.0-alpha.1+001") // 1
+```
+
+### version.satisfies(v, x)
+
+Tests that a version adheres to one or more version constraints. Satisfies supports the following restriction operators:
+
+- =
+- !=
+- \>
+- <
+- \>=
+- <=
+- ~>
+
+Satisfies supports the following usage scenarios:
+
+- A constraint with a pre-release can only match a pre-release version that contains the same major, minor and patch identifiers.
+- A constraint without a pre-release can only match a version without a pre-release.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").satisfies("> 1.0.0-alpha.1+001") // false
+version.new("1.0.0-alpha.1+001").satisfies(">= 1.0.0-alpha.1+001") // true
+version.new("1.0.0-alpha.1+001").satisfies(">= 1.0.0-alpha.1+001, < 1.0.0-beta+001") // true
+version.new("1.0.0").sat("= 1.0.0") // true
+version.new("0.12.7").sat("~> 0.12.0") // true
+```
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/index.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/index.mdx
new file mode 100644
index 0000000000..4497b40b83
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/index.mdx
@@ -0,0 +1,31 @@
+---
+layout: 'docs'
+page_title: 'Documentation'
+sidebar_current: 'docs-home'
+description: |-
+ Sentinel is a language and framework for policy built to be embedded in existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+[introduction](/sentinel/intro) to understand what Sentinel is, how it
+compares to other software, and more.
+
+
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/language/arithmetic.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/language/arithmetic.mdx
new file mode 100644
index 0000000000..3a341db72e
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/language/arithmetic.mdx
@@ -0,0 +1,68 @@
+---
+page_title: Sentinel Language - Arithmetic
+sidebar_title: Arithmetic
+sidebar_current: docs-language-arith
+description: >-
+ Sentinel supports arithmetic operators for integers and floats. Sentinel
+ supports sum, difference, product, quotient, and remainder.
+layout: docs
+---
+
+# Language: Arithmetic
+
+Sentinel supports arithmetic operators for integers and floats. Sentinel
+supports sum, difference, product, quotient, and remainder.
+
+```plaintext
++ sum
+* difference
+* product
+/ quotient
+% remainder
+```
+
+These operators work in a typical infix style:
+
+```sentinel
+4 + 8 // 12
+8 * 2 // 16
+8 / 4 // 2
+8 / 5 // 1
+8 % 5 // 3
+```
+
+## Order of Operations
+
+Arithmetic follows a standard mathematical order of operations.
+Grouping with parentheses can be used to affect ordering.
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+(4 + 5) * 2 // 18
+```
+
+A full table of operator precendence can be found on the
+[boolean expressions page](/sentinel/language/boolexpr). This
+shows how arithmetic operators relate to other operators.
+
+## Integer Division
+
+Integer division with a remainder rounds down to the nearest integer.
+Example: `8 / 3 is 2`.
+
+If the divisor is zero, an error occurs.
+
+## Mixed Numeric Operations
+
+Mixed numeric operations between integer and floating-point values are
+permitted. The result is a floating-point operation with the integer converted
+to a floating-point value for purposes of calculation.
+
+```sentinel
+import "types"
+
+a = 1.1 + 1 // 2.1
+types.type_of(a) // "float"
+```
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/language/boolexpr.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/language/boolexpr.mdx
new file mode 100644
index 0000000000..40804404b8
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/language/boolexpr.mdx
@@ -0,0 +1,208 @@
+---
+page_title: Sentinel Language - Boolean Expressions
+sidebar_title: Boolean Expressions
+sidebar_current: docs-language-boolexpr
+description: >-
+ Boolean expressions are expressions that evaluate to a boolean value from the
+ result of a combination of one or more comparisons and logical operators.
+layout: docs
+---
+
+# Language: Boolean Expressions
+
+Boolean expressions are expressions that evaluate to a boolean value
+from the result of a combination of one or more comparisons and logical
+operators. Boolean expressions form the basis of policy since policy can be broken
+down to a set of logical decisions that turn into true or false.
+
+A single boolean expression is the body of a [rule](/sentinel/language/rules).
+Additionally, boolean expressions are used with [conditionals](/sentinel/language/conditionals),
+and more.
+
+## Order of Operations
+
+The precedence of operators is shown below. Operators with higher
+precedence values (larger numbers) are evaluated first. Operators with
+the same precedence value are evaluated left-to-right.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Examples of this precedence are shown below:
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+```
+
+## Logical Operators
+
+Logical operators are used to change or combine logical expressions.
+There are three binary operators `and`, `or`, and `xor`. There is
+a single unary operator `not` (which can also be specified as `!`).
+
+The binary operators require two boolean operands and the unary
+operator requires a single boolean operand. In both case, the operands
+are values or expressions that are boolean types.
+
+Below is a basic explanation of the logical operators directly from
+the specification:
+
+```sentinel
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+Using parentheses to group boolean expressions, you can combine
+boolean expressions to become more complex or affect ordering:
+
+```sentinel
+(p and q) or r
+p and (q or r)
+```
+
+## Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+For any comparison, the two operands must be equivalent types. The one
+exception are integers and floats. When an integer is compared with
+a float, the integer is promoted to a floating point value.
+
+The available comparison operators are:
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+The behavior of `is` with `==` and `is not` with `!=` is identical.
+
+Example comparisons:
+
+```sentinel
+name is "Mitchell"
+idnumber > 42
+idnumber <= 12
+```
+
+Using the logical operators, these can be combined:
+
+```sentinel
+name is "Mitchell" and idnumber > 42
+```
+
+The language specification provides more detail on exactly
+[how comparison behaves](/sentinel/language/spec#comparison-operators).
+
+## Set Operators
+
+The set operators `contains` and `in` test for inclusion in a collection
+(a list or map).
+
+Set operators may 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.
+
+`contains` tests if the left-hand collection contains the right-hand value.
+`in` tests if the right-hand collection contains the left-hand value. For
+maps, "contains" looks at the keys, not the values.
+
+Examples:
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+```
+
+## Matches Operator
+
+The `matches` operator tests if a string matches a regular expression.
+The `matches` operator can be negated by prefixing the operator with
+`not`, such as `not matches`.
+
+The left-hand value is the string to test. The right-hand value is
+a string value representing a regular expression. The syntax of the regular
+expression is the same general syntax used by Python, Ruby, and other languages.
+The precise syntax accepted is the syntax accepted by RE2.
+
+Regular expressions are not anchored by default; any anchoring must be
+explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+## Any, All Expressions
+
+`any` and `all` expressions allow testing that any or all elements of a
+collection match some boolean expression. The result of an `any` and `all`
+expression is a boolean.
+
+`any` returns the boolean value `true` if any value in the collection expression
+results in the body expression evaluating to `true`. If the body expression
+evalutes to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression
+result in the body expression evaluating to `true`. If any value in the
+collection expression result in the body expression evaluating to `false`,
+the `all` expression returns `false`.
+
+For empty collections, `any` returns `false` and `all` returns `true`.
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" }
+any group.tasks as t { t.driver is "vmware" }
+```
+
+Since `any` and `all` expressions are themselves boolean expressions, they
+can be combined with other operators:
+
+```sentinel
+any ["a", "b"] as char { char is "a" } or
+other_value is "another"
+```
+
+## Emptiness Comparison
+
+The expressions `is empty` and `is not empty` provide a convenience
+method for determining the emptiness of a value. It supports the same types
+as the [built-in length](/sentinel/functions/length), collections and strings.
+
+```sentinel
+[] is empty // true
+[] is not empty // false
+["foo"] is empty // false
+["foo"] is not empty // true
+undefined is empty // undefined
+undefined is not empty // undefined
+```
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/language/collection-operations.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/language/collection-operations.mdx
new file mode 100644
index 0000000000..b919843b09
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/language/collection-operations.mdx
@@ -0,0 +1,60 @@
+---
+page_title: Sentinel Language - Collection Operations
+sidebar_title: Collection Operations
+sidebar_current: docs-language-collection-operations
+description: |-
+ Operations for interacting with collections (list, map).
+layout: docs
+---
+
+# Language: Collection Operations
+
+Collection operations are expressions that are performed on a list or map to
+return a variation of the initial data.
+
+At the moment, `filter` is the only collection operation available.
+
+## Filter Expression
+
+`filter` is a [quantifier
+expression](/sentinel/language/spec#quantifier-expressions-any-all-filter-map) that
+returns a subset of the provided collection. Only elements whose filter body
+returns true will be returned. If any of the elements filter body returns
+undefined, the final result will be undefined.
+
+Filter uses the same syntax as the `any` and `all` [boolean
+expressions](/sentinel/language/boolexpr#any-all-expressions):
+
+```sentinel
+filter list as value { condition } // Single-iterator, list
+filter list as idx, value { condition } // Double-iterator, list
+
+filter map as key { condition } // Single-iterator, map
+filter map as key, value { condition } // Double-iterator, map
+```
+
+Examples:
+
+```sentinel
+l = [1, 1, 2, 3, 5, 8]
+evens = filter l as v { v % 2 is 0 } // [2, 8]
+
+m = { "a": "foo", "b": "bar" }
+matched_foo = filter m as _, v { v is "foo" } // { "a": "foo" }
+```
+
+## Map Expression
+
+`map` is a [quantifier
+expression](/sentinel/language/spec#quantifier-expressions-any-all-filter-map) that
+returns a list, regardless of the input collection type. Each element within the
+input collection is evaluted according to the map expression body and appended
+to the result.
+
+```sentinel
+l = [1, 2]
+r = map l as v { v % 2 } // [false, true]
+
+m = { "a": "foo", "b": "bar" }
+r = map m as k, v { v } // ["foo", "bar"]
+```
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/language/conditionals.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/language/conditionals.mdx
new file mode 100644
index 0000000000..af85dafcc6
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/language/conditionals.mdx
@@ -0,0 +1,162 @@
+---
+page_title: Sentinel Language - Conditionals
+sidebar_title: Conditionals
+sidebar_current: docs-language-conditional
+description: |-
+ Conditional statements allow your policy to behave differently depending on a condition.
+layout: docs
+---
+
+# Language: Conditionals
+
+Conditional statements allow your policy to behave differently depending
+on a condition.
+
+Conditional statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## If Statements
+
+`if` statements only execute their bodies if a condition is met.
+The syntax of an `if` statement is:
+
+```sentinel
+if condition {
+ // ... this is executed if condition is true
+}
+```
+
+The `condition` must result in a boolean, such as by calling a function
+or evaluating a [boolean expression](/sentinel/language/boolexpr). If
+the `condition` is `true`, the body (within the `{}`) is executed. Otherwise,
+the body is skipped.
+
+Examples:
+
+```sentinel
+// This would execute the body
+value = 12
+if value is 18 {
+ print("condition met")
+}
+
+// Direct boolean values can be used
+value = true
+if value {
+ print("condition met")
+}
+
+// This would not execute the body since the boolean expression will
+// result in undefined.
+value = {}
+if value["key"] > 12 {
+ print("condition met")
+}
+```
+
+### Else, Else If
+
+An `else` clause can be given to an `if` statement to execute a body
+in the case the condition is _not met_. By putting another `if` statement
+directly after the `else`, multiple conditions can be tested for.
+The syntax is:
+
+```sentinel
+if condition {
+ // ...
+} else {
+ // ...
+}
+
+if condition {
+ // ...
+} else if other_condition {
+ // ...
+} else {
+ // ...
+}
+```
+
+### Scoping
+
+The body of an `if` statement does not create a new
+[scope](/sentinel/language/scope). Any variables assigned within the body
+of an if statement will modify the scope that the `if` statement itself
+is in.
+
+Example:
+
+```sentinel
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+```sentinel
+a = 18
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+## Case Statements
+
+`case` statements are a selection control mechanism that execute a clause based
+on matching expressions. It is worth noting that the expression for `case` is
+optional. When no expression is provided, it defaults the expression to `true`.
+Additionally, the order of clauses is important, as they are evaluated from top
+to bottom, executing the first match.
+The syntax of a case statement is:
+
+```sentinel
+case expression {
+ when clause_expression:
+ // executed when clause_expression and expression are equal
+ else:
+ // executed if no clause matches expression
+}
+```
+
+### When Clause
+
+Any clause that has an expression for comparison must use the `when` keyword.
+It accepts a list of expressions, seperated by a `,`.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+}
+```
+
+```sentinel
+case {
+ when x > 40:
+ return true
+}
+```
+
+### Else Clause
+
+The `else` keyword allows for capturing any expressions that have no matching
+`when` clause.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+ else:
+ return false
+}
+```
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/language/functions.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/language/functions.mdx
new file mode 100644
index 0000000000..c55dfe4d81
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/language/functions.mdx
@@ -0,0 +1,174 @@
+---
+page_title: Sentinel Language - Functions
+sidebar_title: Functions
+sidebar_current: docs-language-functions
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Functions
+
+Functions allow you to create reusable code to perform computations.
+
+Below is a trivial example function that doubles a number and shows
+the main rule using the function:
+
+```sentinel
+double = func(x) {
+ return x * 2
+}
+
+main = rule { double(12) is 24 }
+```
+
+Functions may contain any expressions,
+[conditionals](/sentinel/language/conditionals),
+and [loops](/sentinel/language/loops). They must return a value
+at the end of their execution. If no reasonable return value exists,
+the function should return [undefined](/sentinel/language/undefined).
+
+## Creating a Function
+
+A function is created by assigning a variable to a `func`.
+
+A function can have zero or more parameters. These parameters are specified
+within parentheses `()` separated by commas. If a function has no parameters,
+the parentheses must still be present.
+
+A function must terminate with a `return` statement to return a value back to
+the caller. Only a single value can be returned. The type of a return can
+vary between return statements.
+
+Example:
+
+```sentinel
+add1 = func(x) { return x + 1 }
+```
+
+This example creates a function that adds 1 to the parameter `x`. It is
+assigned to the variable `add1`.
+
+## Calling a Function
+
+Functions are called by accessing their name followed by parentheses with
+a list of comma-separated values for the parameters. If the function takes no
+parameters, an empty set of parentheses must still be used to denote a
+function call.
+
+To call the function in the example above:
+
+```sentinel
+x = 1
+y = add1(x)
+```
+
+## Scoping
+
+The body of a `func` creates a new [scope](/sentinel/language/scope).
+
+The parent scope is the scope in which the function is created. This allows
+for the creation of functions within functions that can access their outer
+scopes.
+
+Example:
+
+```sentinel
+f = func() {
+ a = 42
+ print(a) // 42
+ return undefined
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+f = func() {
+ a = 42
+ return undefined
+}
+
+print(a) // 18
+```
+
+And below is an example of creating a function in a function which uses
+outer values:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double()
+}
+
+print(f()) // 84
+```
+
+A more complex example below shows how scoping works when passing
+functions around as arguments or results:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double
+}
+
+double = f()
+print(double()) // 84
+```
+
+## Pass By Value
+
+The parameters to a function are passed by value, but not deep copied.
+This means that elements of collections can still be modified but the
+root value cannot be changed.
+
+Example:
+
+```sentinel
+f = func(x) {
+ x = "value"
+ return x
+}
+
+x = "outside"
+f(x)
+print(x) // "outside"
+```
+
+```sentinel
+f = func(x) {
+ append(x, "value")
+ return x
+}
+
+x = []
+f(x)
+print(x) // ["value"]
+```
+
+## Recursion
+
+A function is allowed to call other functions, including itself.
+This can be used to implement recursion. For example, the fibonacci sequence
+is implemented below:
+
+```sentinel
+fib = func(x) {
+ if x <= 0 {
+ return undefined
+ }
+
+ if x == 1 {
+ return 1
+ } else {
+ return x + fib(x - 1)
+ }
+}
+```
+
+Note that this example also shows using
+[undefined](/sentinel/language/undefined)
+as a return value in cases where a function has undefined behavior.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/language/imports.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/language/imports.mdx
new file mode 100644
index 0000000000..945bb95930
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/language/imports.mdx
@@ -0,0 +1,117 @@
+---
+page_title: Sentinel Language - Imports
+sidebar_title: Imports
+sidebar_current: docs-language-imports
+description: >-
+ Imports enable a Sentinel policy to access reusable libraries and external
+ data and functions.
+layout: docs
+---
+
+# Language: Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external
+data and functions. The exact list of available imports is dependent on the
+host system. Host systems may also make the available imports configurable
+via plugins.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that
+the import `calendar` allows access to engineer calendars. This import
+only exists for the purpose of this example and at the time of writing is
+not a real available import.
+
+```sentinel
+import "calendar"
+
+// Get the calendar for Bob for today
+bob_calendar = calendar.for("bob").today
+
+// Allow this policy to pass if Bob is not on vacation.
+main = rule { not bob_calendar.has_event("vacation") }
+```
+
+This example shows an import being used to deny a behavior if Bob is on
+vacation. Hopefully real policies do not depend on a single person being
+in the office, but the example shows the power of imports.
+
+In addition to consuming imports, you can also
+[extend Sentinel by writing custom imports](/sentinel/extending)
+This allows you to add any arbitrary behavior to your Sentinel-protected
+systems.
+
+## Importing
+
+Whether accessing a built-in library or an external plugin, the syntax
+for an import is the same:
+
+```sentinel
+import "name"
+```
+
+This adds the import with the name `name`. It can be referenced using
+the identifier `name`. For example, if the import above exposed first
+and last name data, you could access it via `name.first` or `name.last`.
+
+You can shadow imports by assigning a variable. In the example below,
+the import `name` becomes unavailable within the function because it is
+shadowed by a variable of the same name.
+
+Shadowing an import is not the same as overwriting a variable. Imports
+are not part of the [scope](/sentinel/language/scope), meaning that
+the shadow only exists for the scope it is created within. For everything
+else, the import works even after it is shadowed. The example below shows that
+as well.
+
+```sentinel
+import "name"
+
+f = func() {
+ name = "jane"
+ return name
+}
+
+print(f()) // "jane"
+print(name.first) // "bob"
+```
+
+## Aliases
+
+An import can be aliased to another name using the syntax below:
+
+```sentinel
+import "name" as other
+```
+
+This would make the import "name" available as `other`.
+
+An import may only be imported once, regardless of aliases. The following
+would be **invalid** and would result in an error:
+
+```sentinel
+import "name" as one
+import "name" as two
+```
+
+## Accessing Import Data
+
+Imports are accessed with dot-separated identifiers, such as `name.first` or
+`name.stage.first`. These are called _selector expressions_. An import may
+return nested data that further can be accessed via selector expressions.
+
+In the first example on this page with the "calendar" import, we see this:
+
+```sentinel
+import "calendar"
+
+a = calendar.for("bob") // selector expression calendar.for
+b = a.today // selector expression on the result
+c = b.vacation // accessing further nested data
+```
+
+The exact structure of an import is dependent on the import. Please reference
+the documentation for an import to learn more.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/language/index.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/language/index.mdx
new file mode 100644
index 0000000000..cc21d9b1e8
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/language/index.mdx
@@ -0,0 +1,115 @@
+---
+page_title: Sentinel Language
+sidebar_title: Language
+sidebar_current: docs-language-basics
+description: |-
+ Sentinel policies are written using the Sentinel language. This language
+ is easy to learn and easy to write. You can learn the Sentinel language
+ and be productive within an hour. Learning Sentinel doesn't require any
+ formal programming experience.
+layout: docs
+---
+
+# Sentinel Language
+
+Sentinel policies are written using the Sentinel language. This language
+is easy to learn and easy to write. You can learn the Sentinel language
+and be productive within an hour. Learning Sentinel doesn't require any
+formal programming experience.
+
+This language guide will contain many details that are not necessary to
+be productive with Sentinel or are targeted to those who are looking for
+exact information about the language (such as what types of line endings are
+allowed). You can safely ignore these sentences.
+
+You may also view the
+[official language specification](/sentinel/language/spec)
+This is a specific and detailed document on the syntax and behavior of
+the language primarily intended for implementation creators and to disambiguate
+the language.
+
+## Simplest Example
+
+The example below is about the simplest practical example of Sentinel.
+It is reasonable to imagine this as a realistic policy. This shows that
+in most cases, Sentinel will be extremely simple:
+
+```sentinel
+main = rule { request.method is "GET" and request.headers contains "X-Key" }
+```
+
+## Files
+
+Sentinel policies are single files that end in the `.sentinel` file extension.
+There is currently no built-in mechanism to Sentinel for merging multiple
+files. This is purposefully done to make Sentinel policies easy to submit
+to systems that support Sentinel policies.
+
+Sentinel policy files must be UTF-8 encoded and can end in both
+Unix (LF) or Windows (CRLF) line breaks. When running the
+[auto-formatter](#),
+line endings will always use Unix line breaks.
+
+## Ordering
+
+Sentinel policies are executed top-down. For example:
+
+```sentinel
+a = 1 // a = 1 here
+b = a + 1 // b = 2 here
+a = 3 // a = 3, b = 2
+```
+
+In this example, the value of `a` and `b` is shown at each line. Since Sentinel
+executes values top-down, the final value of `a` is 3 and `b` is 2. `b` _does
+not_ become 4.
+
+## Main
+
+Sentinel expects there to be a `main` [rule](/sentinel/language/rules).
+The value of this rule is the result of the entire policy.
+
+The result of the policy depends on the evaluated contents of the `main` rule.
+For booleans, a policy passes on a `true` value, and fails on a `false` value.
+Other types generally follow a zero or zero-length pattern for determining
+success.
+
+| Type | Passing Condition | Failing Condition |
+| ------- | ----------------- | -------------------------- |
+| Boolean | `true` | `false` |
+| String | `""` | Any non-zero length string |
+| Integer | `0` | Any non-zero value |
+| Float | `0.0` | Any non-zero value |
+| List | `[]` | Any non-zero length list |
+| Map | `{}` | Any non-zero length map |
+
+A value of main that falls outside of the above types will result in a policy
+error. As a special case, if `main` evaluates to an undefined value, the error
+message will indicate as such, with a reference to where the undefined value was
+encountered.
+
+## More Complex Example
+
+The simple example above is a full working example. In our experience with
+Sentinel, many policies can be representing using this simple form. However,
+to show more features of the language, a more complex example is shown below.
+This example is also a realistic example of what Sentinel may be used for.
+
+```sentinel
+import "units"
+
+memory = func(job) {
+ result = 0
+ for job.groups as g {
+ for g.tasks as t {
+ result += t.resources.memory else 0
+ }
+ }
+
+ return result
+}
+
+main = rule {
+ memory(job) < 1 * units.gigabyte
+}
+```
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/language/lists.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/language/lists.mdx
new file mode 100644
index 0000000000..d1e02cdb15
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/language/lists.mdx
@@ -0,0 +1,136 @@
+---
+page_title: Sentinel Language - Lists
+sidebar_title: Lists
+sidebar_current: docs-language-lists
+description: Lists are a collection of zero or more values.
+layout: docs
+---
+
+# Language: Lists
+
+Lists are a collection of zero or more values.
+
+Lists can be created using by wrapping values in `[]` and separating them
+by commas. An optional trailing comma is allowed. List elements can be
+differing types. Examples:
+
+```sentinel
+[] // An empty list
+["foo"] // Single element list
+["foo", 1, 2, true] // Multi element list with different types
+["foo", [1, 2]] // List containing another list
+```
+
+A list can be [sliced](/sentinel/language/slices) to create sublists.
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for value inclusion in a list.
+
+## Accessing Elements
+
+List elements can be accessed with the syntax `name[index]` where
+`index` is zero-indexed.
+
+A negative index accesses the list in reverse. It is the same as
+reversing a list and then using a positive index. Similar to a positive
+index, it is bounded by the length of the list as a negative value.
+
+Accessing beyond the length of the list results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+a = ["foo", 1, true, [1, 2]]
+
+a[0] // "foo"
+a[2] // true
+a[4] // undefined
+a[-2] // true
+a[-4] // "foo"
+a[-5] // undefined
+a[3][1] // 2
+```
+
+## List Append
+
+Values can be appended to a list using the built-in
+[append](/sentinel/functions/append) function.
+
+This modifies the list in-place and returns
+[undefined](/sentinel/language/undefined). For more information on
+why `append` behaves this way, please read the full documentation for the
+[append](/sentinel/functions/append) function.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append([1,2], "foo") // [1, 2, "foo"]
+append([1,2], [3]) // [1, 2, [3]]
+append(1, 3) // error()
+```
+
+## List Concatenation
+
+Two lists can be concatenated using the `+` operator or the shorthand
+`+=` assignment operator. For the `+` operator, a new list is returned.
+For `+=`, the left-hand list is modified in place.
+
+Examples:
+
+```sentinel
+[1] + [2] // [1, 2]
+[1] + [[1]] // [1, [1]]
+[1] + 1 // error
+
+a = [1]
+a += [2] // a = [1, 2]
+a += 3 // error
+```
+
+## List Length
+
+The length of a list can be retrieved using the
+[length](/sentinel/functions/length) function.
+
+Examples:
+
+```sentinel
+length([]) // 0
+length(["foo"]) // 1
+```
+
+## Removing Items From a List
+
+You can use a combination of [list concatenation](#list-concatenation) and
+[slicing](/sentinel/language/slices) to remove elements from a list.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+a = a[:2] + a[3:] // [1, 2, 4, 5]
+```
+
+The shorthand shown here is effectively the same as `a[0:2] + a[3:length(a)]`,
+which creates a new list out of the concatenation two sub-lists composed of the
+first two elements, and the rest of the list starting at index 3. This
+effectively removes the 3rd element from the list (index 2).
+
+## List Comparison
+
+Lists may be [compared](/sentinel/language/boolexpr#comparison-operators)
+for equality. Lists are equal if they are of equal length and their
+corresponding elements are comparable and equal. Lists with the same elements
+but in a different order are not equal.
+
+```sentinel
+[1, 2] is [1, 2] // true
+[1, 2] is [2, 1] // false
+["a"] is ["a", "b"] // false
+["a", ["b", "c"]] is ["a", ["b", "c"]] // true
+```
+
+List comparison speed is O(N), meaning that the speed of the comparison is
+_linearly_ proportional to the number of elements in the list. The more
+elements, the more iterations that are necessary to verify equality.
+
+-> The N-value quoted above should account for the sum of the elements of _all_
+lists in the subjects of comparison, as list comparison will recurse into these
+lists to check for equality.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/language/logging-errors.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/language/logging-errors.mdx
new file mode 100644
index 0000000000..32427697d9
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/language/logging-errors.mdx
@@ -0,0 +1,53 @@
+---
+page_title: Sentinel Language - Logging and Errors
+sidebar_title: Logging and Errors
+sidebar_current: docs-language-log
+description: The built-in functions `print` and `error` can be used for logging and errors.
+layout: docs
+---
+
+# Language: Logging and Errors
+
+The built-in functions `print` and `error` can be used for logging
+and errors. Logging is helpful to understand how a policy is executing
+in development. And errors are useful to halting execution immediately in
+certain scenarios.
+
+## Print
+
+The `print` function is used to output debug information. The exact location
+where this print statements go is dependent on the host application and
+the Sentinel runtime itself.
+
+The `print` function takes one or more Sentinel values as arguments.
+Each value is formatted for human-friendly output and space-separated.
+Example:
+
+```sentinel
+value = 42
+print("the value is", value) // the value is 42
+
+map = { "foo": false }
+print(map) // { "foo": false }
+
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // false
+```
+
+## Errors
+
+Certain actions in Sentinel, such as accessing an undefined variable,
+attempting to add two incompatible types, etc. result in _runtime errors_.
+These errors halt the execution of a policy immediately. The pass/fail result
+of a policy is considered fail.
+
+Runtime errors can be manually triggered using the `error` function.
+The `error` function behaves exactly like the `print` function except that
+execution is halted immediately.
+
+This should only be used in scenarios where the policy _must fail_ due to
+some unexpected or invalid condition. The line between `error` and
+[undefined](/sentinel/language/undefined) can sometimes be unclear. Undefined
+is recommended when a policy can conceivably recover or safely ignore the
+undefined behavior. An error should be used when the policy should fail and
+notify someone regardless.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/language/loops.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/language/loops.mdx
new file mode 100644
index 0000000000..2a04c6957e
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/language/loops.mdx
@@ -0,0 +1,93 @@
+---
+page_title: Sentinel Language - Loops
+sidebar_title: Loops
+sidebar_current: docs-language-loops
+description: >-
+ Loop statements allow you to execute a body of code for each element in a
+ collection or for some fixed number of times.
+layout: docs
+---
+
+# Language: Loops
+
+Loop statements allow you to execute a body of code for each element in
+a collection or for some fixed number of times.
+
+Loop statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## For Statements
+
+`for` statements allow repeated execution of a block for each
+element in a collection.
+
+Example:
+
+```sentinel
+// A basic sum
+count = 0
+for [1, 2, 3] as num {
+ count += num
+}
+```
+
+The syntax is `for COLLECTION as value`. This will iterate over the
+collection, assigning each element to `value`. In the example above,
+each element is assigned to `num`. The body is executed for each element.
+In the example above, the body adds `num` to the `count` variable. This
+creates a basic sum of all values in the collection.
+
+For a map, the assigned element is the key in the map. In the example
+below, `name` would be assigned map keys.
+
+```sentinel
+list = []
+for { "a": 1, "b": 2 } as name {
+ append(list, name)
+}
+
+print(list) // ["a" "b"]
+```
+
+An alternate syntax is `for COLLECTION as key, value`. This will assign
+both the key and value to a variable. For a list, the key is the element
+index. For a map, it is the key and value is assigned the element value.
+Example:
+
+```sentinel
+count = 0
+for { "a": 1, "b": 2 } as name, num {
+ count += num
+}
+
+print(count) // 3
+```
+
+## Scoping
+
+The body of a `for` statement creates a new
+[scope](/sentinel/language/scope). If a variable is assigned within
+the body of a for statement that isn't assigned in a parent scope,
+that variable will only exist for the duration of the body execution.
+
+Example:
+
+```sentinel
+for list as value {
+ a = 42
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+for list as value {
+ a = 42
+}
+
+print(a) // 18
+```
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/language/maps.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/language/maps.mdx
new file mode 100644
index 0000000000..4d3721ee9a
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/language/maps.mdx
@@ -0,0 +1,122 @@
+---
+page_title: Sentinel Language - Maps
+sidebar_title: Maps
+sidebar_current: docs-language-maps
+description: >-
+ Maps are a collection of zero or more key/value pairs. These are useful for
+ storing values that are looked up by a unique key.
+layout: docs
+---
+
+# Language: Maps
+
+Maps are a collection of zero or more key/value pairs. These are useful
+for storing values that are looked up by a unique key.
+
+Maps can be created using `{}` and specifying key/value pairs. Keys and
+values can be differing types. An optional trailing comma is allowed.
+Examples:
+
+```sentinel
+// Empty map
+{}
+
+// Map with a single value on one line
+{ "key": "value" }
+
+// Map with multiple values with differing types on multiple lines
+{
+ "key": "value",
+ 42: true,
+}
+```
+
+Maps are **unordered**. When
+[looping](/sentinel/language/loops)
+over a map, the key/value pairs can be returned in any order.
+
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for key inclusion in a map.
+
+## Accessing Elements
+
+Map elements can be accessed with the syntax `name[key]`. This looks up
+a value by a key.
+
+Accessing a key that doesn't exist results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+map = { "key": "value", 42: true, }
+
+map["key"] // "value"
+map[42] // true
+map[0] // undefined
+```
+
+## Modifying or Adding Elements
+
+Elements can be added or modified in a map by assigning to `name[key]`.
+If the key doesn't exist, the value is added. If the key already exists,
+the value is overridden.
+
+```sentinel
+map = { "key": "value" }
+
+map[42] = true // Add a new key/value
+map["key"] = 12 // Modify the value of "key"
+```
+
+## Deleting Elements
+
+An element can be deleted from a map using the
+[delete](/sentinel/functions/delete) function.
+
+Examples:
+
+```sentinel
+map = { "key": "value" }
+delete(map, "key") // map is now empty
+delete(map, "other") // no effect for non-existent key
+```
+
+## Keys and Values
+
+The keys and values of a map can be retrieved as
+[lists](/sentinel/language/lists) using the
+[keys](/sentinel/functions/keys) and
+[values](/sentinel/functions/values) functions.
+
+Because maps are unordered, the keys and values are returned in an
+unspecified order. It should not be assumed that keys and values will be
+returned in a consistent order.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+## Map Comparison
+
+Maps may be [compared](/sentinel/language/boolexpr#comparison-operators) for
+equality. Maps are equal if they are of equal length and both their
+corresponding keys and values are comparable and equal.
+
+```sentinel
+{"foo": "bar"} is {"foo": "bar"} // true
+{"foo": "bar"} is {"baz": "bar"} // false
+{"foo": "bar"} is {"foo": "baz"} // false
+{"foo": "bar"} is {"foo": "bar", "baz": "qux"} // false
+{1: "a"} is {1.0: "a"} // true (int/float comparable)
+
+// also true (maps are not ordered):
+{"m": {"a": "b"}, "l": ["a"]} is {"l": ["a"], "m": {"a": " b"}}
+```
+
+-> The current worst-case comparison speed for maps is O(N²), or _quadratic
+time_. This is the square of the cumulative elements or the parent and all child
+maps contained within the map. Consider this when making comparisons on larger,
+more complex maps. This may be optimized in the future.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/language/parameters.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/language/parameters.mdx
new file mode 100644
index 0000000000..10bd3aac72
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/language/parameters.mdx
@@ -0,0 +1,133 @@
+---
+page_title: Sentinel Language - Parameters
+sidebar_title: Parameters
+sidebar_current: docs-language-parameters
+description: >-
+ Parameteres allow a Sentinel policy to describe variables that are expected
+ to be supplied by the calling application, in addition to any default value.
+layout: docs
+---
+
+# Language: Parameters
+
+Sentinel allows a policy author to supply parameters to help facilitate policy
+reuse and ensure sensitive values do not need to be hard-coded in a policy.
+
+Parameters are supplied by using the `param` keyword, followed by an identifier.
+A default value can also be supplied by using the `default` keyword.
+
+```sentinel
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+```
+
+Once declared, parameters can be used like any other variable, including being
+re-assigned.
+
+```sentinel
+param foo default 1 // 1 (default)
+
+foo = foo + 1 // 2
+```
+
+## Variable Descriptions
+
+You can supply a description to a parameter by adding a comment at the top of
+it. This value can be communicated to a specific implementation of Sentinel to
+provide information about what the parameter is for during configuration.
+
+```sentinel
+// An example parameter. Must be supplied or the policy will fail.
+param foo
+```
+
+## Supplying Parameter Values Using the Sentinel CLI
+
+In a production implementation, supplying parameters to a policy is an
+implementation-specific detail - see the documentation for your particular
+implementation for details.
+
+Using the [Sentinel CLI](/sentinel/commands), you can supply parameters one of
+four ways.
+
+### Supplying Parameter Values Using the Configuration File
+
+You can supply parameters using the
+[`param`](/sentinel/configuration#parameters) section of the
+[configuration file](/sentinel/configuration).
+
+```json
+{
+ "param": {
+ "foo": "bar"
+ }
+}
+```
+
+This method works for both `sentinel apply` and `sentinel test`.
+
+### Supplying Parameter Values Using the Environment
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can supply a value using environment variables - prefix the parameter with
+`SENTINEL_PARAM_`, using the name of the parameter to supply.
+
+```plaintext
+SENTINEL_PARAM_foo=bar sentinel apply policy.sentinel
+```
+
+### Supplying Parameter Values Using the CLI
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can also use the `-param` CLI argument to supply parameter in a `key=value`
+pair.
+
+```plaintext
+sentinel apply -param foo=bar policy.sentinel
+```
+
+### Interactive CLI Prompting
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+If a required value has not been supplied when a policy is run with `sentinel apply`, it will be prompted for, along with its description:
+
+
+ $ sentinel apply policy.sentinel
+
+ policy.sentinel:2:7: requires value for parameter foo
+
+ An example parameter. Must be supplied or the policy will fail.
+
+
+ Values can be strings, floats, or JSON array or object values. To force
+
+ strings, use quotes.
+
+
+ Enter a value: bar
+
+
+
+ Pass
+
+
+
+### CLI Value Format
+
+-> **NOTE:** This section contains details for the parameter features supported by `sentinel apply`.
+
+The CLI takes either strings, or JSON numbers, arrays, or maps. If you need a
+literal string value, quote the value.
+
+```sentinel
+foo // string
+42 // number (float)
+"42" // string ("42", without quotes)
+[1, 2] // array (list)
+{"a": "b"} // object (map)
+```
+
+-> **NOTE:** Boolean values are not supported by this method.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/language/rules.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/language/rules.mdx
new file mode 100644
index 0000000000..967ce77a7d
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/language/rules.mdx
@@ -0,0 +1,203 @@
+---
+page_title: Sentinel Language - Rules
+sidebar_title: Rules
+sidebar_current: docs-language-rules
+description: >-
+ Rules form the basis of a policy by representing behavior that is either
+ passing or failing (true or false).
+layout: docs
+---
+
+# Language: Rules
+
+Rules form the basis of a policy by representing behavior that is either passing
+or failing. A policy can be broken down into a set of rules. Breaking down a
+policy into a set of rules can make it more understandable and aids with
+testing.
+
+An example is shown below:
+
+```sentinel
+is_sunny = rule { weather is "sunny" }
+is_wednesday = rule { day is "wednesday" }
+
+main = rule {
+ is_sunny and
+ is_wednesday
+}
+```
+
+A rule contains a single expression. This can be a simple [boolean
+expression](/sentinel/language/boolexpr), or an expression representing the
+discovery of a set of violations using more in-depth expressions like [`filter`
+and `map`](/sentinel/language/collection-operations). You can split expressions
+into multiple lines for readability, as you would with any other expression
+within Sentinel.
+
+A rule is also _lazy_ and _memoized_. _Lazy_ means that the rule is only
+evaluated when it is actually required, not at the point that it is
+created. This is covered in more detail in the [lazy](#lazy-and-memoized) section.
+_Memoized_ means that the value is only computed once and then saved. Once
+a rule is evaluated, the result of it is reused anytime the rule is referenced
+again.
+
+## Easing Understandability
+
+Rules are an abstraction that make policies much more understandable
+by making it easier for humans to see what the policy is trying to do.
+
+For example, consider the policy before which doesn't abstract into rules:
+
+```sentinel
+main = rule {
+ ((day is "saturday" or day is "sunday") and homework is "") or
+ (day in ["monday", "tuesday", "wednesday", "thursday", "friday"] and
+ not school_today and homework is "")
+}
+```
+
+Assume that `day`, `homework`, and `school_day` are available.
+
+In plain English, this policy is trying to say: you can play with your friends
+only on the weekend as long as there is no homework, or during the week if
+there is no school and no homework.
+
+Despite the relatively simple nature of this policy (a few lines, about 5
+logical expressions), it is difficult to read and understand, especially if
+you've not seen it before.
+
+The same policy using rules as an abstraction:
+
+```sentinel
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+is_valid_weekend = rule { is_weekend and homework is "" }
+is_valid_weekday = rule { not is_weekend and not school_today and homework is "" }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+By reading the names of the rules, its much clearer to see what each individual
+part of the policy is trying to achieve. The readability is improved even
+further when adding comments to the rules to explain them further, which is
+a recommended practice:
+
+```sentinel
+// A weekend is Sat or Sun
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+// A valid weekend is a weekend without homework
+is_valid_weekend = rule { is_weekend and homework is "" }
+
+// A valid weekday is a weekday without school
+is_valid_weekday = rule { not is_weekend and not school_today and homework is "" }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+## "When" Predicates
+
+A rule may have an optional `when` predicate attached to it. When this is
+present, the rule is only evaluated when the predicate results in `true`.
+Otherwise, the rule is not evaluated and the result of the rule is always
+`true`.
+
+"When" predicates should be used to define the context in which the rule
+is semantically meaningful. It is common when translating human language
+policy into Sentinel to have scenarios such as "when the key has a prefix
+of `/account/`, the remainder must be numeric." In that example, when the
+key _does not_ have the specified prefix, the policy doesn't apply.
+
+Assume you have rules `is_prefix` and `is_numeric` to check both the
+conditions in the example above. An example is shown with and without
+the "when" predicate:
+
+```sentinel
+example_no_when = rule { (is_prefix and is_numeric) or not is_prefix }
+
+example_when = rule when is_prefix { is_numeric }
+```
+
+The rules are equivalent in behavior for all values of `is_prefix` and
+`is_numeric`, but the second rule is more succint and easier to understand.
+
+## Testing
+
+To verify a policy works correct, the
+[built-in Sentinel test framework](/sentinel/writing/testing)
+uses rules as the point where you can assert behavior. You say that
+you expect certain rules to be true or false. By doing so, you ensure that
+the policy results in the value you expect using the rule flow that you
+expect.
+
+Therefore, in addition to readability, we recommend splitting policies into
+rules to aid testability.
+
+## Non-Boolean Values
+
+Sentinel supports non-boolean values in rules. This can be useful for passing
+more detail along to a calling integration when more detail is required than a
+boolean value would be able to communicate. This data shows up in the [policy
+trace](/sentinel/writing/tracing) and can be utilized in different ways
+depending on the integration you are using Sentinel with.
+
+Consider a different take on our policy above:
+
+```sentinel
+// A policy to check a set of scheduled days to determine what days you can't
+// come out and play, based on the day not being a weekend day and there being
+// homework to do.
+
+param days
+
+main = rule {
+ filter days as d {
+ d.day not in ["saturday", "sunday"] and
+ d.homework is not ""
+ }
+}
+```
+
+When given a value in the set of `days` that has a `day` that is a weekday, and
+`homework` present, the policy will fail. Additional, when tracing was enabled,
+the days that were detected as violating the policy would be given in the trace
+for the `main` rule.
+
+Generally, for non-boolean values, a policy fails on non-zero data. See the
+details on [the `main` rule](/sentinel/language#main) for more details.
+
+The result of a rule must be either a boolean, string, integer, float, list, or
+map. All other types will result in a runtime error.
+
+## Lazy and Memoized
+
+A rule is _lazy_ and _memoized_.
+
+_Lazy_ means that the rule is only evaluated when it is actually required,
+not at the point that it is created. And _memoized_ means that the value is
+only computed once and then saved. Once a rule is evaluated, the result of it
+is reused anytime the rule is referenced again.
+
+Both of these properties have important implications for Sentinel
+policies:
+
+**Performance:** Rules are a way to improve the performance of a Sentinel
+policy. If you have a boolean expression that is reused a lot, a rule will
+only be evaluated once. In the example shown above, `is_weekend` is used
+multiple times, but will only have to be evaluated once.
+
+**Behavior:** Rules accessing variables see the value of those variables
+at _the time they are evaluated_. This can lead to surprising behavior
+in some cases. For example:
+
+```sentinel
+a = 1
+b = rule { a == 1 }
+a = 2
+main = b
+```
+
+In this example, `main` will actually result to `false`. It is evaluated
+when it is needed, which is when the policy executes `main`. At this point,
+`a` is now 2 and `b` has not been evaluated yet. Therefore, `b` becomes `false`.
+All future references to `b` will return `false` since a rule is memoized.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/language/scope.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/language/scope.mdx
new file mode 100644
index 0000000000..2fb562a75d
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/language/scope.mdx
@@ -0,0 +1,42 @@
+---
+page_title: Sentinel Language - Scope
+sidebar_title: Scope
+sidebar_current: docs-language-scope
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Scope
+
+Scope is the context in which variables are created and accessed. If a
+variable exists in a parent scope, it is accessed and can be modified.
+Otherwise, the variable is created in your current scope.
+
+Scopes are created with _blocks_. A block is a possibly empty sequence
+of statements within matching brace brackets `{}`. You may nest blocks
+to create nested scopes.
+
+In addition to explicitly created blocks, there are implicit blocks:
+
+1. The policy block encapsulates an entire policy.
+2. Each `any`, `all`, and `for` statement is considered to be in
+ its own block. Note that `if` statements _do not_ create their
+ own block.
+
+The example policy below shows various effects of scopes:
+
+```sentinel
+a = 1
+print(a) // 1
+
+f = func() {
+ print(a) // 1
+ a = 12
+ b = 1
+ return undefined
+}
+f()
+
+print(a) // 12
+print(b) // undefined
+```
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/language/slices.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/language/slices.mdx
new file mode 100644
index 0000000000..a8dafd5ec2
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/language/slices.mdx
@@ -0,0 +1,46 @@
+---
+page_title: Sentinel Language - Slices
+sidebar_title: Slices
+sidebar_current: docs-language-slices
+description: >-
+ Slices are an efficient way to create a substring or sublist from an existing
+ string or list, respectively.
+layout: docs
+---
+
+# Language: Slices
+
+Slices are an efficient way to create a substring or sublist from an
+existing string or list, respectively.
+
+The syntax for creating a slice is:
+
+```sentinel
+a[low : high]
+```
+
+`low` is the lowest index to slice from. The resulting slice includes
+the value at `low`. `high` is the index to slice to. The resulting slice
+will not include the value at `high`. The length of the resulting list
+or string is always `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+
+a = "hello"
+b = a[1:4] // "ell"
+```
+
+## Convenience Shorthands
+
+Some convenience shorthands are available by omitting the value for either the
+low or high index in the slice expression: omitting `low` implies a low index of
+0, and omitting `high` implies a high index of the length of the list.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+
+b = a[:2] // [1, 2] (same as a[0:2])
+b = a[2:] // [3, 4, 5] (same as a[2:length(a)])
+```
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/language/spec.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/language/spec.mdx
new file mode 100644
index 0000000000..ea9d41f4aa
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/language/spec.mdx
@@ -0,0 +1,1336 @@
+---
+page_title: Sentinel Language Specification
+sidebar_title: Specification
+sidebar_current: docs-language-spec
+description: This is the specification for the Sentinel policy language.
+layout: docs
+---
+
+# Sentinel Language Specification
+
+This is the specification for the Sentinel policy language.
+
+The Sentinel language is designed with policy enforcement in mind. It is dynamically typed and garbage collected and has explicit support for _rule_ construction representing boolean logic.
+
+The language is designed to be easy to learn and use by non-programmers. It is expected to be embedded within applications.
+
+## Table of Contents
+
+
+
+
+- [Source code representation](#source-code-representation)
+- [Declarations and Scope](#declarations-and-scope)
+- [Blocks](#blocks)
+- [Lexical Elements](#lexical-elements)
+ - [Comments](#comments)
+ - [Identifiers](#identifiers)
+ - [Keywords](#keywords)
+ - [Operators and Delimiters](#operators-and-delimiters)
+ - [Integer Literals](#integer-literals)
+ - [Floating-point Literals](#floating-point-literals)
+ - [String Literals](#string-literals)
+ - [Implicit Line Joining](#implicit-line-joining)
+ - [Whitespace](#whitespace)
+ - [Semicolons](#semicolons)
+- [Variables](#variables)
+- [Undefined](#undefined)
+- [Expressions](#expressions)
+ - [Operand](#operand)
+ - [Primary Expressions](#primary-expressions)
+ - [Null](#null)
+ - [Booleans](#booleans)
+ - [Boolean Literals](#boolean-literals)
+ - [Boolean Expressions](#boolean-expressions)
+ - [List Literals](#list-literals)
+ - [Map Literals](#map-literals)
+ - [Function Literals](#function-literals)
+ - [Rule Expressions](#rule-expressions)
+ - [Index Expressions](#index-expressions)
+ - [Selectors](#selectors)
+ - [Slice Expressions](#slice-expressions)
+ - [Calls](#calls)
+ - [Operators](#operators)
+ - [Operator Precedence](#operator-precedence)
+ - [Arithmetic Operators](#arithmetic-operators)
+ - [Integer operators](#integer-operators)
+ - [Integer overflow](#integer-overflow)
+ - [Floating-point operators](#floating-point-operators)
+ - [String Concatenation](#string-concatenation)
+ - [List Concatenation](#list-concatenation)
+ - [Comparison Operators](#comparison-operators)
+ - [Logical Operators](#logical-operators)
+ - [Set Operators](#set-operators)
+ - [Matches Operator](#matches-operator)
+ - [Else Operator](#else-operator)
+ - [Quantifier Expressions (any, all, filter, map)](#quantifier-expressions-any-all-filter-map)
+- [Statements](#statements)
+ - [Expression Statements](#expression-statements)
+ - [Assignments](#assignments)
+ - [If Statements](#if-statements)
+ - [Case Statements](#case-statements)
+ - [For Statements](#for-statements)
+ - [Break Statements](#break-statements)
+ - [Continue Statements](#continue-statements)
+ - [Return Statements](#return-statements)
+- [Imports](#imports)
+- [Parameters](#parameters)
+- [Built-in Functions](#built-in-functions)
+ - [Length](#length)
+ - [Collections](#collections)
+ - [List Append](#list-append)
+ - [Map Delete](#map-delete)
+ - [Keys and Values](#keys-and-values)
+ - [Range](#range)
+ - [Type Conversion](#type-conversion)
+ - [Printing](#printing)
+ - [Errors](#errors)
+- [Program Execution](#program-execution)
+
+
+
+## Source code representation
+
+Source code is Unicode text encoded in UTF-8. A "character" referenced in this document refers to a Unicode code point. Each code point is distinct: upper and lower case letters are different characters.
+
+The underscore character \_ (U+005F) is considered a "letter".
+
+```ebnf
+letter = unicode_letter | "_" .
+decimal_digit = "0" … "9" .
+octal_digit = "0" … "7" .
+hex_digit = "0" … "9" | "A" … "F" | "a" … "f" .
+```
+
+## Declarations and Scope
+
+A declaration binds an identifier to a value. An identifier is declared at the point it is first assigned. An assignment is considered a first assignment if the identifier hasn't already been previously declared in the current scope or any parent scopes.
+
+The scope of an identifier is the extent of source text in which the identifier denotes the specified value. Sentinel is lexically scoped using blocks.
+
+An identifier declared in a block may be redeclared in an inner block. While the identifier of the inner declaration is in scope, it denotes the value assigned in the inner declaration.
+
+## Blocks
+
+A block is a possibly empty sequence of statements within matching brace brackets.
+
+```ebnf
+Block = "{" StatementList "}" .
+StatementList = { Statement ";" } .
+```
+
+Blocks nest and affect scoping.
+
+In addition to explicit blocks in the source code, there are implicit blocks:
+
+1. The universe block encompasses all source text.
+2. Each program has a program block containing all source text for that program.
+3. Each "any", "all", and "for" statements is considered to be in its own implicit block. "if" does not create an implicit block.
+
+## Lexical Elements
+
+### Comments
+
+Comments are sections of source text used for documentation.
+
+```plaintext
+# Single line comment
+// Single line comment
+/* multi
+line
+ comment */
+```
+
+Three forms of comments are supported: two single-line forms and one multi-line form. A single-line comment begins with the token `//` or `#`. Everything between the starting token and the end of the line is ignored.
+
+A multi-line comment begins with the token `/*` and ends with the token `*/`. Everything between `/*` and `*/` is ignored.
+
+A comment may not start inside a string literal or inside another comment.
+
+A multi-line comment containing no newlines acts like a space.
+
+### Identifiers
+
+Identifiers name program entities such as rules, variables, and functions. An identifier is a sequence of one or more letters and digits. The first character in an identifier must be a letter.
+
+```ebnf
+identifier = letter { letter | unicode_digit } .
+```
+
+```sentinel
+a
+_a
+_A
+αβ
+```
+
+#### Pre-Declared Identifiers
+
+The following identifiers are implicitly declared in the [universe block](#blocks):
+
+```plaintext
+Constants:
+ false null true undefined
+
+Functions:
+ append bool delete error float int keys length print range string values
+```
+
+### Keywords
+
+The following keywords are reserved and may not be used as identifiers:
+
+```plaintext
+all
+any
+as
+break
+case
+continue
+default
+else
+filter
+for
+func
+if
+import
+map
+param
+return
+rule
+when
+```
+
+### Operators and Delimiters
+
+The following character sequences represent operators, delimiters, and other special tokens:
+
+```plaintext
++
+-
+*
+/
+%
++=
+-=
+*=
+/=
+%=
+==
+<
+>
+=
+!
+!=
+<=
+>=
+( )
+[ ]
+{ }
+,
+.
+:
+;
+and
+contains
+else
+in
+is
+matches
+not
+or
+xor
+```
+
+As a special case, `else` is both a keyword and operator, depending on the context. See [Else Operator](#else-operator) for more details.
+
+### Integer Literals
+
+An integer literal is a sequence of digits representing an integer constant. An optional prefix sets a non-decimal base: `0` for octal, `0x` or `0X` for hexadecimal. In hexadecimal literals, letters `a-f` and `A-F` represents values 10 through 15.
+
+Integers are signed 64-bit values (-9223372036854775808 to 9223372036854775807).
+
+```ebnf
+int_lit = decimal_lit | octal_lit | hex_lit .
+decimal_lit = ( "1" … "9" ) { decimal_digit } .
+octal_lit = "0" { octal_digit } .
+hex_lit = "0" ( "x" | "X" ) hex_digit { hex_digit } .
+```
+
+```
+42
+0600
+0xBadFace
+170141183460469231731687303715884105727
+```
+
+### Floating-point Literals
+
+A floating-point literal is a decimal representation of a floating-point constant. It has an integer part, a decimal point, a fractional part, and an exponent part. The integer and fractional part comprise decimal digits; the exponent part is an e or E followed by an optionally signed decimal exponent. One of the integer part or the fractional part may be elided; one of the decimal point or the exponent may be elided.
+
+Floating-point numbers are IEEE-754 64-bit floating numbers.
+
+```ebnf
+float_lit = decimals "." [ decimals ] [ exponent ] |
+ decimals exponent |
+ "." decimals [ exponent ] .
+decimals = decimal_digit { decimal_digit } .
+exponent = ( "e" | "E" ) [ "+" | "-" ] decimals .
+```
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+### String Literals
+
+String literals are character sequences between double quotes, as in "bar". Within the quotes, any character may appear except newline and unescaped double quote. The text between the quotes forms the value of the literal.
+
+A multi-character sequence beginning with a backslash encode values in various formats.
+
+Backslash escapes allow arbitrary values to be encoded as ASCII text. There are four ways to represent the integer value as a numeric constant: `\x` followed by exactly two hexadecimal digits; `\u` followed by exactly four hexadecimal digits; `\U` followed by exactly eight hexadecimal digits, and a plain backslash `\` followed by exactly three octal digits. In each case the value of the literal is the value represented by the digits in the corresponding base.
+
+After a backslash, certain single-character escapes represent special values:
+
+```plaintext
+\a U+0007 alert or bell
+\b U+0008 backspace
+\f U+000C form feed
+\n U+000A line feed or newline
+\r U+000D carriage return
+\t U+0009 horizontal tab
+\v U+000b vertical tab
+\\ U+005c backslash
+\" U+0022 double quote
+```
+
+The three-digit octal (\nnn) and two-digit hexadecimal (\xnn) escapes represent individual bytes of the resulting string; all other escapes represent the (possibly multi-byte) UTF-8 encoding of individual characters. Thus inside a string literal \377 and \xFF represent a single byte of value 0xFF=255, while ÿ, \u00FF, \U000000FF and \xc3\xbf represent the two bytes 0xc3 0xbf of the UTF-8 encoding of character U+00FF.
+
+A string value is a (possibly empty) sequence of bytes. Strings are immutable: once created, it is impossible to change the contents of a string.
+
+The length of a string s (its size in bytes) can be discovered using the built-in function `length`. An individual character (of type string) can be accessed by integer indices 0 through `length(s)-1`.
+
+```ebnf
+string_lit = `"` { unicode_value | byte_value } `"` .
+unicode_value = unicode_char | little_u_value | big_u_value | escaped_char .
+byte_value = octal_byte_value | hex_byte_value .
+octal_byte_value = `\` octal_digit octal_digit octal_digit .
+hex_byte_value = `\` "x" hex_digit hex_digit .
+little_u_value = `\` "u" hex_digit hex_digit hex_digit hex_digit .
+big_u_value = `\` "U" hex_digit hex_digit hex_digit hex_digit
+ hex_digit hex_digit hex_digit hex_digit .
+escaped_char = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | `"` ) .
+```
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+### Implicit Line Joining
+
+Expressions can be split over more than one line. For example:
+
+```sentinel
+a or
+b or # This is a comment
+c
+```
+
+Implicitly continued lines can have trailing comments. Blank continued lines are allowed.
+
+### Whitespace
+
+Whitespace is needed to separate tokens, but no distinction is made between the number and combination of whitespace characters. Whitespace characters can be space (U+0020), horizontal tabs (U+0009), carriage returns (U+000D), and newlines (U+000A).
+
+```sentinel
+a or b
+
+a or b
+
+a or
+ b
+
+rule { a or b }
+
+rule {
+ a or b }
+
+rule {
+ a or
+ b
+}
+```
+
+### Semicolons
+
+The formal grammar uses semicolons ";" as terminators in a number of productions.
+It is idiomatic Sentinel source to omit semicolons in most cases.
+
+A semicolon is automatically inserted into the token stream immediately after
+a line's final token if that token is:
+
+- An identifier
+- An integer, float, or string literal
+- The keyword `break`, `continue`, or `return`
+- The delimiter `)`, `]`, or `}`
+
+## Variables
+
+A variable is a storage location for holding a value.
+
+A variable has a dynamic type, which is the concrete type of the value assigned to the variable at run time. The dynamic type may vary during execution and change as a result of further assignments.
+
+A variable's value is retrieved by referring to the variable in an expression; it is the most recent value assigned to the variable. If a variable has not yet been declared (initially assigned), it is an error.
+
+```sentinel
+x = 7 // x is type int
+x = "foo" // x is now type string
+
+x = y // error if y is not previously assigned
+```
+
+## Undefined
+
+The value denoted by the keyword `undefined` represents undefined behavior or values. It can be created directly using the keyword `undefined`. It is also returned as a result of expressions in specified cases.
+
+`undefined` is a valid operand for any operations. Only `undefined or true` will result in true. All other operations result in `undefined`. An exception is if `undefined` is not reached as a result of short-circuit operations.
+
+```sentinel
+undefined OR true = true
+undefined OR false = undefined
+undefined OR undefined = undefined
+undefined AND true = undefined
+undefined AND false = undefined
+undefined AND undefined = undefined
+undefined XOR true = undefined
+undefined XOR false = undefined
+undefined XOR undefined = undefined
+
+// Short-circuit examples
+false OR true OR undefined = true
+false OR undefined OR true = true
+true AND false AND undefined = false
+true AND undefined AND false = undefined
+
+// Non-logical operators
+undefined + 5 = undefined
+-undefined = undefined
+!undefined = undefined
+```
+
+If the result of the main rule is `undefined`, it is treated as `false` but is indicative of erroneous logic.
+
+## Expressions
+
+### Operand
+
+Operands denote the elementary values in an expression. An operand may be a literal, an identifier denoting a variable, rule, or function, or a parenthesized expression.
+
+```ebnf
+Operand = Literal | OperandName | MethodExpr | "(" Expression ")" .
+Literal = BasicLit | CompositeLit | FunctionLit | RuleLit .
+BasicLit = int_lit | float_lit | string_lit .
+OperandName = identifier | QualifiedIdent.
+```
+
+### Primary Expressions
+
+Primary expressions are the operands for unary and binary expressions.
+
+```ebnf
+PrimaryExpr =
+ Operand |
+ PrimaryExpr Selector |
+ PrimaryExpr Index |
+ PrimaryExpr Slice |
+ PrimaryExpr Arguments |
+ PrimaryExpr ( "is empty" | "is not empty" ).
+
+Selector = "." identifier .
+Index = "[" Expression "]" .
+Slice = "[" [ Expression ] ":" [ Expression ] "]" |
+ "[" [ Expression ] ":" Expression ":" Expression "]" .
+Arguments = "(" [ ( ExpressionList | Type [ "," ExpressionList ] ) [ "..." ] [ "," ] ] ")" .
+```
+
+```sentinel
+x
+2
+(s + ".txt")
+f(3.1415, true)
+m["foo"]
+s[i : j + 1]
+obj.color
+f.p[i].x()
+x is empty
+x is not empty
+```
+
+### Null
+
+The reserved word `null` denote the singleton value `null`. Null represents the explicit absense of a value. Behavior of `null` within expressions is specified explicitly for each expression.
+
+### Booleans
+
+### Boolean Literals
+
+The reserved words `true` and `false` denote objects that represent the boolean values `true` and `false`, respectively. These are boolean literals.
+
+```ebnf
+BoolLit = "true" | "false" .
+```
+
+#### Boolean Expressions
+
+Boolean expressions are expressions that must result in a boolean value. Any other value type becomes the `undefined` value.
+
+```ebnf
+BoolExpr = Expression .
+```
+
+### List Literals
+
+A list literal denotes a list, which is an integer indexed collection of values.
+
+```ebnf
+ListLit = "[" [ ElementList [ "," ] ] "]" .
+ElementList = Element { "," Element } .
+Element = Expression | LiteralValue .
+```
+
+A list may contain zero or more values. The number of values in a list is its length.
+
+A list has a set of indices. An empty list has an empty set of indices. A non-empty list has the index set `{0...n - 1}` where `n` is the length of the list. Attempting to access a list using an index that is not a member of its set of indices results in the `undefined` value.
+
+### Map Literals
+
+A map literal denotes a map, which is an unordered group of elements indexed by a set of unique keys.
+
+```ebnf
+MapLit = "{" [ KeyedElementList [ "," ] ] "}" .
+KeyedElementList = KeyedElement { "," KeyedElement } .
+KeyedElement = Element ":" Element .
+```
+
+Keys can only be a boolean, numeric, or string type.
+
+The value of a non-existent key is the `undefined` value.
+
+### Function Literals
+
+A function literal represents a function.
+
+```ebnf
+FunctionLit = "func" Function .
+Function = Parameters FunctionBody .
+FunctionBody = Block .
+Parameters = "(" [ IdentifierList [ "," ] ] ")" .
+IdentifierList = identifier { "," identifier } .
+```
+
+```sentinel
+func(a, b) { return b }
+```
+
+Function literals are only allowed in the file scope. They may refer to variables defined in surrounding blocks.
+
+A function must terminate with a return statement. If a function has no meaningful return value, it should return `undefined`.
+
+### Rule Expressions
+
+A rule is an expression that is evaluated lazily and the result is memoized.
+
+If the optional "when" predicate is present, the rule is evaluated only when
+the "when" boolean expression results in true. If the predicate is false,
+the rule is not evaluated and returns true. The predicate is evaluated when
+the rule would be evaluated; it is also lazy and memoized in the same way.
+
+```ebnf
+RuleExpr = "rule" [ "when" BoolExpr ] "{" Expr "}" .
+```
+
+```sentinel
+rule { x is y }
+
+rule {
+ x == "value" or
+ y == "other"
+}
+
+rule when x is y { y > 42 }
+
+rule { map ["a", "b", "c"] as _, id {
+ { "id": id }
+}}
+```
+
+### Index Expressions
+
+A primary expression of the form `a[x]` denotes the element of a list, map, or string indexed by x. The value x is called the index or key.
+
+For `a` of type map:
+
+- If `a` contains a key `x`, `a[x]` is the map value with key `x`.
+- If `a` does not contain key `x`, `a[x]` is the `undefined` value.
+
+For `a` of type list:
+
+- `x` must be an integer
+- `x` must be in the range `[-1 * length(a), length(a)-1]`
+- If `x` is contained in the set of indices of `a`, `a[x]` is the list element at index `x`. If `x` is negative, `a[x]` is equivalent to `a[length(a)+x]`.
+- If `x` is not contained in the set of indices of `a`, `a[x]` is the `undefined` value.
+
+For `a` of type string:
+
+- `x` must be an integer
+- `x` must be in the range `[-1 * length(a), length(a)-1]`
+- If `x` is in range, `a[x]` is the byte value at index `x` as a string. If `x` is negative, `a[x]` is equivalent to `a[length(a)+x]`.
+- If `x` is out of range, `a[x]` is the `undefined` value.
+
+For `a` of value `null`:
+
+- `a[x]` is the `undefined` value.
+
+Otherwise `a[x]` is an error.
+
+### Selectors
+
+For a primary expression x, the selector expression `x.f` denotes the field `f` of the value `x`. The identifier `f` is called the selector. The type of the selector expression is the type of the selector.
+
+As a special case, selectors can be [reserved words](#keywords) and keyword [operators](#operators-and-delimiters), but cannot be any other non-identifier element.
+
+Selectors are used to access data from [imports](#imports). The first primary expression `x` in `x.f` denotes the import name. The field `f` is the selector to access data from the import.
+
+Selectors may also be used to access map data with static keys. They are syntactic sugar over index expressions. `math.pi` is equivalent to `math["pi"]` and exhibit the same limitations and behavior as an index expression. Selectors cannot, however, be used to assign values to map data.
+
+Selectors on undefined result in undefined.
+
+```sentinel
+math.pi
+time.pst.hour
+```
+
+### Slice Expressions
+
+Slice expressions construct a substring or list from a string or list.
+
+The primary expression
+
+```sentinel
+a[low : high]
+```
+
+constructs a substring or list. The indices `low` and `high` select which elements of operand `a` appear in the result. The result has indices starting at 0 and length equal to `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+```
+
+For convenience, any of the indices may be omitted. A missing `low` index defaults to zero; a missing `high` index defaults to the length of the sliced operand:
+
+```sentinel
+a[2:] // same as a[2 : length(a)]
+a[:3] // same as a[0 : 3]
+a[:] // same as a[0 : length(a)]
+```
+
+The indices are in range if `0 <= low <= high <= length(a)`, otherwise they are out of range. If the indices are out of range at run time, the result is the `undefined` value.
+
+If `a` is the value `null`, the result is the `undefined` value.
+
+If `a` is any other value type, it is an error.
+
+### Calls
+
+Given an expression `f` where `f` is a function value:
+
+```sentinel
+f(a1, a2, … an)
+```
+
+calls `f` with arguments `a1, a2, ... an`. The type of the expression is the result of `f`. The arguments are evaluated left to right. Arguments are passed by value.
+
+### Operators
+
+```ebnf
+Expression = UnaryExpr | Expression binary_op Expression .
+UnaryExpr = PrimaryExpr | unary_op UnaryExpr .
+
+binary_op = logical_op | set_op | rel_op | add_op | mul_op | else_op .
+logical_op = "and" | "or" | "xor" .
+set_op = ["not"] ( "contains" | "in" ).
+rel_op = "==" | "!=" | "<" | "<=" | ">" | ">=" |
+ "is" | "is not" | "matches" | "not matches" .
+add_op = "+" | "-" .
+mul_op = "*" | "/" | "%" .
+else_op = "else" .
+unary_op = "+" | "-" | "!" | "not" .
+```
+
+#### Operator Precedence
+
+Unary operators have the highest precedence.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Binary operators of the same precedence associate from left to right. For instance, x / y _ z is the same as (x / y) _ z.
+
+### Arithmetic Operators
+
+Arithmetic operators apply to numeric values and yield a result of the same type when both operands are the same.
+
+Arithmetic operations between integer and floating-point types result in a floating-point value with the integer operand treated as a floating-point value for purposes of calculation.
+
+Arithmetic operations between numeric values (integer, floating-point) and non-numeric values (strings, lists) are not permitted.
+
+All five supported arithmetic operators (+, -, \*, /, %) apply to both integer and floating-point types; + also applies to strings.
+
+```plaintext
++ sum integers, floats, strings, lists
+* difference integers, floats
+* product integers, floats
+/ quotient integers, floats
+% remainder integers, floats
+```
+
+#### Integer operators
+
+For two integer values x and y, the integer quotient `q = x / y` and remainder `r = x % y` satisfy the following relationships:
+
+```sentinel
+x = q*y + r and |r| < |y|
+```
+
+with x / y truncated towards zero.
+
+```plaintext
+ x y x / y x % y
+ 5 3 1 2
+-5 3 -1 -2
+ 5 -3 -1 2
+-5 -3 1 -2
+```
+
+As an exception to this rule, if the dividend x is the most negative value for the int type of x (-9223372036854775808), the quotient `q = x / -1` is equal to x (and r = 0).
+
+If the divisor is a constant, it must not be zero. If the divisor is zero at run time, an error occurs.
+
+#### Integer overflow
+
+For signed integers, the operations `+`, `-`, and `*` may legally overflow and the resulting value exists and is deterministically defined by the signed integer representation, the operation, and its operands. No exception is raised as a result of overflow.
+
+#### Floating-point operators
+
+For floating-point numbers, +x is the same as x, while -x is the negation of x. The result of a floating-point division by zero is not specified beyond the IEEE-754 standard.
+
+#### String Concatenation
+
+Strings can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = "hi"
+y = "hello"
+x = x + ", " + y // "hi, hello"
+x += " and good bye" // "hi, hello and good bye"
+```
+
+String addition creates a new string by concatenating the operands.
+
+#### List Concatenation
+
+Lists can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = [1, 2]
+x = x + [2, 3] // [1, 2, 2, 3]
+x += [4] // [1, 2, 2, 3, 4]
+```
+
+List addition creates a new list by concatenating the operands.
+
+### Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+In any comparison, the two operands must be equivalent types. The only exception are integers and floats, which are considered numeric and may be compared. Any comparison between other non-matching types results in the `undefined` value.
+
+The equality operators `==`, `!=`, `is`, and `is not` apply to operands that are comparable. The ordering operators `<`, `<=`, `>`, and `>=` apply to operands that are ordered. The behavior of `is` with `==` and `is not` with `!=` is identical. These terms and the result of the comparisons are defined as follows:
+
+- Boolean values are comparable. Two boolean values are equal if they are either both true or both false.
+- Integer values are comparable and ordered, in the usual way.
+- Floating point values are comparable and ordered, as defined by the IEEE-754 standard.
+- An integer compared with floating point value treats the integer as the converted floating point value.
+- String values are comparable and ordered, lexically byte-wise.
+- Lists are comparable. Lists are equal if they are of equal length and their
+ corresponding elements are comparable and equal.
+- Maps are comparable. Maps are equal if they are of equal length and both their
+ corresponding keys and values are comparable and equal.
+
+If either operand is the `undefined` value, the result of the expression is the `undefined` value.
+
+### Emptiness Comparisons
+
+Checking the emptiness of an object can be achieved by using one of the emptiness expressions, `is empty` or `is not empty`. Although similar to [Comparison Operators](#comparison-operators) in that they yield a boolean value and read as though a comparison is taking place, both `is empty` and `is not empty` are evaluated as a multi-word expression.
+
+An emptiness comparison can only be performed against collections, strings or `undefined`, with the same rules as the [built-in length](/sentinel/functions/length) function.
+
+```sentinel
+"" is empty // true
+"foo" is empty // false
+[] is empty // true
+[1] is empty // false
+{} is empty // true
+{"a": "b"} is empty // false
+
+"" is not empty // false
+"foo" is not empty // true
+[] is not empty // false
+[1] is not empty // true
+{} is not empty // false
+{"a": "b"} is not empty // true
+
+undefined is empty // undefined
+undefined is not empty // undefined
+```
+
+### Logical Operators
+
+Logical operators apply to boolean values and yield a boolean result.
+
+Logical operators are evaluated left-to-right and perform short-circuit logic. The right-hand side is not guaranteed to be evaluated if a short-circuit can be performed.
+
+```plaintext
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+### Set Operators
+
+The set operators `contains` and `in` test for set inclusion for lists
+and maps, and substring inclusion for strings.
+
+Set operators may be negated by prefixing the operator with `not`: `not contains` and `not in`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+`contains` tests if the left-hand collection contains the right-hand value. For lists, this tests equality of any value. For maps, this tests equality of any key. For strings, this tests for substring existence.
+
+`in` tests if the right-hand collection contains the left-hand value. The behavior is equivalent to `contains`.
+
+The collection must be a list, map, or string. If it is the `undefined` value, the result is the `undefined` value. For any other value, the result is an error.
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+
+"test" contains "est" // true
+"test" contains "best" // false
+"test" in "testing" // true
+"best" in "testing" // false
+```
+
+### Matches Operator
+
+The matches operator tests if a string matches a regular expression.
+
+The matches operators may be negated by prefixing the operator with `not`: `not matches`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+The left-hand value is the string to test. The right-hand value is a string value representing a regular expression.
+
+The syntax of the regular expression is the same general syntax used by Python, Ruby, and other languages. More precisely, it is the syntax accepted by RE2. The regular expression is not anchored by default; any anchoring must be explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+If either operand is `undefined`, the result is the `undefined` value. For any other non-string value, the result is an error.
+
+### Else Operator
+
+Else operators can be used to provide a default value for an expression that may evaluate to the `undefined` value. Else operators return their left-hand value unless it is `undefined`, otherwise they return their right-hand value.
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else null
+```
+
+### Quantifier Expressions (any, all, filter, map)
+
+Quantifiers are expressions that apply a predicate to a collection (list or map). The purpose of the quantifier is to produce a result based on its particular type.
+
+`any` and `all` expressions are existential and universal quantifiers, respectively. `any` expressions are equivalent to a chain of `or` and `all` expressions are equivalent to a chain of `and`. Both expressions implement short-circuiting equivalent to logical operators.
+
+The `map` expression is an apply-to-all quantifier and returns a new list for all values in the input collection. The output list will be the same length as the input collection.
+
+The `filter` expression is a subset quantifier and returns the subset of all values in the input collection for which the supplied predicate applies. This will be zero or more elements, up to the length of the input.
+
+The body of a quantifier expression is a boolean expression.
+
+`any` returns the boolean value `true` if any value in the collection expression results in the body expression evaluating to `true`. If the body expression evaluates to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression result in the body expression evaluating to `true`. If any value in the collection expression result in the body expression evaluating to `false`, the all expression returns `false`.
+
+For empty collections, `any` returns false and `all` returns `true`.
+
+`map` always returns a list, regardless of if the input collection is a list itself; maps (as in the data type) also return lists when processed through `map`. Each element is the result of the supplied expression body.
+
+`filter` returns a list or map, the same type as its input collection. Only the elements for which the expression body evaluated to `true` will be returned. If an iteration of the expression body results in `undefined`, the entire result set is `undefined`.
+
+When the collection is a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When the collection is a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+QuantExpr = QuantOp Expression "as" [ identifier "," ] identifier "{" BoolExpr "}" .
+QuantOp = "all" | "any" | "filter" | "map" .
+```
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" } // true if every iterations is true
+any group.tasks as t { t.driver is "vmware" } // true if any iteration is true
+map group.tasks as t { { "driver": t.driver } } // [{"driver": "vmware"}, ...]
+filter group.tasks as t { t.driver is "vmware" } // subset of tasks that is true
+```
+
+## Statements
+
+### Expression Statements
+
+Function call expressions may exist in the statement context.
+
+```ebnf
+ExpressionStmt = Expression .
+```
+
+### Assignments
+
+Assignments set the value of an expression to the value of another expression.
+
+```ebnf
+Assignment = AssignExpr assign_op Expression .
+AssignExpr = identifier | IndexExpr .
+assign_op = [ add_op | mul_op ] "=" .
+```
+
+The assignment `x op= y` is equivalent to `x = x op (y)` for supported values of `op`.
+
+```sentinel
+a = 1
+b[12] = 42
+c["key"] = "value"
+
+a *= 12
+b[12] += 8
+```
+
+Assignments to lists and maps can be carried out using [index expressions](#index-expressions), with the operands of the index expression being evaluated alongside the right hand side expression in a right-to-left (RHS, LHS) order.
+
+In addition to the rules detailed in the section describing their use, the following rules apply when assigning to maps or lists using index expressions:
+
+- The [variable](#variables) must exist and be a list or map. Attempts to use an index assignment on an unknown variable, or a variable that not a list or map, results in a runtime error.
+- Assigning to a list or map index that already exists overwrites that index's data with evaluated right hand side's value.
+- Assigning to an unknown key in a map creates a key for that map with the evaluated right hand side's value assigned to that key.
+- Attempting to assign a value to a list index that is out of range results in a runtime error.
+
+### If Statements
+
+If statements specify the conditional execution of two branches according to the value of a boolean expression. If the expression evaluates to true, the "if" branch is executed, otherwise, if present, the "else" branch is executed.
+
+```ebnf
+IfStmt = "if" BoolExpr Block [ "else" ( IfStmt | Block ) ] .
+```
+
+```sentinel
+if x < y {
+ return x
+} else if x > z {
+ return z
+} else {
+ return y
+}
+```
+
+### Case Statements
+
+Case statements specify a selection control mechanism to conditionally execute a branch based on expression equality.
+
+Each clause that wishes to provide an expression must use the "when" keyword. Multiple expressions can be provided, separated with a comma (",").
+
+There can be one, optional, "else" clause within a `case` statement. The "else" clause is executed if all other clauses failed to run.
+
+The clauses are evaluated in the order they are listed, with the first successful evaluation being executed.
+
+A `case` statement can optionally provide an expression. If no expression is provided, it is the equivalent of writing `case true {`.
+
+```ebnf
+CaseStmt = "case" [ Expression ] "{" { CaseWhenClause } "}" .
+CaseWhenClause = CaseWhenCase ":" StatementList .
+CaseWhenCase = "when" Expression { "," Expression } | "else" .
+```
+
+```sentinel
+case x {
+when y, z:
+ return true
+else:
+ return false
+}
+
+case {
+when x > 42:
+ return true
+else:
+ return false
+}
+```
+
+### For Statements
+
+A `for` statement specifies repeated execution of a block.
+
+The expression must evaluate to a list or a map.
+
+When iterating over a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When iterating over a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+ForStmt = "for" Expressions "as" [ identifier "," ] identifier Block .
+```
+
+```sentinel
+for [1, 2, 3] as v { count += v } // basic sum
+
+for [1, 2, 3] as idx, v {
+ if idx > 1 { count += v }
+}
+
+data = { "a": 12, "b": 32 }
+for data as k { count += data[k] } // sum map values
+for data as k, v { count += v }
+```
+
+### Break Statements
+
+A `break` statement terminates execution of the innermost `for` statement
+within the same function.
+
+```ebnf
+BreakStmt = "break" .
+```
+
+```sentinel
+// Will only print "1"
+for [1, 2, 3] as v {
+ print(v)
+ break
+}
+```
+
+### Continue Statements
+
+A `continue` statement begins the next iteration of the innermost `for` loop.
+The `for` loop must be within the same function.
+
+```ebnf
+ContinueStmt = "continue" .
+```
+
+```sentinel
+// Will print 1, 3
+for [1, 2, 3] as v {
+ if v == 2 {
+ continue
+ }
+
+ print(v)
+ break
+}
+```
+
+### Return Statements
+
+A return statement in a function F terminates the execution of F and provides the result value.
+
+```ebnf
+ReturnStmt = "return" Expression .
+```
+
+A function must terminate with a return statement.
+
+The return statement can be invoked at any time during a function to terminate execution.
+
+## Imports
+
+An import adds an assignment to the global scope to external definitions.
+
+The import declarations must only appear at the top of the source file, before any other statements. Comments may appear before imports.
+
+```ebnf
+ImportDecl = "import" Name ["as" identifier] .
+Name = string_lit .
+```
+
+An import name and identifier must be distinct. Two names may not repeated even if they're declared with different identifiers.
+
+If an identifier is not specified, the identifier is the name of the import itself.
+
+An import is not a valid value. The identifier representing the import may not be used as an expression, such as in assignment statements or call expressions.
+
+Values in imports are not assignable directly via their selectors. Function calls may be used to alter the state of the external data represented by the import. Whether or not this is supported is specific to the import implementation. The exception to this assignment restriction is the memoized data returned by a call to an import (see below).
+
+Data returned by imports can be memoized, meaning that data is returned by the import in the form of a map which is then used as much as possible without having to return to the import for more data. For example, `t = time.now` returns a map so that `t.hour` will be able to be looked up without having to go back to the import for that data.
+
+Data returned by imports may be callable, meaning that methods may be called on the memoized data returned by an import. For example, given `t = time.now`, `t.after(some_previous_time)` is valid, with the receiver data being set to the local memoized data stored in `t`. It is a valid case to accept modifications to the receiver data (example: assignment to the memoized data via index expressions), as long as all data in in the receiver continues to be string-keyed. It is an invalid case to accept receiver data with non-string-typed keys. Methods may also alter the contents of receiver data so that it is different after the call is finished.
+
+As with methods, imports may also have callable keys where the data may not be memoized. Example: in the event of `v = foo.bar`, if `v.baz` is not included in the memoized data, a call will be made to the import to fetch it. The same rules and restrictions to receiver data apply to callable keys as do to method calls.
+
+The support for callable methods and non-memoized keys on data returned by imports is specific to the import implementation.
+
+The handling of import loading is specific to the runtime implementation.
+
+Implicit imports may be added by the runtime, for example for standard library definitions. An explicit import of the same name should override a matching implicit import. For example, if "time" is an implicit import and the program specifies `import "time" as cal`, then only `cal` is defined.
+
+```sentinel
+import "time"
+import "math" as m
+```
+
+## Parameters
+
+```ebnf
+ParamDecl = "param" identifier [ "default" Expression ]
+```
+
+A parameter is a way for a policy to describe variables that are expected to be supplied by the calling application, in addition to any default value.
+
+Parameter declarations must appear at the top of the source file, following imports.
+
+Like imports, comments may appear before parameters; this is mainly pertinent when the policy is not importing anything, which would make parameters the first declarations that appear in a policy.
+
+A parameter declaration describes a valid variable that is added to the scope of a policy at runtime. This variable has the same properties and rules as other variables assigned during the course of policy evaluation as defined by the specification. This includes having a dynamic type and being able to be re-assigned during execution.
+
+Parameters may only be strings, integers, floating point numbers, booleans, lists, or maps. More complex types such as rules or functions are not allowed.
+
+A parameter can specify a default value by adding one in the declaration via use of the "default" keyword, and supplying a literal for the default value. The literal for a default is a very limited expression, comprising of:
+
+- For strings, a simple string literal;
+- For integers and floating-point numbers, either a literal denoting the value, or a unary expression with the literal, and the operators - or +;
+- For booleans, the pre-declared identifiers true or false;
+- For lists and maps, their respective literals composed of values and keys (for maps) of the above values only.
+
+Any other type of expression or identifier is not allowed.
+
+A parameter that does not have a default value defined is required by the policy. Evaluating a policy with a required parameter that has not been supplied at execution results in a runtime error.
+
+Parameters must not conflict with imports or any other identifiers currently defined in the global scope, including any reserved or pre-declared identifiers. Attempting to assign a parameter to an existing value results in a runtime error.
+
+```sentinel
+import "time"
+
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+param time default "11:00" // error, conflicts with "time" import
+param undefined // error, reserved identifier
+```
+
+## Built-in Functions
+
+Built-in functions are predeclared. They are called like any other function.
+
+### Length
+
+The built-in function `length` returns the length of a collection of string.
+
+The length of a string is the number of bytes in the string. It is not necessarilly the number of characters.
+
+The length of a collection is the number of elements in that collection.
+
+The length of `undefined` is `undefined`.
+
+### Collections
+
+#### List Append
+
+The built-in function `append` appends a value to the end of a list in-place.
+
+Appending to a non-list value results in an immediate `fail`.
+
+The return value of `append` is always the `undefined` value.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+#### Map Delete
+
+The built-in function `delete` deletes elements from a map by key. The map is modified in-place.
+
+Deleting a key that does not exist does nothing.
+
+Calling delete for a non-map value results in an immediate `fail`.
+
+The return value of `delete` is always the `undefined` value.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
+
+#### Keys and Values
+
+The built-in function `keys` and `values` return the keys and values of a map, respectively. The return value is a list. Both functions return values in an unspecified order.
+
+The `keys` or `values` of `undefined` is `undefined`.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+### Range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it defaults to `1`.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4]
+range(1, 5, 2) // [1,3]
+range(0, -3, -1) // [0,-1,-2]
+```
+
+### Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a value of that type according to the rules below.
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
+
+### Printing
+
+The built-in function `print` can be used to output formatted debug information. The runtime may omit print function calls depending on its execution mode. The runtime may choose where the output of a print function goes.
+
+`print` is a variadic function, accepting one or more values to be printed; values are separated by a single whitespace character (`" "`).
+
+The return value of `print` is always `true` to allow it to be used in boolean expressions.
+
+```sentinel
+print("hello") // "hello"
+print("hello", "world") // "hello world"
+print("The", "number", "is", 42) // "The number is 42"
+print([1, 2, 3]) // "[1, 2, 3]"
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // "false"
+```
+
+### Errors
+
+The built-in function `error` is equivalent to `print` but immediately causes execution to halt and the main rule to evaluate to `false`. The program execution is considered to have failed.
+
+## Program Execution
+
+Program execution begins by evaluating the source top to bottom. If evaluation is successful, the `main` rule is evaluated and its result is returned. Execution can be interrupted at any time by an error, which can be called explicitly or implicitly through illegal or undefined behavior.
+
+---
+
+> The content of this page is licensed under the [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
+>
+> Based on [The Go Programming Language Specification](https://golang.org/ref/spec) licensed under [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/language/undefined.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/language/undefined.mdx
new file mode 100644
index 0000000000..fa8dd50cc4
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/language/undefined.mdx
@@ -0,0 +1,98 @@
+---
+page_title: Sentinel Language - Undefined
+sidebar_title: Undefined
+sidebar_current: docs-language-undefined
+description: >-
+ The value `undefined` is a special value representing undefined behavior or an
+ unknown value. Any operation on an `undefined` value results in `undefined`.
+layout: docs
+---
+
+# Language: Undefined
+
+The value `undefined` is a special value representing undefined behavior
+or an unknown value.
+
+Undefined is not an error because there are situations where the undefined
+value can be safely ignored and the language also provides construts for
+recovering from an undefined value.
+
+The undefined value can be created manually with `undefined`. In most cases,
+undefined is created by accessing a non-existent list element or value.
+The undefine value is created in a number of other circumstances. The documentation
+will note when an undefined value may be created.
+
+Almost any operation with `undefined` results in `undefined`. For example,
+performing math on undefined, attempting to call a function that is undefined,
+etc.
+
+## Main and Undefined
+
+If the value `undefined` is returned from the top-level `main` rule, then
+the policy is considered `false`. However, the runtime provides mechanisms
+for the host application to determine the policy failure was caused by
+an undefined value and report that to the user.
+
+The `main` rule returning the undefined value should be considered a logical
+error in most cases and should probably be corrected by the policy writer.
+
+## Debugging Undefined
+
+When an `undefined` value is first created (either explicitly or implicitly),
+the runtime will record the position where the undefined was created. This
+location can be used to find logic that could be erroneous.
+
+## Boolean Logic and Undefined
+
+Boolean logic is one way to safely recover from `undefined`. If boolean
+logic can safely determine a result despite an undefined value, that result
+will be returned.
+
+For example: `undefined or true` will result in `true`, because no matter
+what actual value `undefined` could've been if the policy behaved properly,
+the boolean expression would still be true.
+
+Another scenario where `undefined` can be safely ignored is short-circuit
+logic with `and`. `false and undefined` will result in `false`.
+
+The full table of logical operations on `undefined` is below:
+
+```sentinel
+undefined or true = true
+undefined or false = undefined
+undefined or undefined = undefined
+undefined and true = undefined
+undefined and false = undefined
+undefined and undefined = undefined
+undefined xor true = undefined
+undefined xor false = undefined
+undefined xor undefined = undefined
+
+// Short-circuit examples
+false or true or undefined = true
+false or undefined or true = true
+true and false and undefined = false
+true and undefined and false = undefined
+```
+
+## Else Expressions
+
+The `else` expression allows explicit recovery from undefined and is useful
+for setting default values.
+
+`else` is an operator where the left and right hand side are values. If the
+left-hand value is `undefined`, the right-hand value is returned. Otherwise,
+the left-hand value is returned.
+
+Examples:
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else "default"
+
+// In more complex scenarios
+
+foo() else 42 == 12
+a = config.value else "default"
+```
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/language/values.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/language/values.mdx
new file mode 100644
index 0000000000..d2969549b9
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/language/values.mdx
@@ -0,0 +1,195 @@
+---
+page_title: Sentinel Language - Values
+sidebar_title: Values
+sidebar_current: docs-language-values
+description: Values are the data that Sentinel policies operate on.
+layout: docs
+---
+
+# Language: Values
+
+Values are the _data_ that Sentinel policies operate on. You can create this
+data yourself, for example by just typing the number `42`, or you may access
+this data from an external source to make policy decisions.
+
+Values have a _type_, which determines what kind of operatins can be performed
+on the data. Example types are booleans (true and false), numbers,
+and strings (text).
+
+This page documents all the available value types. You can also
+[convert between types](#type-conversion).
+
+## Boolean
+
+A boolean is a value that is either true or false.
+
+A boolean value is created literally with `true` or `false`.
+
+As a policy language, booleans are central to the behavior of Sentinel.
+Booleans are used as conditions in [if statements](/sentinel/language/conditionals),
+are the result of [rules](/sentinel/language/rules), and more. The ultimate
+result of a Sentinel policy is true or false.
+
+## Integer
+
+An integer is a whole number.
+
+An integer is a 64-bit value. This means it can represent numbers from
+-9,223,372,036,854,775,808 to 9,223,372,036,854,775,808.
+
+Integers are created by typing them out literally with no
+separators (such as a comma). An optional prefix can set a non-decimal base:
+`0` for octal, and `0x` for hexadecimal. In hexadecimal literals, letters
+`a-f` and `A-F` represent values 10 to 15.
+
+Example integers are shown below:
+
+```sentinel
+42
+0600
+0xBadFace
+170141183460469
+```
+
+Integers are used for math and numerical comparison.
+
+## Float
+
+A float is a number with a decimal point. It has an integer part, a decimal
+point, a fractional part, and optionally an exponent part. Example
+floats are shown below:
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+Floats are IEEE-754 64-bit floating point numbers.
+
+## String
+
+Strings are text values.
+
+Strings are created by wrapping text in double quotes, such as `"hello"`.
+Within the quotes, any character may appear except newline and an unescaped
+double quote. The text between the quotes is the value.
+
+Because Sentinel policies must be UTF-8 encoded text, strings themselves are
+UTF-8 encoded text. This means you can put any value UTF-8 character into
+a string, such as `"日本語"`.
+
+You can have a string with a literal double-quote by _escaping_ it with
+a backslash. For example: `"they said \"hello\""` turns into the value
+`they said "hello"`.
+
+Backslash escapes can be used for much more than only escaping a double quote.
+Newlines are `\n`, a literal backslash is `\\`, and many more. The full list
+of available backslash escapes can be found
+[in the language specification](/sentinel/language/spec#string-literals).
+
+Example strings:
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+Strings support indexing. Indexing a string will access that _byte_
+in the string as if the string were a byte array. This is an important
+distinction: it _will not_ access the character at that position if you're
+using multi-byte characters.
+
+Strings support [slicing](/sentinel/language/slices) to efficiently create
+substrings.
+
+## List
+
+See [Lists](/sentinel/language/lists).
+
+## Map
+
+See [Maps](/sentinel/language/maps).
+
+## Rule
+
+See [Rules](/sentinel/language/rules).
+
+## Function
+
+See [Functions](/sentinel/language/functions).
+
+## Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a
+value of that type. Some examples are shown below followed by a list of exact
+rules for type conversion.
+
+```sentinel
+int(42) // 42
+int("42") // 42
+int(42.8) // 42
+int(true) // 1
+
+float(1.2) // 1.2
+float(1) // 1.0
+float("4.2") // 4.2
+float(true) // 1.0
+
+string("foo") // "foo"
+string(88) // "88"
+string(0xF) // "15"
+string(true) // "true"
+
+bool("true") // true
+bool(1) // true
+bool(-1) // true
+bool(0.1) // true
+bool("false") // false
+bool(0) // false
+```
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/language/variables.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/language/variables.mdx
new file mode 100644
index 0000000000..69655b0c63
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/language/variables.mdx
@@ -0,0 +1,100 @@
+---
+page_title: Sentinel Language - Variables
+sidebar_title: Variables
+sidebar_current: docs-language-vars
+description: Variables store values that can be used later.
+layout: docs
+---
+
+# Language: Variables
+
+Variables store values that can be used later.
+
+Most notably, a variable assignment of `main` is required for all
+Sentinel policies. This is the main [rule](/sentinel/language/rules) that
+is executed to determine the result of a policy. The `main` rule may
+use other variables that are assigned in the policy.
+
+Example:
+
+```sentinel
+a = 1
+b = a + 1
+```
+
+In this example, two variables are assigned: `a` and `b`. `a` is given
+the value of "1" and `b` uses the `a` to calculate its own value.
+
+## Syntax
+
+The syntax for assigning a variable is:
+
+```sentinel
+IDENTIFIER = VALUE
+```
+
+On the left of the equal sign is an _identifier_. This is a name for your
+variable and will be how you reference it later. An identifier is any
+combination of letters and digits, but must start with a letter. An
+underscore `_` is also a valid letter.
+
+On the right is any valid Sentinel value or expression. A value is a
+literal value such as a number or string. An expression is some computed
+value such as doing math, calling a function, etc.
+
+## Assignment and Reassignment
+
+A variable is _assigned_ when it is given a value. You can also _reassign_
+variables at any time by setting it to a new value. The new value takes
+effect for any subsequent use of that variable. A variable can be reassigned
+to a different type.
+
+For example:
+
+```sentinel
+a = 1 // a = 1
+b = a // b = 1
+a = "value" // a = "value", b = 1
+c = a // c = "value", b = 1
+```
+
+In the above example you can see that the variables are set and reassigned.
+Notice that the value of a variable is the _current_ value, and that reassigning
+a variable only affects _future_ uses of that variable. You can see this with
+`c` and `b` being different values.
+
+## Unassigned Variables
+
+Using a variable that is _unassigned_ is an error.
+
+In the example below, the first line would result in the policy erroring.
+Sentinel is executed top-down and the value of `c` is not available yet
+on the first line.
+
+```sentinel
+a = c // Error!
+c = 1
+```
+
+## Type Conversion
+
+While a topic more related to [values][lang-values], variables can be assigned
+(or-reassigned) a different value type via type conversion.
+
+[lang-values]: /sentinel/language/values
+
+```sentinel
+s = "1.1"
+a = int(s) // a = 1
+a = float(s) // a = 1.1
+
+a = 1
+s = string(a) // s = "1"
+```
+
+For more details, see the type conversion sections in the
+[values][lang-values-type-conversion] page, or the [Sentinel Language
+Specification][lang-spec-type-conversion].
+
+[lang-values-type-conversion]: /sentinel/language/values#type-conversion
+[lang-spec-type-conversion]: /sentinel/language/spec#type-conversion
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/nomad.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/nomad.mdx
new file mode 100644
index 0000000000..b519c1bf29
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/nomad.mdx
@@ -0,0 +1,50 @@
+---
+page_title: Nomad and Sentinel
+sidebar_title: Nomad
+sidebar_current: docs-app-nomad
+description: >-
+ Nomad Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies can currently execute on job
+ submission (creation, update).
+layout: docs
+---
+
+# Nomad
+
+[Nomad Enterprise](https://www.hashicorp.com/products/nomad/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies can currently execute on job submission (creation, update).
+
+Sentinel policies have full access to the job structure. This allows the
+Sentinel policy to control behavior based on any attribute within a job,
+such as the driver, resource requests, network configuration, volume
+configuration, and more. The information that Sentinel policies have access
+to will expand over time.
+
+Nomad fully supports all [enforcement levels](/sentinel/concepts/enforcement-levels).
+For soft mandatory policies, the `sentinel-override` capability must be
+available on the user's ACL policy to allow override. Overrides are always
+logged.
+
+The Nomad integration with Sentinel is documented in depth in the
+[Nomad Enterprise documentation](https://www.nomadproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Only allow Docker-based jobs.**
+
+```sentinel
+# Test policy only allows Docker based tasks
+main = rule { all_drivers_docker }
+
+# all_drivers_docker checks that all the drivers in use are Docker
+all_drivers_docker = rule {
+ all job.task_groups as tg {
+ all tg.tasks as task {
+ task.driver is "docker"
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/terraform.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/terraform.mdx
new file mode 100644
index 0000000000..4825ad24fe
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/terraform.mdx
@@ -0,0 +1,60 @@
+---
+page_title: Terraform and Sentinel
+sidebar_title: Terraform
+sidebar_current: docs-app-terraform
+description: >-
+ Sentinel can be used with Terraform and Terraform Enterprise to enforce policy
+ on Terraform configurations, states, and plans.
+layout: docs
+---
+
+# Terraform
+
+[Terraform Enterprise](https://www.hashicorp.com/products/terraform/) uses Sentinel to enforce
+policy on [Terraform](https://www.terraform.io) configurations, states, and plans.
+
+The Sentinel integration with Terraform runs within
+[Terraform Enterprise](https://www.hashicorp.com/products/terraform/)
+after a `terraform plan` and before a `terraform apply`. The policies
+have access to the created plan, the state at the time of the plan,
+and the configuration at the time of the plan.
+
+The Terraform integration with Sentinel is documented in depth in the [Terraform Enterprise documentation](https://www.terraform.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show basic examples.
+
+### Examples
+
+**Example: All AWS instances must have a tag**
+
+```sentinel
+import "tfplan"
+
+main = rule {
+ all tfplan.resources.aws_instance as _, instances {
+ all instances as _, r {
+ (length(r.applied.tags) else 0) > 0
+ }
+ }
+}
+```
+
+**Example: Only allow GCP instance sizes smaller than `n1-standard-16`**
+
+```sentinel
+import "tfplan"
+
+allowed_machine_types = [
+ "n1-standard-1",
+ "n1-standard-2",
+ "n1-standard-4",
+ "n1-standard-8",
+]
+
+main = rule {
+ all tfplan.resources.google_compute_instance as _, instances {
+ all instances as _, r {
+ r.applied.machine_type in allowed_machine_types
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/vault.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/vault.mdx
new file mode 100644
index 0000000000..9c37e0eedb
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/vault.mdx
@@ -0,0 +1,65 @@
+---
+page_title: Vault and Sentinel
+sidebar_title: Vault
+sidebar_current: docs-app-vault
+description: >-
+ Vault Enterprise uses Sentinel to augment the built-in policy system to
+ provide Role Governing Policies (RGPs) and Endpoint Governing Policies (EGPs)
+ to enable complex, flexible policies across identities and endpoints.
+layout: docs
+---
+
+# Vault
+
+[Vault Enterprise](https://www.hashicorp.com/products/vault/) uses Sentinel
+to augment the built-in policy system to provide Role Governing Policies (RGPs)
+and Endpoint Governing Policies (EGPs) to enable complex, flexible policies
+across identities and endpoints.
+
+**Role Governing Policies (RGPs)** are Sentinel policies that are tied to
+particular tokens, Identity entities, or Identity groups. They have access to
+a rich set of controls across various aspects of Vault. These are evaluated
+whenever a token they're attached to is used.
+
+**Endpoint Governing Policies (EGPs)** are Sentinel policies that are tied to
+particular paths instead of tokens. They have access to as much request
+information as possible, but they can take effect even on unauthenticated
+paths, such as login paths.
+
+The Vault integration with Sentinel is documented in depth in the
+[Vault Enterprise documentation](https://www.vaultproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Endpoint policy that requires MFA authentication from a corporate network.**
+
+```sentinel
+import "sockaddr"
+
+# We expect logins to come only from our private IP range
+cidrcheck = rule {
+ sockaddr.is_contained(request.connection.remote_addr, "10.20.0.0/16")
+}
+
+# Require Ping MFA validation to succeed
+ping_valid = rule {
+ mfa.methods.ping.valid
+}
+
+main = rule when request.path is "auth/ldap/login" {
+ ping_valid and cidrcheck
+}
+```
+
+**Example: Endpoint policy that disallows tokens created before a certain time.**
+
+```sentinel
+import "time"
+import "strings"
+
+main = rule when not strings.has_prefix(request.path, "auth/ldap/login") {
+ time.load(token.creation_time).unix > time.load("2017-09-17T13:25:29Z").unix
+}
+```
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/writing/basic.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/writing/basic.mdx
new file mode 100644
index 0000000000..1a881476a4
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/writing/basic.mdx
@@ -0,0 +1,107 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Basics
+sidebar_current: docs-writing-basic
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Policy Basics
+
+Sentinel policies are easy to write while still supporting advanced constructs
+for creating complex policies. This page will explain the basics of writing
+Sentinel policies to get started. You don't need any prior Sentinel knowledge,
+but we do recommend reading the
+[getting started guide](/sentinel/intro/getting-started/first-policy) and
+[language guide](/sentinel/language) after this.
+
+Sentinel policies are text files written using the [Sentinel language](/sentinel/language).
+The policies are evaluated top-to-bottom. The value of `main` after execution
+determines whether a policy passes or fails.
+
+## The Simplest Policy
+
+Sentinel only requires that a policy have a `main` variable that evaluates to
+a boolean value.
+
+A valid example is shown below:
+
+```sentinel
+main = 10 > 5
+```
+
+This type of minimal policy is not purely academic. In practice, simple
+policies can often be reduced to a single line logical statement resulting
+in `true` or `false`. However, the expression is usually wrapped in a
+[rule](/sentinel/language/rules) for testing reasons.
+
+You can verify Sentinel will execute this minimal policy using the CLI:
+
+```
+$ sentinel apply minimal.sentinel
+Pass
+```
+
+## Logical Expressions
+
+Policy is at its core a set of logic: you can or can not perform some action
+under a certain set of circumstances. Those circumstances are logical
+expressions. Therefore, Sentinel policies primarily translate into logical
+expressions.
+
+Detailed documentation on boolean expressions is
+[available in the language guide](/sentinel/language/boolexpr).
+
+A simple numerical comparison was seen in the first example on this page.
+Sentinel also provides inclusion operators such as `contains`, `any`, `all`, and
+more. Sentinel allows some operators to have aliases to promote readability
+while remaining programmer-familiar, such as `==` which can equivalently be `is`.
+
+The example below verifies that all numbers in a list are even:
+
+```sentinel
+numbers = [6, 22, 8, 4, 12]
+main = all numbers as n { n % 2 is 0 }
+```
+
+## Variables
+
+A policy will very often use variables. Applications such as
+[Nomad](https://www.nomadproject.io) inject variables into the global
+scope of a policy for making policy decisions. For example, Nomad injects
+the job that is being run into the policy scope. Knowing how to use
+variables is critical to effectively using Sentinel.
+
+Detailed documentation on how to define and access variables is
+[available in the language guide](/sentinel/language/variables).
+
+Variables can be defined and used explicitly. For example:
+
+```sentinel
+value = 10
+main = value > 5
+```
+
+But they may also be introduced implictly by the host system. Nomad
+injects `job` into policies to describe the job that is being run. The
+policy below is a valid policy that requires a job have two task groups.
+Notice that `job` is not defined anywhere. It is implicitly inserted by
+the host application (in this case Nomad). Refer to the application you're
+writing policy for to determine if it implicitly inserts values.
+
+```sentinel
+main = length(job.task_groups) is 2
+```
+
+## And more!
+
+The Sentinel language supports many more features such as functions,
+loops, and more. You can learn about all of this in the
+[complete language guide](/sentinel/language).
+
+The other pages in the [writing policy](/sentinel/writing)
+will cover other information you need to know about writing Sentinel
+policies that isn't simply a language reference.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/writing/debugging.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/writing/debugging.mdx
new file mode 100644
index 0000000000..7311de7be3
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/writing/debugging.mdx
@@ -0,0 +1,46 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Debugging
+sidebar_current: docs-writing-debugging
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Debugging
+
+As policies become more complex, it becomes harder to understand exactly why
+a policy may be behaving differently than you expect.
+
+Prior to actively debugging, there are multiple best practices we recommend
+following. We recommend breaking your policy down into a set of
+[rules](/sentinel/writing/rules). This will make it easier to understand
+[traces](/sentinel/writing/tracing) and make it easier to
+[test](/sentinel/writing/testing) your policies.
+This should help to debug policies up to a significant complexity.
+
+## Print Logging
+
+The built-in [print function](/sentinel/language/logging-errors#print)
+can be used to log data. The print output is only shown during failures
+or when tracing is explicitly enabled.
+
+The print function is [variadic](https://en.wikipedia.org/wiki/Variadic_function).
+Each argument specifies a value to print together, concatenated with a space
+in between each. You can put any type as an argument and Sentinel will
+convert that to a human-friendly string format.
+
+Example:
+
+```sentinel
+value = 42
+print("the value is", value) // the value is 42
+
+map = { "foo": false }
+print(map) // { "foo": false }
+```
+
+The `print` function returns the value `true` so that it can also be
+used within rule expressions. Note that if short-circuiting is occuring
+within the boolean logic, the `print` function may never be reached.
+The runtime contains a special case where `print` will not short-circuit
+its own logic, so `print() or expr` will always evaluate `expr`.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/writing/imports.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/writing/imports.mdx
new file mode 100644
index 0000000000..1bb8763c32
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/writing/imports.mdx
@@ -0,0 +1,124 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Imports
+sidebar_current: docs-writing-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom import](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only local
+context for making policy decisions.
+
+Sentinel also comes with a set of [standard imports](/sentinel/imports).
+Standard imports are available to every Sentinel policy to help policy writers
+with common tasks such as working with the time, network addresses, and more.
+
+-> **This page is about writing policies that _use_ imports.** If you're
+interested in _creating_ a new import, please see the section on [extending
+Sentinel](/sentinel/extending) for information on how to write modules and
+import plugins.
+
+## Using Imports
+
+To use an import, you use the `import` keyword at the top of your policy. This
+specifies the name of the import you want to use. The application you're writing
+the policy for must already be
+[configured](/sentinel/extending/plugins#installing-plugins) to provide that
+import.
+
+Details on imports can be found in the
+[import section in the language reference](/sentinel/language/imports).
+
+In the example below, we use the [time](/sentinel/imports/time) import:
+
+```sentinel
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+There are two options to develop a policy locally when using imports:
+configure Sentinel to launch the import on `apply`, or mock the import
+using `mock`. The former requires access to the import plugin while the
+latter is faster (doesn't have to launch a process) and doesn't require
+the plugin.
+
+## Mocking Imports
+
+The first option to developing policies locally is to mock the import values.
+When mocking an import, you don't need the import plugin available. This can be
+useful since some imports may not be available as a plugin and may only be
+available to the application the policy runs in.
+
+Mocks are specified via the [configuration
+file](/sentinel/configuration). Mocks can also be used for
+[testing](/sentinel/writing/testing).
+
+You can supply mock configuration one of two ways, depending on your use case:
+
+- **[Using static data](/sentinel/configuration#mocking-static-data):**
+ Use this method when you can accurately represent your mock data in JSON and
+ do not need to mock complex Sentinel features such as functions.
+- **[Using Sentinel code](/sentinel/configuration#mocking-with-sentinel-code):**
+ Use this method when using a static JSON object is insufficient, such as when
+ you need to mock functions or other complex Sentinel features.
+
+Our example does not require complex data to be mocked, so a static JSON object
+is sufficient:
+
+```json
+{
+ "mock": {
+ "time": {
+ "now": {
+ "hour": 12,
+ "weekday_name": "Tuesday"
+ }
+ }
+ }
+}
+```
+
+This can be used via the CLI:
+
+```
+$ sentinel apply -config=config.json policy.sentinel
+Pass
+```
+
+## Launching Imports
+
+If you have access to the plugin binary, you can launch the import. The
+benefit of this is that it is really using the import to test your
+policy. If the import changes, your policies may start failing. If you
+only use mock data and the import changes, your policies will still
+appear to work.
+
+Imports are configured in the configuration file:
+
+```json
+{
+ "imports": {
+ "time": {
+ "path": "/path/to/sentinel-time-import"
+ }
+ }
+}
+```
+
+This would require the `sentinel-time-import` binary. For this example
+this doesn't currently exist. We plan on writing one to provide for this
+section of the documentation.
+
+## Custom Imports
+
+You can also [create your own imports](/sentinel/extending).
+
+If your policy decisions could benefit from accessing external information,
+then you can use custom imports as a way to do this.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/writing/index.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/writing/index.mdx
new file mode 100644
index 0000000000..e7743c0011
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/writing/index.mdx
@@ -0,0 +1,36 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Writing Policy
+sidebar_current: docs-writing
+description: >-
+ Sentinel provides a language and workflow for building policy across any
+ system that embeds Sentinel. By learning Sentinel once, you are able to
+ effectively control access to many systems using Sentinel's powerful features.
+ Basic concepts that are important to understand for Vault usage.
+layout: docs
+---
+
+# Writing Sentinel Policy
+
+This section covers how to write Sentinel policies.
+
+It is recommended that you complete the
+[getting started guide](/sentinel/intro/getting-started/first-policy) prior to
+reading this section of the documentation. The getting started guide will
+explain all the basics of writing and testing policies. This section of the
+documentation will serve as more of a reference guide to all available
+features of Sentinel.
+
+Sentinel provides a language and workflow for building policy across any system
+that embeds Sentinel. By learning Sentinel once, you are able to effectively
+control access to many systems using Sentinel's powerful features.
+
+Sentinel also provides a [local CLI](/sentinel/commands) for developing and testing
+Sentinel policies. This CLI can be integrated into a daily developer's workflow
+along with CI to treat [policy as code](/sentinel/concepts/policy-as-code).
+
+Sentinel uses its own [language](/sentinel/language) for writing
+policies. You can view a [language reference](/sentinel/language)
+as well as the [specification](/sentinel/language/spec) for details. You
+don't have to read those documents immediately, since the language should
+be easy enough to pick up throughout this section.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/writing/rules.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/writing/rules.mdx
new file mode 100644
index 0000000000..f57a814e55
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/writing/rules.mdx
@@ -0,0 +1,61 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Rules
+sidebar_current: docs-writing-rules
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Rules
+
+Rules form the basis of a policy by representing behavior that is either passing
+or failing. Rules are a first class language construct in Sentinel. A policy can
+and should be broken down into rules to aid with readability, testability, and
+performance.
+
+Rules provide:
+
+- **Readability:** A policy that is broken down into rules can be read
+ more easily. The logic becomes clearer to see for policy writers when
+ they come back to it.
+
+- **Reportability:** When [tracing](/sentinel/writing/tracing) is enabled,
+ rules form the foundation of the data returned. All evaluated rules are added
+ to the trace with the data the rule evaluated to, and their position within
+ policy or module source files.
+
+- **Testability:** [Policy testing](/sentinel/writing/testing) is
+ built on asserting the values of rules. By breaking logic down into
+ rules, you're able to more effectively test your policies.
+
+- **Performance:** Rules are only evaluated on demand, and are also only
+ evaluated once. This means that a rule referenced multiple time only has a
+ one-time performance cost. For complex logic, this could result in improved
+ performance.
+
+An example usage of rules is shown below:
+
+```sentinel
+is_sunny = rule { weather is "sunny" }
+is_wednesday = rule { day is "wednesday" }
+main = rule { is_sunny and is_wednesday }
+```
+
+Rules can also contain non-boolean data. This can be useful for passing more
+detail to the caller than an opaque boolean value would be able to communicate.
+
+```sentinel
+main = rule {
+ filter days as d {
+ d.weather is not "sunny"
+ }
+}
+```
+
+Details about rules and their behavior can be found in
+[the language reference](/sentinel/language/rules). Rules have important
+behavior so we recommend reading the language reference on rules.
+Rules will be used throughout the remainder of the documentation.
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/writing/testing.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/writing/testing.mdx
new file mode 100644
index 0000000000..0c3788ca1c
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/writing/testing.mdx
@@ -0,0 +1,492 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Testing
+sidebar_current: docs-writing-testing
+description: >-
+ Sentinel has a built-in test framework to validate a policy behaves as
+ expected.
+layout: docs
+---
+
+# Testing
+
+Sentinel has a built-in test framework to validate a policy behaves as expected.
+
+With an ever-increasing amount of automation surrounding technology,
+the guardrails provided by policies are a critical piece towards ensuring
+expected behavior. As a reliance on correct policy increases, it is important
+to test and verify policies.
+
+Testing is a necessary step to fully realize
+[policy as code](/sentinel/concepts/policy-as-code). Just as good software
+is well tested, a good set of policies should be equally well tested.
+
+## Test Folder Structure
+
+Policies are tested by asserting that [rules](/sentinel/writing/rules)
+are the expected values given a pre-configured input. Tests are run
+by executing the [test command](/sentinel/commands/test).
+
+Sentinel is opinionated about the folder structure required for tests.
+This opinionated structure allows testing to be as simple as running
+`sentinel test` with no arguments. Additionally, it becomes simple to test
+in a CI or add new policies.
+
+The structure Sentinel expects is `test//*.[hcl|json]` where ``
+is the name of your policy file without the file extension. Within that folder
+is a list of HCL or JSON files. Each file represents a single test case.
+Therefore, each policy can have multiple tests associated with it.
+
+-> Note that the primary configuration file format for Sentinel is HCL. While
+you can write configuration files in a HCL-equivalent JSON, we only discuss the
+use of HCL on this page.
+
+## Test Case Format
+
+Each HCL file within the test folder for a policy is a single test case.
+
+The file is the same configuration format as the [CLI configuration
+file](/sentinel/configuration). The format lets you define mock data, imports to
+use, and more. This mock data is the key piece in being able to test policies:
+you craft a specific scenario and assert your policy behaves as you expect.
+
+Test cases also use the `test` block within the configuration file to assert the
+value of [rules](/sentinel/writing/rules). If the `test` key is omitted, the
+policy is expected to pass. If the test key is specified, only the rules
+specified in the map will be asserted. This means if you omit `main`, then the
+final policy result is not asserted.
+
+Example with assertions:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+
+test {
+ rules = {
+ main = false
+ is_open_hours = false
+ is_weekday = true
+ }
+}
+```
+
+The configuration above specifies some parameter data, and asserts the result of
+some rules. This is the same configuration used in the example section below.
+
+## Example
+
+Lets use the following file as an example. Save this file to a directory
+and name it `policy.sentinel`. It can be named anything with the `sentinel`
+extension, but by naming it `policy.sentinel` your output should match
+the example output on this page.
+
+```sentinel
+// The day of the week.
+param day
+
+// The hour of the day.
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+### A Passing Test
+
+Next, let's define a single test case. Relative to where you saved
+the policy, create a file at the path `test/policy/good.hcl`.
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 14
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/good.hcl
+```
+
+This passed because the policy passed. We didn't assert any specific
+rules. By not specifying any assertions, `test` expects the policy itself
+to fully pass.
+
+### A Failing Test
+
+Define another test case to fail. We want to verify our policy fails when expected, too.
+
+Save the following as `test/policy/7-am.hcl`:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+FAIL - policy.sentinel
+ FAIL - test/policy/7-am.hcl
+ expected "main" to be true, got: false
+
+ trace:
+ policy.sentinel:9:1 - Rule "main"
+ bool: false
+
+ policy.sentinel:8:1 - Rule "is_open_hours"
+ bool: false
+
+ PASS - test/policy/good.hcl
+```
+
+As you can see, the test fails because "main" is false. This is good
+because the policy _should_ have failed since we specified an invalid
+hour. But, we expect main to be false and don't want our test to fail!
+Update `7-am.hcl` to add test assertions:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+
+test {
+ rules = {
+ main = false
+ is_open_hours = false
+ }
+}
+```
+
+And when we run the tests:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/7-am.hcl
+ PASS - test/policy/good.hcl
+```
+
+The test passes. We asserted that we expect the `main` rule to be false,
+the `is_open_hours` rule to be false, and the `is_weekday` rule to be
+true. By asserting some rules are true, we can verify that our policy
+is failing for reasons we expect.
+
+## Mocking
+
+The above example demonstrates how to test by supplying different
+[parameters](/sentinel/language/parameters). Parameters in a policy can be
+specifically useful when you want to control user-defined input values to a
+policy.
+
+However, generally, when testing, you will need mimic the conditions you will
+see in production. Production implementations of Sentinel will supply data using
+one of two methods:
+
+- **Global data:** Data is injected directly into the policy's scope and is
+ accessible using normal identifiers, similar to variables.
+- **Imports:** Data is stored behind an [import](/sentinel/language/imports)
+ and loaded on demand as needed by the policy author.
+
+Proper testing of a policy requires that these values be able to be _mocked_ -
+or, in other words, simulated in a way that allows the accurate testing of the
+scenarios that a policy could reasonably pass or fail under.
+
+Mocking both globals and imports can be done by setting various parts of the
+configuration file.
+
+### Mocking Globals
+
+Demonstrating the mocking of globals can be seen by making a few modifications to
+our example policy, removing the `param` declarations:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Then, change the `param` section in the configuration file to
+[`global`](/sentinel/configuration#global).
+
+```json
+global "day" {
+ value = "monday"
+}
+
+global "hour" {
+ value = 14
+}
+```
+
+This test should still pass, as if nothing had happened, although what we've
+done is shifted our parameters to globals, simulating an environment where `day`
+and `hour` are already defined for us.
+
+### Mocking Imports
+
+To mock imports, we need to use the
+[`mock`](/sentinel/configuration#mock-imports) section of the
+configuration file.
+
+Let's say the above example is behind an import named `time`.
+
+-> **NOTE:** [`time`](/sentinel/imports/time) is a valid standard import.
+This example may not be accurate to the
+import's syntax.
+
+The code now looks like this:
+
+```sentinel
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To mock this import, we can [mock it as static
+data](/sentinel/configuration#mocking-static-data). The configuration
+file now looks like, without assertions:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ weekday_name = "Monday"
+ hour = 14
+ }
+ }
+}
+```
+
+The policy will now pass, with the `time` import mocked.
+
+Data can also be [mocked as Sentinel
+code](/sentinel/configuration#mocking-with-sentinel-code). In this case,
+the above configuration file would look like:
+
+```hcl
+mock "time" {
+ module = {
+ source = "mock-time.sentinel"
+ }
+}
+```
+
+And a file named `mock-time.sentinel` would now hold your mock values:
+
+```sentinel
+day = "monday"
+hour = 14
+```
+
+Mocking as Sentinel code allows more complex details to be mocked as well, such
+as functions. Say we wanted to mock the `time.load()` function. To mock this,
+just add it to the `mock-time.sentinel` file:
+
+```sentinel
+load = func(_) {
+ return {
+ "weekday_name": "Monday",
+ "hour": 14,
+ }
+}
+```
+
+Your code can now be written as:
+
+```sentinel
+import "time"
+
+t = time.load("a_mock_timestamp")
+
+is_weekday = rule { t.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { t.hour > 8 and t.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To see more details, see the [Mock
+Imports](/sentinel/configuration#mock-imports) section in the
+configuration file.
+
+## Asserting Non-Boolean Rules
+
+Non-boolean rules can also be asserted by `sentinel test`.
+
+To assert non-boolean values, simply enter the expected value into the rule
+contents:
+
+```hcl
+param "maintenance_days" {
+ value = [
+ {
+ day = "wednesday"
+ hour = 9
+ },
+ {
+ day = "friday"
+ hour = 1
+ },
+ {
+ day = "sunday"
+ hour = 1
+ },
+ ]
+}
+
+test {
+ rules = {
+ main = [
+ {
+ day = "wednesday"
+ hour = 9
+ },
+ ]
+ }
+}
+```
+
+This would assert a policy checking for violations of a maintenance policy,
+saying that maintenance hours should happen before 6AM on any given day:
+
+```sentinel
+param maintenance_days
+
+main = rule {
+ filter maintenance_days as d {
+ d.hour >= 6
+ }
+}
+```
+
+## Test JSON Output
+
+Running `sentinel test` with the `-json` flag will give you the test results in
+a JSON output format, suitable for parsing by reporting software.
+
+-> **Note:** The JSON output format is currently under development and the
+format may change at a later time. Additionally, support for other well-known
+formats, such as JUnit, may become available in the future.
+
+The current format is an object with the only top-level key being `policies`,
+with each test grouped up by policy being run.
+
+The policy result fields are:
+
+- `path`: The path of the policy and the index of the test result in the
+ `policies` field in the root object.
+- `status`: A string representation of the policy's test status as a whole. Can
+ be one of `PASS`, `FAIL`, `ERROR`, or `?`. The final status, `?`, represents a
+ policy that has no tests to process, and acts like a passing test.
+- `errors`: An array of any error messages encountered during processing the
+ policy for testing. Usually reserved for policy file or parser-related errors.
+ For case-specific errors, see the error field for the particular case.
+- `cases`: A map of case results, indexed by case path.
+
+The case result fields are:
+
+- `path`: The path of the test case and the result's index in the `cases` field
+ in the policy object.
+- `status`: A string representation of the case's test status. Can be one of
+ `PASS`, `FAIL` or `ERROR`.
+- `errors`: An array of any error messages encountered during running this test
+ case.
+- `trace`: The trace for this policy in JSON format. See the
+ [tracing](/sentinel/writing/tracing) page for more details.
+- `rule_detail`: When the `status` of this test case is `FAIL`, contains an
+ object of assertion failure detail, indexed by rule. Only failures are counted
+ here; any rules not found here can be assumed to have passed or not asserted.
+- `config_warnings`: An array of strings denoting any configuration warnings
+ found while processing the configuration for this test case.
+- `config_legacy`: Denotes whether or not the configuration is a legacy JSON
+ configuration and needs to be modernized. This field may be removed in future
+ releases.
+
+A passing example is shown below:
+
+```json
+{
+ "policies": {
+ "policy.sentinel": {
+ "path": "policy.sentinel",
+ "status": "PASS",
+ "errors": null,
+ "cases": {
+ "test/policy/pass.hcl": {
+ "path": "test/policy/pass.hcl",
+ "status": "PASS",
+ "errors": null,
+ "trace": {
+ "description": "A very basic policy to determine if a run is within working hours.",
+ "error": null,
+ "print": "",
+ "result": true,
+ "rules": {
+ "is_open_hours": {
+ "desc": "Passes if run during business hours.",
+ "ident": "is_open_hours",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 290,
+ "line": 13,
+ "column": 1
+ },
+ "value": true
+ },
+ "is_weekday": {
+ "desc": "Passes if the day does not fall on the weekend.",
+ "ident": "is_weekday",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 193,
+ "line": 10,
+ "column": 1
+ },
+ "value": true
+ },
+ "main": {
+ "desc": "",
+ "ident": "main",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 338,
+ "line": 14,
+ "column": 1
+ },
+ "value": true
+ }
+ }
+ },
+ "rule_detail": {},
+ "config_warnings": null,
+ "config_legacy": false
+ }
+ }
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.17.x/content/sentinel/docs/writing/tracing.mdx b/content/sentinel/v0.17.x/content/sentinel/docs/writing/tracing.mdx
new file mode 100644
index 0000000000..96a2eff37a
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/docs/writing/tracing.mdx
@@ -0,0 +1,397 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_title: Traces
+sidebar_current: docs-writing-tracing
+description: >-
+ Sentinel provides execution traces to better understand the execution of a
+ policy.
+layout: docs
+---
+
+# Traces
+
+Sentinel provides execution traces to better understand the execution of a policy.
+
+When using the Sentinel [CLI](/sentinel/commands), traces are shown
+on policy failure during `apply` or `test`, or when the `-trace` flag is
+explicitly specified. For Sentinel-enabled applications, traces are optional
+and may require special configuration to enable.
+
+The availability of the trace may vary from application to application. While
+some applications may only log traces on failure, others, such as [Terraform
+Cloud](https://www.terraform.io/cloud), log the trace on every execution. For
+more details, consult your implementation's documentation.
+
+-> **Note:** We're actively improving Sentinel tracing in each release
+to provide better data and improve readability. The exact format of a
+trace may not match the Sentinel version embedded in the application you're
+using, but the data should be similar. The canonical format for a trace should
+get more stable as we approach a 1.0 release.
+
+## Analyzing a Trace
+
+To show traces, let's use the example policy below with the CLI:
+
+```sentinel
+param day
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+
+main = rule { is_open_hours and is_weekday }
+```
+
+Let's cause the policy to fail so the trace is more interesting.
+If you're on a terminal that supports it, the trace output will be colored
+so you can more clearly see passes, failures, and rules.
+
+
+ $ sentinel apply main.sentinel{'\n'}
+
+ main.sentinel:1:7: requires value for parameter day
+
+ {'\n'}
+ {'\n'}
+ {'\n'}
+ {' '}Values can be strings, floats, or JSON array or object values. To force{
+ '\n'
+ }
+ {' '}strings, use quotes.{'\n'}
+ {'\n'}
+ {' '}Enter a value: sunday{'\n'}
+ {'\n'}
+
+ main.sentinel:2:7: requires value for parameter hour
+
+ {'\n'}
+ {'\n'}
+ {'\n'}
+ {' '}Values can be strings, floats, or JSON array or object values. To force
+ {'\n'}
+ {' '}strings, use quotes.{'\n'}
+ {'\n'}
+ {' '}Enter a value: 14{'\n'}
+ {'\n'}
+ Execution trace. The information below
+ will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+
+ Fail - main.sentinel
+
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:7:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}
+ false
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:5:1 - Rule "is_open_hours"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}true{'\n'}
+ {'\n'}
+
+ main.sentinel:4:1 - Rule "is_weekday"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}false{'\n'}
+
+
+We can see all of our rules neatly and clearly displayed along with the result
+of the policy. If you are getting the trace due to a failed policy and not
+because you explicitly used `-trace`, an informational message is displayed.
+
+As we can see from our basic example, the `main` rule has failed and its result
+is highlighted in red. All peripheral rules that were also evaluated as part of
+the policy are also displayed below the main rule. Source positions are also
+displayed so that you can find the references to the rules in your code.
+
+Via the trace, we can clearly see that while `is_open_hours` returned a true
+result, `is_weekday` did not, and per the logic in our code, the policy is
+rejected.
+
+## Non-Boolean Rules and the Trace
+
+The trace is particularly important when working with rules that return
+non-boolean data, such as rules where you want to see violations instead of a
+simple opaque boolean value. Let's take our example from the [rules](./rules)
+section, and write a small static policy for it:
+
+```sentinel
+days = [
+ {"weekday": "Sunday", "weather": "clouds"},
+ {"weekday": "Monday", "weather": "rain"},
+ {"weekday": "Tuesday", "weather": "sunny"},
+ {"weekday": "Wednesday", "weather": "sunny"},
+ {"weekday": "Thursday", "weather": "clouds"},
+ {"weekday": "Friday", "weather": "rain"},
+ {"weekday": "Saturday", "weather": "sunny"},
+]
+
+main = rule {
+ filter days as d {
+ d.weather is not "sunny"
+ }
+}
+```
+
+Here is the output of our policy:
+
+
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+ {'\n'}
+
+ Fail - weather.sentinel
+
+ {'\n'}
+ {'\n'}
+
+ weather.sentinel:11:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+
+ {' [\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Sunday"'}
+ {'\n'}
+ {' "weather": "clouds"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Monday"'}
+ {'\n'}
+ {' "weather": "rain"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Thursday"'}
+ {'\n'}
+ {' "weather": "clouds"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Friday"'}
+ {'\n'}
+ {' "weather": "rain"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' ]'}
+
+
+
+We can now see all of the weekdays with non-sunny weather.
+
+Note that prevent formatting issues and excessive output, the human-readable
+trace is limited in the volume of output it will display for collections.
+
+## JSON Output
+
+The trace can also be displayed in JSON by adding `-json` to `sentinel apply`
+and `sentinel test`, and will display the trace in its entirety, unlike the
+standard CLI output.
+
+Here is the result of running `sentinel apply -json` against our weather policy:
+
+```json
+{
+ "can_override": false,
+ "error": null,
+ "policies": [
+ {
+ "allowed_failure": false,
+ "error": null,
+ "policy": "weather.sentinel",
+ "result": false,
+ "trace": {
+ "description": "",
+ "error": null,
+ "print": "",
+ "result": false,
+ "rules": {
+ "main": {
+ "desc": "",
+ "ident": "main",
+ "position": {
+ "filename": "weather.sentinel",
+ "offset": 328,
+ "line": 11,
+ "column": 1
+ },
+ "value": [
+ {
+ "weather": "clouds",
+ "weekday": "Sunday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Monday"
+ },
+ {
+ "weather": "clouds",
+ "weekday": "Thursday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Friday"
+ }
+ ]
+ }
+ }
+ }
+ }
+ ],
+ "result": false
+}
+```
+
+Note that the trace is always included with this output, regardless of whether
+or not the policy passes or fails; using `-trace` is unnecessary.
+
+Additionally, with `sentinel apply`, you can display the value of a single rule
+in JSON, instead of the entire trace. This is done with the `-json-rule` flag.
+
+-> **Note:** `sentinel apply -json-rule` needs to be run either against a single
+on-disk policy, or against a single policy within a policy set. It is ignored
+in full policy set executions and functions exactly like `-json` in these
+scenarios.
+
+Here is the result of executing `sentinel apply -json-rule main weather.sentinel`:
+
+```json
+[
+ {
+ "weather": "clouds",
+ "weekday": "Sunday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Monday"
+ },
+ {
+ "weather": "clouds",
+ "weekday": "Thursday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Friday"
+ }
+]
+```
+
+## Other Trace Details
+
+There are some other details that are good to know when working with the trace:
+
+- Only rules that get executed are added to the trace. Considering our first
+ example where the `main` rule is the expression `is_open_hours and is_weekday`,
+ if our expression was using an `or` operator instead of `and`, in addition to
+ the policy passing, `is_open_hours` would be present in the trace, but
+ `is_weekday` would not be, as the policy would have never evaluated that rule.
+- Descriptions for both rules and policies will show up in the trace as well if
+ present. Here is the output to our first policy with the appropriate
+ annotations:
+
+
+ ...{'\n\n'}
+ Execution trace. The information below
+ will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+
+ Fail - main.sentinel
+
+ {'\n'}
+ {'\n'}
+ Description:{'\n'}
+ {' A very basic policy to determine if a run is within working\n'}
+ {' hours.\n'}
+ {'\n'}
+
+ main.sentinel:7:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}
+ false
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:5:1 - Rule "is_open_hours"
+
+ {'\n'}
+ {' '}
+ Description:
+ {'\n'}
+ {' Passes if run during business hours.\n'}
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}true{'\n'}
+ {'\n'}
+
+ main.sentinel:4:1 - Rule "is_weekday"
+
+ {'\n'}
+ {' '}
+ Description:
+ {'\n'}
+ {' Passes if the day does not fall on the weekend.\n'}
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}false{'\n'}
+
diff --git a/content/sentinel/v0.17.x/content/sentinel/intro/getting-started/first-policy.mdx b/content/sentinel/v0.17.x/content/sentinel/intro/getting-started/first-policy.mdx
new file mode 100644
index 0000000000..42b25b4e28
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/intro/getting-started/first-policy.mdx
@@ -0,0 +1,60 @@
+---
+page_title: Your First Sentinel Policy
+sidebar_title: Your First Policy
+sidebar_current: intro-getting-started-first
+description: Let's begin by writing a working Sentinel policy.
+layout: intro
+---
+
+# Your First Sentinel Policy
+
+Sentinel is a system to enforce complex policies on an integrated application.
+
+Writing Sentinel policy requires minimal programming experience. The Sentinel
+language is designed to be approachable and learned quickly and easily. Whether
+you're a professional programmer or someone who uses SQL and Excel, you can
+learn to write Sentinel policies.
+
+Let's begin by writing a simple, working Sentinel policy:
+
+```sentinel
+hour = 4
+main = rule { hour >= 0 and hour < 12 }
+```
+
+This is a valid Sentinel policy. It will pass since we hardcoded the
+`hour` to be 4. In a real system, `hour` may be something that is provided
+to us and actually set to the current hour. We'll learn more about that later.
+
+For now, try running this policy locally. Save the above example to a file
+named `policy.sentinel` and execute it. Then, modify the policy to make it
+fail. Play around more if you'd like.
+
+```
+$ sentinel apply policy.sentinel
+Pass
+```
+
+## Main
+
+Every Sentinel policy must have a `main` rule. This is the rule that
+is evaluated to determine the result of a policy.
+
+A rule describes an expression that generally means one of two things:
+
+- Does a policy _pass a condition_ that would authorize an operation? In our
+ above example, describe a policy that checks the supplied hour (4) is within an
+ authorized time window (between 0 - midnight, and 12 noon).
+- Conversely, can a policy find any _violations_ that would block authorization
+ of the operation? Building on the above, consider a policy that takes a
+ schedule, and finds all time blocks that fall outside of the example time window
+ supplied in the above policy.
+
+It is easy to imagine that such a rule might be used in a system such
+as [Nomad](https://www.nomadproject.io) to restrict the times when a
+deploy can occur. The power of arbitrary logical statements within Sentinel
+allows Sentinel policies to restrict almost any behavior.
+
+Next, we'll
+[introduce and explain rules](/sentinel/intro/getting-started/rules)
+so we can use them in our policies.
diff --git a/content/sentinel/v0.17.x/content/sentinel/intro/getting-started/imports.mdx b/content/sentinel/v0.17.x/content/sentinel/intro/getting-started/imports.mdx
new file mode 100644
index 0000000000..ba26b7be62
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/intro/getting-started/imports.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Imports
+sidebar_title: Imports
+sidebar_current: intro-getting-started-imports
+description: Imports enable Sentinel to access external data and functions.
+layout: intro
+---
+
+# Imports
+
+Imports enable Sentinel to access external data and functions.
+
+Sentinel ships with a set of [standard imports](/sentinel/imports).
+Standard imports are available by default to every Sentinel policy. Note that
+the application embedding Sentinel may allow or deny the available
+set of imports.
+
+To use an import, use the `import` statement. Then, access data and
+functions on the import using the import name followed by a period and
+the field you want to access. The example below checks whether the request
+path starts with a prefix. Assume that `request_path` is available.
+
+```sentinel
+import "strings"
+
+main = rule { strings.has_prefix(request_path, "/admin") }
+```
+
+## External Data
+
+The true power in imports is their ability to reference external data.
+Imports can be added to a Sentinel-enabled application through
+[plugins](/sentinel/extending). This enables any Sentinel-enabled
+application to make policy decision based on any source of data.
+
+This ability allows the policy system to enforce almost any necessary
+organizational policy, since the ability of a policy isn't restricted
+purely to the embedding application's data model.
+
+For example, policies in [Nomad](https://www.nomadproject.io) can
+access data in [Consul](https://www.consul.io) to determine attributes
+of a policy. In the example below, we use a hypothetical Consul import:
+
+```sentinel
+import "consul"
+
+main = rule {
+ job.tasks[0].resources.memory <= int(consul.get("policy/nomad/max-memory"))
+}
+```
+
+In this example, a Nomad job's memory usage is limited to the value of
+a Consul key/value item. By simply changing a Consul KV entry, policy
+can be changed. Imports can do anything, you can [write your own import plugins](/sentinel/extending)
+to extend Sentinel.
+
+Next, we'll learn how to
+[test policies](/sentinel/intro/getting-started/testing)
+to ensure our policy logic is correct.
diff --git a/content/sentinel/v0.17.x/content/sentinel/intro/getting-started/index.mdx b/content/sentinel/v0.17.x/content/sentinel/intro/getting-started/index.mdx
new file mode 100644
index 0000000000..0942cdb637
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/intro/getting-started/index.mdx
@@ -0,0 +1,17 @@
+---
+page_title: Install Sentinel CLI - Getting Started
+sidebar_title: Getting Started
+sidebar_current: intro-getting-started-overview
+description: The first step to using Sentinel is to get the CLI installed.
+layout: intro
+---
+
+# Getting Started With Sentinel
+
+This section covers getting started with Sentinel. Here, we will take you
+through the first steps that you will need to do to get started with installing
+the Sentinel CLI, writing your first policy, and getting familiar with rules,
+boolean logic, imports, and testing.
+
+You can select a topic on the menu to get started. The first step is to [install
+the Sentinel CLI](/sentinel/intro/getting-started/install).
diff --git a/content/sentinel/v0.17.x/content/sentinel/intro/getting-started/install.mdx b/content/sentinel/v0.17.x/content/sentinel/intro/getting-started/install.mdx
new file mode 100644
index 0000000000..4c3959d617
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/intro/getting-started/install.mdx
@@ -0,0 +1,57 @@
+---
+page_title: Install Sentinel CLI - Getting Started
+sidebar_title: Installing the CLI
+sidebar_current: intro-getting-started-install
+description: The first step to using Sentinel is to get the CLI installed.
+layout: intro
+---
+
+# Install Sentinel CLI
+
+Sentinel is a policy framework that is embedded in the enterprise versions of
+HashiCorp tools. The policies you write are deployed to these applications and
+enforced there.
+
+The Sentinel CLI is a command-line interface for local development and testing.
+For the getting started guide, we'll use the CLI to learn how to write policies
+for Sentinel-enabled applications. The Sentinel CLI is distributed as a [binary
+package](/sentinel/downloads) for all supported platforms and architectures.
+
+## Installation Instructions
+
+To install the Sentinel CLI, find the [appropriate package](/sentinel/downloads) for your
+system and download it. The CLI is packaged as a zip archive.
+
+After downloading Sentinel, unzip the package. The CLI runs as a single binary
+named `sentinel`. Any other files in the package can be safely removed and
+Sentinel will still function.
+
+The final step is to make sure that the `sentinel` binary is available on the `PATH`.
+See [this page](https://stackoverflow.com/questions/14637979/how-to-permanently-set-path-on-linux)
+for instructions on setting the PATH on Linux and Mac.
+[This page](https://stackoverflow.com/questions/1618280/where-can-i-set-path-to-make-exe-on-windows)
+contains instructions for setting the PATH on Windows.
+
+## Verifying the Installation
+
+After installing the Sentinel CLI, verify the installation worked by opening a
+new terminal session and checking that the `sentinel` binary is available. By
+executing `sentinel`, you should see help output similar to the following:
+
+```
+$ sentinel
+Usage: sentinel [--version] [--help] []
+
+Available commands are:
+ apply Execute a policy and output the result
+ fmt Format Sentinel policy to a canonical format
+ test Test policies
+ version Prints the Sentinel version
+```
+
+If you get an error that the binary could not be found, then your `PATH`
+environment variable was not setup properly. Please go back and ensure that your
+`PATH` variable contains the directory where Sentinel was installed.
+
+Otherwise, the CLI is installed and ready to go. Let's celebrate by [writing our
+first policy!](/sentinel/intro/getting-started/first-policy)
diff --git a/content/sentinel/v0.17.x/content/sentinel/intro/getting-started/logic.mdx b/content/sentinel/v0.17.x/content/sentinel/intro/getting-started/logic.mdx
new file mode 100644
index 0000000000..893d63c851
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/intro/getting-started/logic.mdx
@@ -0,0 +1,55 @@
+---
+page_title: Logic
+sidebar_title: Logic
+sidebar_current: intro-getting-started-logic
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Logic
+
+The core of a policy is a set of logical expressions to determine whether
+a behavior is allowed or not.
+Sentinel makes it easy to write readable logical expressions to express
+the intended policy.
+
+The logical expression syntax will be familiar to anyone who has programmed
+before. Sentinel also supports a couple more unique constructs to assist
+with common policy requirements. Detailed documentation on how to write
+logical expressions and the supported operators is available in
+[the boolean expression reference](/sentinel/language/boolexpr).
+
+Example logic:
+
+```sentinel
+a is b // Equality, you can also use ==
+a is not b // Inequality, you can also use !=
+
+a > b // Relational operators, comparison
+a < b
+a >= b
+a <= b
+
+a contains b // Inclusion check, substrings, etc.
+a not contains b // Negated version of above
+```
+
+The full list of available operators can be found in
+[the boolean expression reference](/sentinel/language/boolexpr).
+
+Logic can be combined with `and` or `or`. When combined with `and`, both
+sides must result in `true`. When combined with `or`, only one side needs
+to be true to result in `true`.
+
+With these building blocks, basic rules can be built up:
+
+```sentinel
+main = rule {
+ hour >= 8 and
+ hour < 17
+}
+```
+
+Next, we'll introduce
+[imports](/sentinel/intro/getting-started/imports)
+so that we can write rules that interact with external data.
diff --git a/content/sentinel/v0.17.x/content/sentinel/intro/getting-started/next-steps.mdx b/content/sentinel/v0.17.x/content/sentinel/intro/getting-started/next-steps.mdx
new file mode 100644
index 0000000000..23b470be59
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/intro/getting-started/next-steps.mdx
@@ -0,0 +1,21 @@
+---
+page_title: Next Steps
+sidebar_current: intro-getting-started-nextsteps
+description: That concludes the getting started guide for Sentinel.
+layout: intro
+---
+
+# Next Steps
+
+That concludes the getting started guide for Sentinel. Hopefully you're excited
+about the possibilities of Sentinel and ready to put this knowledge to use to
+improve the safety of your environments.
+
+We've covered the basics of the core features of Sentinel in this guide.
+We recommend the following as next steps:
+
+- [Documentation](/sentinel) - The documentation is an in-depth
+ reference guide of all the features of Sentinel.
+
+- [Language Reference](/sentinel/language) - The language reference
+ is a complete reference to the features of the Sentine policy language.
diff --git a/content/sentinel/v0.17.x/content/sentinel/intro/getting-started/rules.mdx b/content/sentinel/v0.17.x/content/sentinel/intro/getting-started/rules.mdx
new file mode 100644
index 0000000000..d39bda7466
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/intro/getting-started/rules.mdx
@@ -0,0 +1,87 @@
+---
+page_title: Rules
+sidebar_title: Rules
+sidebar_current: intro-getting-started-rules
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Rules
+
+Rules in Sentinel are a first-class concept. Within a policy, rules serve a few
+purposes:
+
+- They make complex logic more understandable by allowing said logic to be
+ broken down.
+- They allow assertion of this logic through
+ [testing](/sentinel/intro/getting-started/testing) of the rule's contents.
+- They facilitate reporting of data as rules get published as part of the [policy
+ trace](/sentinel/writing/tracing).
+
+A rule functions in ways similar to both a variable and a function: they hold a
+value, but are lazily evaluated; a rule's value is not assigned until the first
+time it's referenced in a policy. Additionally, while the value of evaluated
+rules will be available within a policy's trace after it's evaluated, values of
+variables - and the return value of functions - are not. Finally, a rule value
+is memoized - further references to the rule will not change its result.
+
+Rules can hold more than just boolean data. For more advanced rule patterns, see
+[the language reference](/sentinel/language/rules).
+
+## Writing Rules
+
+Let's look at the Sentinel policy we wrote in the previous section:
+
+```sentinel
+hour = 4
+main = rule { hour >= 0 and hour < 12 }
+```
+
+The logic in this policy is straightforward and easy to understand. However,
+it is common for policy logic to become much more complicated. Rules are the
+key abstraction for making complex logic understandable. We can turn the
+policy above into a set of rules:
+
+```sentinel
+hour = 4
+
+past_midnight = rule { hour >= 0 }
+before_noon = rule { hour < 12 }
+
+main = rule {
+ past_midnight and
+ before_noon
+}
+```
+
+This policy is easier to read. Our original policy was already quite
+simple, but it is easy to imagine a more complex policy becoming much
+more readable by extracting logical expressions into a set of rules.
+
+Rules don't just exist to make a policy more readable. They're also a
+testing and debugging point. For testing, you can assert that certain rules
+are certain values given a specific input to verify that your policy works
+as you expect. Testing and debugging are covered in later sections.
+
+## Conditional Rules
+
+In many cases, a rule is only valid when something else is also true.
+
+Consider the human language statement "before you eat, you must wash your hands."
+The rule "you must wash your hands" is only valid "before you eat". In any
+other scenario, the rule is meaningless.
+This is also true in a technical context. Imagine the rule "if the driver
+is Docker, you must not expose any ports." For this example, assume
+rules `is_docker` and `has_no_exposed_ports` exist. You can write this rule
+like this:
+
+```sentinel
+main = rule when is_docker { has_no_exposed_ports }
+```
+
+When `is_docker` is true, the rule is evaluated as normal. However,
+when `is_docker` is false, the rule always returns `true`, because the
+logic of the rule is meaningless.
+
+Let's learn how to create more complex rules with
+[logical expressions](/sentinel/intro/getting-started/logic).
diff --git a/content/sentinel/v0.17.x/content/sentinel/intro/getting-started/testing.mdx b/content/sentinel/v0.17.x/content/sentinel/intro/getting-started/testing.mdx
new file mode 100644
index 0000000000..7149a7966f
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/intro/getting-started/testing.mdx
@@ -0,0 +1,72 @@
+---
+page_title: Testing
+sidebar_current: intro-getting-started-testing
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Testing
+
+It is important to ensure the policies you write are correct. Sentinel
+includes a built-in test framework that can be run locally and in CI
+environments to test that policies behave as expected.
+
+A common pitfall with many simple ACL systems is that they provide no easy
+way to verify their correctness. You basically have to set the ACL and try
+the behaviors against a real system to verify it is working as expected.
+This requires a lot of setup and is unique to each system.
+
+[Sentinel's built-in test framework](/sentinel/writing/testing) has zero
+dependencies. Contained within the [Sentinel CLI](/sentinel/commands), it can
+mock the data that real systems are exposing to the policy. It is designed to be
+CI-friendly and enables continuous testing of your policies. This is necessary
+for [policy as code](/sentinel/concepts/policy-as-code).
+
+Detailed documentation on testing policies is available in [the Sentinel Testing
+reference](/sentinel/writing/testing).
+
+## Writing Tests
+
+For this example, save the following policy as `policy.sentinel`:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Next, let's write a passing test case. This will test that the policy tests
+when we expect it to pass. Save the following in `test/policy/good.hcl`:
+
+```hcl
+global "day" {
+ value = "monday"
+}
+
+global "hour" {
+ value = 14
+}
+```
+
+And run `sentinel test`:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/good.json
+```
+
+The `sentinel test` command will automatically find all Sentinel policies
+and their associated test cases and run them all. The test framework will
+run all HCL files as individual test cases, allowing you to test a variety
+of scenarios for your policies.
+
+Try adding another test case to force the policy to fail. This test case can
+be saved at `test/policy/fail.hcl`. For test cases with intentional
+failures, you'll need to use the [test assertions described in the testing
+reference](/sentinel/writing/testing#a-failing-test).
+
+Now that you have a good grasp of what Sentinel is and how to use it, please feel
+free to look through the
+[next steps](/sentinel/intro/getting-started/next-steps)
+you can take to further your Sentinel knowledge.
diff --git a/content/sentinel/v0.17.x/content/sentinel/intro/index.mdx b/content/sentinel/v0.17.x/content/sentinel/intro/index.mdx
new file mode 100644
index 0000000000..785a442fb8
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/intro/index.mdx
@@ -0,0 +1,32 @@
+---
+layout: intro
+page_title: Documentation
+sidebar_current: docs-home
+description: >-
+ Sentinel is a language and framework for policy built to be embedded in
+ existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+[introduction](/sentinel/intro) to understand what Sentinel is, how it
+compares to other software, and more.
+
+
diff --git a/content/sentinel/v0.17.x/content/sentinel/intro/what.mdx b/content/sentinel/v0.17.x/content/sentinel/intro/what.mdx
new file mode 100644
index 0000000000..284fa5a82e
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/intro/what.mdx
@@ -0,0 +1,65 @@
+---
+page_title: Introduction
+sidebar_title: What is Sentinel?
+sidebar_current: intro-what
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: intro
+---
+
+# Introduction to Sentinel
+
+Welcome to the intro guide to Sentinel! This guide is the best
+place to start with Sentinel.
+
+If you are already familiar with the basics of Sentinel, the
+[documentation](/sentinel) provides a better reference
+guide for all available features as well as internals.
+
+## What is Sentinel?
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+Sentinel provides a language for writing policy and a workflow for developing
+and testing policies independent of the software they'll be written to. We
+also provide a framework for developers to build plugins that allow a
+Sentinel-enabled system to access external information to make policy
+decisions.
+
+Most systems today have some degree of access control. You are able to define
+identities and what they have access to. These ACL systems solve an immediate
+and necessary problem of locking down a system in very broad strokes. Sentinel
+is a reusable system for more advanced software policy decisions. Sentinel
+enables:
+
+- **Fine-Grained Policy**: Most ACL systems only enable coarse-grained
+ behaviors: "read", "write", etc. Sentinel enables fine-grained behavior such
+ as disallowing a certain API call when specific parameters are present.
+
+- **Logic-Based Policy**: You can write policy using full
+ conditional logic. For example, you may only allow a certain application behavior
+ on Monday to Thursday unless there is a manager override.
+
+- **Accessing External Information**: Sentinel can source external information
+ to be used in policy decisions. For example, a policy that restricts the size
+ of a payload may read data from [Consul](https://www.consul.io) to determine
+ the payload size limit.
+
+- **Enforcement levels**: Sentinel allows policies to be defined along
+ with an "enforcement level" that dictates the pass/fail behavior of a policy.
+ Advisory policies warn if they fail, soft mandatory policies can have their
+ failures overridden, and hard mandatory policies must pass under all
+ circumstances. Having this as a built-in concept enables you to model
+ policy more accurately and completely for your organization.
+
+## Next Steps
+
+See the page on [Why Sentinel?](/sentinel/intro/why) to learn more
+about the origins of the product. Then, continue onwards with
+the [getting started guide](/sentinel/intro/getting-started/install) to write
+your first policies and see how Sentinel works in practice.
diff --git a/content/sentinel/v0.17.x/content/sentinel/intro/why.mdx b/content/sentinel/v0.17.x/content/sentinel/intro/why.mdx
new file mode 100644
index 0000000000..37e01c6fc3
--- /dev/null
+++ b/content/sentinel/v0.17.x/content/sentinel/intro/why.mdx
@@ -0,0 +1,50 @@
+---
+page_title: Why Sentinel?
+sidebar_title: Why Sentinel?
+sidebar_current: intro-why
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: intro
+---
+
+# Why Sentinel?
+
+The growth of infrastructure and applications has been enabled in part
+by an increasing trend towards automation everywhere.
+Configuration as code (such as Chef or Puppet with [Packer](https://www.packer.io))
+has enabled automated machine configuration. Infrastructure
+as code (such as [Terraform](https://www.terraform.io)) has enabled
+automatic infrastructure creation. And schedulers (such as
+[Nomad](https://www.nomadproject.io) and Kubernetes) have enabled
+automatic application deployment.
+Sentinel enables guardrails to be put in place
+on automation while allowing the codification and automatic enforcement
+of business requirements in critical areas of your infrastructure.
+
+Meanwhile, businesses have business requirements and sometimes legal
+requirements which must be expressed in policies. Traditionally, these
+policies are enforced by humans. But in a highly automated world, the
+automation is only as fast as its slowest component. In many cases, this
+is the human verification step.
+
+As an example: before infrastructure as code and autoscaling, if an order
+came through for 5,000 new machines, a human would likely respond to the
+ticket verifying that the user really intended to order 5,000 new machines.
+Today, automation can almost always freely order 5,000 new compute instances
+without any hesitation, which can result in unintended expense or system
+instability.
+
+Sentinel introduces [policy as code](/sentinel/concepts/policy-as-code)
+and a powerful framework built-in to HashiCorp tooling to allow automation
+guardrails, business requirements, legal compliance, and more to be
+actively enforced by the running systems in realtime.
+
+With Sentinel, you can require an override to create certain numbers of
+infrastructure resources. You can disallow unsafe deployment configurations
+with Nomad. You can enforce certain key/value formats in Consul. You
+can restrict secret access by time in Vault. And more.
+
+Sentinel is available today in HashiCorp enterprise products. You can
+try Sentinel immediately with the [getting started guide](/sentinel/intro/getting-started/install) or learn more about the integrations in the
+[documentation](/sentinel).
diff --git a/content/sentinel/v0.17.x/data/docs-nav-data.json b/content/sentinel/v0.17.x/data/docs-nav-data.json
new file mode 100644
index 0000000000..5c6d583186
--- /dev/null
+++ b/content/sentinel/v0.17.x/data/docs-nav-data.json
@@ -0,0 +1,316 @@
+[
+ {
+ "title": "Release Notes",
+ "path": "changelog"
+ },
+ {
+ "title": "Basic Concepts",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "concepts"
+ },
+ {
+ "title": "Policy as Code",
+ "path": "concepts/policy-as-code"
+ },
+ {
+ "title": "Policy Language",
+ "path": "concepts/language"
+ },
+ {
+ "title": "Imports",
+ "path": "concepts/imports"
+ },
+ {
+ "title": "Enforcement Levels",
+ "path": "concepts/enforcement-levels"
+ }
+ ]
+ },
+ {
+ "title": "Configuration File Syntax",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "configuration"
+ },
+ {
+ "title": "Remote Sources",
+ "path": "configuration/remote-sources"
+ }
+ ]
+ },
+ {
+ "title": "Commands (CLI)",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "commands"
+ },
+ {
+ "title": "apply",
+ "path": "commands/apply"
+ },
+ {
+ "title": "fmt",
+ "path": "commands/fmt"
+ },
+ {
+ "title": "test",
+ "path": "commands/test"
+ }
+ ]
+ },
+ {
+ "title": "Writing Policy",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "writing"
+ },
+ {
+ "title": "Basics",
+ "path": "writing/basic"
+ },
+ {
+ "title": "Rules",
+ "path": "writing/rules"
+ },
+ {
+ "title": "Traces",
+ "path": "writing/tracing"
+ },
+ {
+ "title": "Testing",
+ "path": "writing/testing"
+ },
+ {
+ "title": "Imports",
+ "path": "writing/imports"
+ },
+ {
+ "title": "Debugging",
+ "path": "writing/debugging"
+ }
+ ]
+ },
+ {
+ "title": "Extending Sentinel",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "extending"
+ },
+ {
+ "title": "Modules",
+ "path": "extending/modules"
+ },
+ {
+ "title": "Plugins",
+ "path": "extending/plugins"
+ },
+ {
+ "title": "Internals",
+ "path": "extending/internals"
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Language",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "language"
+ },
+ {
+ "title": "Variables",
+ "path": "language/variables"
+ },
+ {
+ "title": "Values",
+ "path": "language/values"
+ },
+ {
+ "title": "Lists",
+ "path": "language/lists"
+ },
+ {
+ "title": "Maps",
+ "path": "language/maps"
+ },
+ {
+ "title": "Rules",
+ "path": "language/rules"
+ },
+ {
+ "title": "Imports",
+ "path": "language/imports"
+ },
+ {
+ "title": "Parameters",
+ "path": "language/parameters"
+ },
+ {
+ "title": "Boolean Expressions",
+ "path": "language/boolexpr"
+ },
+ {
+ "title": "Arithmetic",
+ "path": "language/arithmetic"
+ },
+ {
+ "title": "Slices",
+ "path": "language/slices"
+ },
+ {
+ "title": "Conditionals",
+ "path": "language/conditionals"
+ },
+ {
+ "title": "Loops",
+ "path": "language/loops"
+ },
+ {
+ "title": "Collection Operations",
+ "path": "language/collection-operations"
+ },
+ {
+ "title": "Functions",
+ "path": "language/functions"
+ },
+ {
+ "title": "Scope",
+ "path": "language/scope"
+ },
+ {
+ "title": "Undefined",
+ "path": "language/undefined"
+ },
+ {
+ "title": "Logging and Errors",
+ "path": "language/logging-errors"
+ },
+ {
+ "title": "Specification",
+ "path": "language/spec"
+ }
+ ]
+ },
+ {
+ "title": "Builtin Functions",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "functions"
+ },
+ {
+ "title": "append",
+ "path": "functions/append"
+ },
+ {
+ "title": "delete",
+ "path": "functions/delete"
+ },
+ {
+ "title": "error",
+ "path": "functions/error"
+ },
+ {
+ "title": "keys",
+ "path": "functions/keys"
+ },
+ {
+ "title": "length",
+ "path": "functions/length"
+ },
+ {
+ "title": "print",
+ "path": "functions/print"
+ },
+ {
+ "title": "range",
+ "path": "functions/range"
+ },
+ {
+ "title": "values",
+ "path": "functions/values"
+ }
+ ]
+ },
+ {
+ "title": "Standard Imports",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "imports"
+ },
+ {
+ "title": "base64",
+ "path": "imports/base64"
+ },
+ {
+ "title": "decimal",
+ "path": "imports/decimal"
+ },
+ {
+ "title": "http",
+ "path": "imports/http"
+ },
+ {
+ "title": "json",
+ "path": "imports/json"
+ },
+ {
+ "title": "runtime",
+ "path": "imports/runtime"
+ },
+ {
+ "title": "sockaddr",
+ "path": "imports/sockaddr"
+ },
+ {
+ "title": "strings",
+ "path": "imports/strings"
+ },
+ {
+ "title": "time",
+ "path": "imports/time"
+ },
+ {
+ "title": "types",
+ "path": "imports/types"
+ },
+ {
+ "title": "units",
+ "path": "imports/units"
+ },
+ {
+ "title": "version",
+ "path": "imports/version"
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Consul",
+ "path": "consul"
+ },
+ {
+ "title": "Nomad",
+ "path": "nomad"
+ },
+ {
+ "title": "Terraform",
+ "path": "terraform"
+ },
+ {
+ "title": "Vault",
+ "path": "vault"
+ }
+]
diff --git a/content/sentinel/v0.17.x/data/intro-nav-data.json b/content/sentinel/v0.17.x/data/intro-nav-data.json
new file mode 100644
index 0000000000..c95181af70
--- /dev/null
+++ b/content/sentinel/v0.17.x/data/intro-nav-data.json
@@ -0,0 +1,47 @@
+[
+ {
+ "title": "What is Sentinel?",
+ "path": "what"
+ },
+ {
+ "title": "Why Sentinel?",
+ "path": "why"
+ },
+ {
+ "title": "Getting Started",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "getting-started"
+ },
+ {
+ "title": "Installing the CLI",
+ "path": "getting-started/install"
+ },
+ {
+ "title": "Your First Policy",
+ "path": "getting-started/first-policy"
+ },
+ {
+ "title": "Rules",
+ "path": "getting-started/rules"
+ },
+ {
+ "title": "Logic",
+ "path": "getting-started/logic"
+ },
+ {
+ "title": "Imports",
+ "path": "getting-started/imports"
+ },
+ {
+ "title": "Testing",
+ "path": "getting-started/testing"
+ },
+ {
+ "title": "Next Steps",
+ "path": "getting-started/next-steps"
+ }
+ ]
+ }
+]
diff --git a/content/sentinel/v0.17.x/data/metadata.js b/content/sentinel/v0.17.x/data/metadata.js
new file mode 100644
index 0000000000..6b162ed019
--- /dev/null
+++ b/content/sentinel/v0.17.x/data/metadata.js
@@ -0,0 +1,2 @@
+export const productName = 'Sentinel'
+export const productSlug = 'sentinel'
diff --git a/content/sentinel/v0.17.x/data/subnav.js b/content/sentinel/v0.17.x/data/subnav.js
new file mode 100644
index 0000000000..dbe89d84ea
--- /dev/null
+++ b/content/sentinel/v0.17.x/data/subnav.js
@@ -0,0 +1,12 @@
+export default [
+ {
+ url: '/sentinel/intro',
+ text: 'Intro',
+ type: 'inbound',
+ },
+ {
+ url: '/sentinel',
+ text: 'Docs',
+ type: 'inbound',
+ },
+]
diff --git a/content/sentinel/v0.17.x/data/version.js b/content/sentinel/v0.17.x/data/version.js
new file mode 100644
index 0000000000..7a93d92ddd
--- /dev/null
+++ b/content/sentinel/v0.17.x/data/version.js
@@ -0,0 +1,2 @@
+// This file is auto-generated - DO NOT EDIT
+export default "0.17.4"
\ No newline at end of file
diff --git a/content/sentinel/v0.17.x/img/sentinel-import-topology.svg b/content/sentinel/v0.17.x/img/sentinel-import-topology.svg
new file mode 100644
index 0000000000..36f7c79bc9
--- /dev/null
+++ b/content/sentinel/v0.17.x/img/sentinel-import-topology.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/changelog.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/changelog.mdx
new file mode 100644
index 0000000000..4bdea9afca
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/changelog.mdx
@@ -0,0 +1,669 @@
+---
+page_title: Sentinel Runtime Release Notes
+sidebar_current: docs-changelog
+description: >-
+ This are the public release notes for the Sentinel runtime. See this document
+ for the latest updates.
+layout: docs
+---
+
+# Sentinel Runtime Release Notes
+
+These are the release notes for the Sentinel runtime.
+
+Each version of the runtime is released with a corresponding version of
+[Sentinel CLI](/sentinel/commands). To download the CLI, see the [downloads
+page](/sentinel/downloads).
+
+Sentinel integrations and embedded runtimes may not always have the latest
+version installed, depending on the product's individual release cycle. For more
+information, contact the support team for your specific integration.
+
+
+## 0.18.13 (October 31, 2022)
+
+BUG FIXES:
+
+- `cmd/apply`: `sentinel apply` no longer aborts on advisory policy failure.
+
+## 0.18.12 (September 15, 2022)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where nested `CallExpr` were not rewritten.
+
+## 0.18.11 (June 8, 2022)
+
+NOTES:
+
+- `config`: The `enforcement_level` key for policies is now optional, defaulting to `"advisory"` when not supplied.
+
+## 0.18.10 (May 20, 2022)
+
+NOTES:
+
+- `runtime/initwd`: Improvements to the installer, including timeouts and filesize limits, as well as disabling support
+ for symlinks within `git` or `hg` repositories.
+
+## 0.18.9 (March 23, 2022)
+
+NOTES:
+
+- This release fixes an issue with the Docker container build pipeline. There are no changes to the
+Sentinel runtime or CLI.
+
+## 0.18.8 (March 22, 2022)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue in the installer for Windows paths.
+
+## 0.18.7 (February 24, 2022)
+
+NOTES:
+
+- **Darwin arm64**. This release will be our first to include Darwin arm64 architecture.
+
+BUG FIXES:
+
+- `lang/ast`: Fixed issues where `IndexSpec` and `RuleLit` nodes where returning incorrect end positions.
+- `lang/ast`: Fixed an issue where `ParamSpec` nodes where returning incorrect end positions.
+
+## 0.18.6 (February 2, 2022)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.18.5 (January 14, 2022)
+
+IMPROVEMENTS:
+
+- `runtime/initwd`: Changed the sum algorithm used during remote installation from `md5` to `sha256` to reduce chance of collision.
+
+## 0.18.4 (July 20, 2021)
+
+FEATURES:
+
+- `imports/static`: Improved handling of struct tags when performing encoding, allowing `-` to mark as ignored.
+
+## 0.18.3 (June 1, 2021)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue for remote source installation where duplicate sub-directories resulted in incorrect installation.
+- `imports/static`: Fixed an issue where global configuration that contained an empty map could not be accessed without raising selector issues.
+
+## 0.18.2 (May 18, 2021)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where import expressions inside lists and maps were not being evaluated correctly.
+
+## 0.18.1 (May 11, 2021)
+
+BUG FIXES:
+
+- `imports/json`: Fixed an issue where empty maps where failing when passed at any level to `marshal`.
+
+## 0.18.0 (March 25, 2021)
+
+FEATURES:
+
+- `imports/http`: Added support for POST actions within the `http` import
+ through addition of the `post` function. Additionally, support for adding
+ a body to the `request` object has been added via `with_body`.
+
+## 0.17.4 (February 2, 2021)
+
+BUG FIXES:
+
+- `runtime/format`: Fixed a nesting issue with the rule printer where nesting
+ state leaks were leading to all collection values eventually being truncated,
+ regardless of their nesting level.
+
+## 0.17.3 (January 15, 2021)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue where local file installation failed on Windows
+ when using `sentinel test`.
+
+## 0.17.2 (January 13, 2021)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where duplicate log messages were being printed with
+ `sentinel test`.
+
+## 0.17.1 (January 7, 2021)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where hard-coded path separator caused `sentinel
+ test` issues in Windows.
+
+## 0.17.0 (January 5, 2021)
+
+LANGUAGE CHANGES:
+
+- **Map Expression** `map`. A new quantifier expression, `map` has been added.
+ `map` takes a collection and applies an operation to each element in the
+ collection, returning a list of results with the resulting values.
+- **Emptiness checks** `is empty` and `is not empty`. Two new expressions,
+ `is empty` and `is not empty` have been added. As they are aliases to the
+ `length` built-in, only `string`, `map`, `list` or `undefined` is
+ supported.
+- **Comparable maps** Maps (the data type) are now comparable. Maps are equal if
+ they are of equal length and both their corresponding keys and values are
+ comparable and equal.
+- **Rich Return Types** Rules can now process and return non-boolean data.
+ Scalar, list, and map types are supported. When non-boolean values are used,
+ the presence of a non-zero or non-zero-length `main` rule determines policy
+ failure. More details can be found on
+ https://docs.hashicorp.com/sentinel/language#main.
+
+FEATURES:
+
+- **CLI**: `sentinel apply` and `sentinel test` now have options for JSON
+ output. For more details, see the CLI documentation.
+- `imports/base64`: Added a new import, `base64`, to assist with encoding and
+ decoding base64 strings.
+
+## 0.16.1 (October 21, 2020)
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where `contains` to ensure any instance of `undefined` would correctly result in `undefined`.
+- `runtime/eval`: Fixed an issue in `any` and `all` expressions to ensure correct boolean logic when dealing with a collection containing `undefined`.
+- `imports`: Fixed an issue where import processes were not killed, which caused non-graceful closures for the clients.
+- `lang/scanner`: Fixed an issue where inline `#` style comments were not being consumed.
+
+## 0.16.0 (October 14, 2020)
+
+BREAKING CHANGES:
+
+- `cmd/doc`: Removal of the deprecated `sentinel doc` command.
+- `sentinel`: Replace `whitelist` and `blacklist` with `allowlist` and `denylist`, as per inclusive language changes.
+
+FEATURES:
+
+- `imports/version`: Added a new import, `version`, which supplies helpers for dealing with semantic versioning.
+- `cmd/apply`: Added an `HCL` based configuration file, which will eventually deprecate the legacy `JSON` configuration.
+- `cmd/apply`: Added support for download remote policies and modules.
+- `cmd/test`: Added support for downloading remote test modules.
+- `cmd/apply`: Added support for evaluating a policy based on it's key within the configuration.
+- `cmd/apply`: Added support for running all policies in a configuration by default.
+
+## 0.15.6 (July 7, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.15.5 (May 20, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel integrations. There are no user-facing changes.
+
+## 0.15.4 (May 13, 2020)
+
+BUG FIXES:
+
+- `imports`: Fixed an issue with the standard imports that was affecting the handling of null data within lists.
+
+## 0.15.3 (April 16, 2020)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where `IndexExpr` were not rewritten, as well as ensuring `SelectorExpr` uses any nested rewrites.
+- `lang/printer`: Fixed issue printing deeply nested structures and/or loops.
+
+IMPROVEMENTS:
+
+- `imports/decimal`: Added alias methods to improve consistency of method names within the `decimal` import. The alias methods are `is_nan` for `isnan`, as well as both `is_inf` and `is_infinite` for `isinfinite`.
+- `command/apply`: `sentinel apply` will now output the policy description when a trace is forced via `-trace`.
+- `sentinel`: Improved `String` output formatting of Policy docstring for `EvalPolicyResult`.
+
+## 0.15.2 (April 2, 2020)
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue with the `printer` that was causing a panic
+ when a `#` line comment was used with no text following it.
+- `imports/types`: Fixed an issue with the `types` import where calls to
+ `type_of` for undefined types were incorrectly returning as map types. These
+ will now be correctly be identified as undefined as expected.
+
+## 0.15.1 (March 6, 2020)
+
+BUG FIXES:
+
+- `sentinel`: Fixed how modules are managed internally in the runtime to correct
+ race conditions in concurrent scenarios and to allow for per-evaluation module
+ restrictions. This functionality is only of interest to embedded applications
+ with long-lived runtimes and is currently not implemented in any HashiCorp
+ integration.
+
+## 0.15.0 (March 5, 2020)
+
+NOTES:
+
+- **Windows Digital Signature**. Our windows releases for this version and up will be signed
+ and verified according to Microsoft's requirements.
+
+FEATURES:
+
+- `cmd/config`: Modules can now be loaded off of local disk using the Sentinel
+ CLI. For information on how to do so, see the Sentinel documentation.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: Changed modules so that they now have singleton scope when
+ loaded. Importing a module under two different aliases, or within a nested
+ module, will now share scopes - modification of state in one will alter the
+ other.
+- `lang/object`: Introduced an interface to allow the duplication of various
+ types, like collections.
+- `runtime/eval`: Changed how collection data is returned from modules. This
+ data is now duplicated to prevent accidental or deliberate circumvention of
+ the prohibition on assignment to import data, and brings behavior to parity
+ with binary imports.
+
+## 0.14.4 (February 6, 2020)
+
+IMPROVEMENTS:
+
+- `imports/time`: Changed the `add` timespace function in the `time` import to
+ allow it to take float types as durations. These values will be truncated to
+ the appropriate integer-value duration.
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where out-of-place right-hand braces were being
+ parsed as empty statements, instead of raising syntax errors.
+
+## 0.14.3 (January 22, 2020)
+
+IMPROVEMENTS:
+
+- `cmd/test`: Fail when an assertion is not found in a trace.
+- `cmd/test`: Added a message to notify when no rules were evaluated in a test.
+
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue where import aliases (the `as baz` in `import
+ "foo/bar" as baz`) were being removed when formatting with `sentinel fmt`.
+
+- `imports/http`: Fixed an issue with the http import where the client library's
+ debug logs were being printed to standard error.
+
+## 0.14.2 (January 15, 2020)
+
+NOTES:
+
+With this release, we are discontinuing support for MacOS 32-bit. 64-bit builds
+are now the only builds available for MacOS.
+
+IMPROVEMENTS:
+
+- `lang/printer`: A newline is now added to files formatted via `sentinel fmt`.
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue in printing where multi-line expressions would
+ indent infinitely. They will now only indent once at the most. This is mostly
+ seen when using `sentinel fmt`.
+- `runtime/encoding`: Fixed an issue where if a plugin returned a deeply
+ embedded undefined value, the value was instead decoded into a map.
+
+## 0.14.1 (January 6, 2020)
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where reserved words could not be used as
+ selectors when the selector expression was the last lexeme on a line.
+
+## 0.14.0 (December 18, 2019)
+
+LANGUAGE CHANGES:
+
+- **Filter Expression** `filter`. A new quantifier expression, `filter` has been added.
+ `filter` returns a subset of the provided collection based on a boolean condition that
+ is asserted for each element.
+
+NOTES:
+
+- **Apple Notarization**. Our darwin releases for this version and up will be signed
+ and notarized according to Apple's requirements.
+
+## 0.13.1 (November 25, 2019)
+
+BUG FIXES:
+
+- `runtime/eval`: Parameters are no longer allowed in mock files. Adding one to
+ a mock will result in a runtime error.
+- `runtime/eval`: Fixed an issue where import calls from within mocks were
+ failing under certain circumstances.
+- `imports/decimal`: `int` should now correctly provide the truncated integer
+ representation of a decimal number, not the rounded one.
+
+## 0.13.0 (November 15, 2019)
+
+LANGUAGE CHANGES:
+
+- **Policy Parameters**. The new [policy
+ parameters](https://docs.hashicorp.com/sentinel/language/parameters) feature
+ allows authors to use a `param` declaration at the top of a policy to supply
+ values that are expected to come from outside of a policy. See the linked
+ documentation for more details.
+
+FEATURES:
+
+- `imports/http`: The [`http`
+ import](https://docs.hashicorp.com/sentinel/imports/http) is a new addition
+ to the standard library enabling the use of HTTP-accessible data from outside
+ the runtime in policy rules.
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where loading other imports before a mocked
+ import would cause those imports to no longer be visible from within the
+ policy.
+
+## 0.12.0 (October 7, 2019)
+
+LANGUAGE CHANGES:
+
+- **New `case` statement**. This statement is a selection control mechanism to
+ conditionally execute a branch based on expression equality, allowing
+ simplification of complex conditional chains that may otherwise need to be
+ written with `else if`. See the [Case
+ Statements](https://docs.hashicorp.com/sentinel/language/spec#case-statements)
+ section of the Sentinel language specification for more details.
+
+IMPROVEMENTS:
+
+- `lang/semantic`: Added a semantic check to ensure usage of append is not using
+ a return value.
+
+BUG FIXES:
+
+- `runtime/eval`: Corrected an issue where calling a method on an import object
+ value that was the result of a method call on another import object value
+ would have erroneously tried to call an import of the name of the "parent"
+ import object value. Example: in `a = subject.new(); b = a.call(); c =
+ b.call()`, `b.call()` would attempt to call a method named `call` on the root
+ namespace for an import named `a`. This has now been corrected so that
+ `b.call()` will now correctly call the `call` method for the respective object
+ namespace residing in the import named `subject`.
+- `imports`: Some standard imports may have been returning null for some unknown
+ keys in objects when they should have been returning undefined. This was due
+ to an SDK issue which was corrected in SDK version 0.3.2, which has now been
+ corrected.
+- `cmd/test`: `sentinel test` will now correctly fail a policy if it encounters
+ an error.
+- `cmd/test`: `sentinel test` will now correctly display errors and other output
+ that were missing due to a formatting issue.
+- `runtime/eval`: The `append` builtin now correctly returns undefined for all
+ calls, as called for by the Specification. Note that in most cases, the
+ semantic check outlined above will trigger an error if the return value is
+ used.
+
+## 0.11.0 (September 5, 2019)
+
+LANGUAGE CHANGES:
+
+- **New builtin function:** `range()`. This function existed in the spec in
+ earlier versions but was removed as it lacked an implementation. This has now
+ been implemented and re-added to the spec. See the
+ [Range](https://docs.hashicorp.com/sentinel/language/spec#range) section of
+ the Sentinel Specification for more details.
+- Lists are now comparable. Lists are equal if their corresponding elements are
+ comparable and equal.
+- Method calls on values returned by imports are now supported. See the
+ [Imports](https://docs.hashicorp.com/sentinel/language/spec#imports) section
+ of the Sentinel Specification for more details.
+
+FEATURES:
+
+- `imports/decimal`: This is a new import designed to do exact precision
+ mathematical calculations.
+- `runtime/eval`: Compound call expressions that refer to imports (example:
+ foo.bar().baz() when `foo` is a loaded import) will now function as expected.
+ Previously, this was only supported up to the first call (example:
+ `foo.bar()`).
+- `imports/time`: Timespaces returned by calls such as `time.now` are now
+ callable. Example: `t = time.now; t.after(some_previous_time)` will now
+ function.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: The implementation of comparison of non-comparable types has
+ now changed. Rather than triggering a runtime error, non-comparable types will
+ now return false when attempting to compare.
+
+## 0.10.4 (August 15, 2019)
+
+BUG FIXES:
+
+- `runtime/encoding`: Fixed an issue with conversion of null values that could
+ lead to crashes in imports.
+
+## 0.10.3 (July 15, 2019)
+
+BUG FIXES:
+
+- `command/config`: Parsing a configuration file where malformed data is
+ encountered after apparently properly-formed data will now report an error. An
+ example would be a situation where misplaced braces would cause a JSON object
+ to only parse part of the configuration file.
+
+## 0.10.2 (June 25, 2019)
+
+BUG FIXES:
+
+- `lang/parser`: Corrected an issue where parsing certain compound binary
+ expressions where the negation predicate (`not`) was in use would cause the
+ negation to have no effect. Example: `foo else "bar" not in "baz"` would have
+ been parsed and evaluated as `foo else "bar" in "baz"`, effectively producing
+ the opposite result.
+
+## 0.10.1 (May 9, 2019)
+
+BUG FIXES:
+
+- `command/test`: `sentinel test` now displays policies without
+tests as an unknown result with [no test files], instead of the somewhat
+erroneous behavior of displaying it as a PASS. A full test run with a mixture
+of passing tests and no tests still results in an overall successful result.
+
+## 0.10.0 (April 18, 2019)
+
+LANGUAGE CHANGES:
+
+- Mixed-number arithmetic operations are now allowed. Addition (`+`),
+ subtraction (`-`), multiplication (`*`), division (`/`), and remainder
+ operations (`%`) are no longer restricted to number values of the same
+ type, and can mix integer and floating point. The result of these operations
+ is always a floating-point number.
+- Remainder (modulo, `%`) operations are now allowed on floating-point numbers.
+
+
+BUG FIXES:
+
+- `lang/parser`: Fixed a bug that affected the use of `not` with `contains`,
+ `matches`, or `in` in a compound expression.
+- `runtime/eval`: Fixed error messages on evaluation errors with `contains` and
+ `in` to indicate that strings are an allowed type along with lists and maps.
+
+
+## 0.9.2 (March 15, 2019)
+
+This is a dependency update related to the changes mentioned in 0.9.1. No other
+changes have been made.
+
+## 0.9.1 (March 14, 2019)
+
+This is a patch release that is required to integrate with the latest versions
+of the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). No other
+changes have been made.
+
+## 0.9.0 (January 28, 2019)
+
+FEATURES:
+
+- `imports/strings`: Added a `join` function to the `strings` import. This can
+ be used to join a list into a string with a specific separator.
+ Multi-dimensional lists and all Sentinel primitives are supported.
+
+## 0.8.1 (January 17, 2019)
+
+BUG FIXES:
+
+- `lang/eval`: Fixed a bug that prevents the effective use of `return`
+ statements in `for` loops.
+
+## 0.8.0 (January 14, 2019)
+
+FEATURES:
+
+- Mocks can now be represented by Sentinel code. This allows for the mocking of
+ functions and other complex data structures that cannot be represented in
+ JSON. For more information on using this feature via mocks, click
+ [here](https://docs.hashicorp.com/sentinel/commands/config#mocking-with-sentinel-code).
+
+
+IMPROVEMENTS:
+
+- Import validation has now been moved to the semantic checking phase. This
+ should result in better reporting of validation errors. In addition, import
+ validation will now enforce the use of an `as` identifier when an import path
+ is not a valid identifier on its own (example: `import "foo/bar"`).
+
+## 0.7.0 (December 12, 2018)
+
+BUG FIXES:
+
+- There have been changes to the runtime in how scope is handled over multiple
+ policy executions. Scope is now correctly unique per single policy execution,
+ and values set or builtins that are overridden in one policy will no longer
+ affect those values within another.
+
+## 0.6.0 (November 30, 2018)
+
+FEATURES:
+
+- `imports/runtime`: This new import allows for one to check various aspects of
+ the Sentinel runtime as it may be embedded in the simulator or a specific
+ implementation. For now, it allows the version to be checked.
+
+## 0.5.1 (November 28, 2018)
+
+IMPROVEMENTS:
+
+- `imports/time`: Added the `zone` and `zone_string` attributes to assist with
+ validation of a timespace's zone.
+- `command/fmt`: Added a new -check flag. This option does not commit changes,
+ but instead checks to see what files need formatting and outputs them on
+ stdout.
+
+BUG FIXES:
+
+- `command/test`: Ensure that passing test results are correctly output one per
+ line. Tests are also now run in a deterministic fashion based on
+ lexicographical (alphabetical) order.
+- `imports/time`: `month_name` and `weekday_name` will now show up correctly in
+ a returned timespace result.
+
+
+## 0.5.0 (November 5, 2018)
+
+IMPROVEMENTS:
+
+- `spec`: Selectors can now contain any reserved word (example: `rule`) or
+ keyword operator (example: `any`, `all`, `is`, `not`). This only works for the
+ _selector_ part of the expression (after the first period) - the first primary
+ expression (before the first period) still needs to be an identifier that does
+ not conflict with reserved words.
+
+BUG FIXES:
+
+- The simulator should now display import function call names correctly in
+ import errors.
+
+## 0.4.0 (October 1, 2018)
+
+FEATURES:
+
+- `builtin`: Added the `bool` built-in type conversion function. Booleans will
+ also now accepted as conversion into other values as well, with the full list
+ of behaviors available in the spec.
+
+## 0.3.2 (September 27, 2018)
+
+FEATURES:
+
+- `command/apply`: `sentinel apply` now prints out messages output by the
+ `print()` function when a trace is output on policy failure, or when a trace
+ is forced with `-trace`.
+- `imports/time`: Added the `month_name` and `weekday_name` keys to the
+ timespace, which return full-English names for the month and day of the week.
+
+
+BUG FIXES:
+
+- `command/fmt`: `sentinel fmt -` Will no longer print out the filter status
+ message on the output stream when `-write=false` Is not explicitly stated.
+ This brings the behavior of the command in line with the help text.
+- `runtime`: Index operations on the right-hand-side that have negative indexes
+ that go out of range (example: `length(list) * -1 - 1`) now correctly return
+ `undefined`. left-hand-side index assignments with a out-of-range negative
+ index still return runtime errors.
+
+## 0.3.1 (August 3, 2018)
+
+BUG FIXES:
+
+- `runtime`: Basic index assignment has been implemented as per the spec.
+- `runtime`: Index expressions for lists with negative indexes will no longer panic if the
+ list index is less than `length(list) * -1`.
+
+## 0.3.0 (July 20, 2018)
+
+FEATURES:
+
+- **New standard import: `types`.** This can be used to dynamicaly detect the
+ type of some value.
+
+## 0.2.0 (April 11, 2018)
+
+FEATURES:
+
+- **New standard import: `json`.** Marshal and unmarshal JSON documents and
+ access their contents as native Sentinel values.
+- **`break` and `continue`.** These are now both specified and implemented.
+ `break` allows loop exiting and `continue` allows immediate execution of the
+ next iteration.
+
+IMPROVEMENTS:
+
+- runtime: `print()` map values are now ordered alphabetically by keys.
+
+BUG FIXES:
+
+- command/test: If no `test` block exists, test behaves like it is asserting
+ `main: true`.
+- runtime: default maximum stack depth to 500
+- runtime: `print()` map values now appear like more typical maps.
+- runtime: division by zero is an error, not a crash
+- runtime: plugins that send map values with `null` values now decode properly
+ into native Sentinel values.
+
+## 0.1.0 (September 19, 2017)
+
+Initial release.
+
+
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/commands/apply.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/commands/apply.mdx
new file mode 100644
index 0000000000..c5e81d6d65
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/commands/apply.mdx
@@ -0,0 +1,63 @@
+---
+page_title: 'Command: apply'
+sidebar_current: docs-commands-apply
+description: >-
+ The `sentinel apply` command is used to execute a policy locally for
+ development purposes.
+layout: docs
+---
+
+# Command: `apply`
+
+The `sentinel apply` command is used to execute a policy locally for development
+purposes.
+
+## Usage
+
+Usage: `sentinel apply [options] POLICY`
+
+This command executes the policy file at the path specified by POLICY. In
+addition to a path to a policy, POLICY can reference a label of a
+[policy](/sentinel/configuration#policies) entry in the configuration.
+
+Use the exit code of this command to determine the exact status of the policy
+evaluation. `0` is pass, `1` is fail, `2` is undefined (fail, but because the
+result was undefined), and `3` is a runtime error. Errors unrelated to the
+policy status itself are returned with an exit status of `9`.
+
+To control the behavior of the `apply` command, create a [configuration
+file](/sentinel/configuration). With this, you can define available
+[import plugins](/sentinel/concepts/imports), mock data, and global values.
+This can help you simulate a policy embedded within an application.
+
+As of the 0.16 release, POLICY is optional. When it is not supplied,
+`sentinel apply` will run **all** policies in the supplied configuration.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-config=path` - Path to a configuration file specifying available imports,
+ mock data, globals, etc. The default is `sentinel.[hcl|json]`.
+
+- `-json` Enable JSON output. Using this mode, all other output will be
+ suppressed and the full detail of the apply will be output in a
+ machine-readable format. Enabling this implies `-trace`. See the section on
+ [tracing JSON output](/sentinel/writing/tracing#json-output) for more details.
+
+- `-json-rule=RULE` Enable JSON output, outputting only the value of the
+ specified rule. When running against a policy set, the policy must be supplied
+ to enable per-rule filtering. Implies `-json`.
+
+- `-global key=value` - Set global values. This is the same as setting `global`
+ in the configuration file, and will override any of these respective values
+ set in the configuration. The value is either a string, or a JSON number,
+ array, or object. To force strings, use quotes.
+
+- `-param key=value` - Set parameters, the same as setting `param` in the
+ configuration file. Values are handled in the same way they are with the
+ `-global` flag.
+
+- `-trace` - Always show the execution trace. This shows intermediate
+ boolean expression values. This always shows for failed policies.
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/commands/fmt.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/commands/fmt.mdx
new file mode 100644
index 0000000000..8297316d8b
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/commands/fmt.mdx
@@ -0,0 +1,33 @@
+---
+page_title: 'Command: fmt'
+sidebar_current: docs-commands-fmt
+description: The `sentinel fmt` command formats a policy source to a canonical format.
+layout: docs
+---
+
+# Command: `fmt`
+
+The `sentinel fmt` command formats a policy source to a canonical format.
+
+## Usage
+
+Usage: `sentinel fmt [options] FILE ...`
+
+This command formats all the specified policy files to a canonical format.
+
+By default, policy files are overwritten in place. This behavior can be
+changed with the `-write` flag. If a specified FILE is `-` then stdin is
+read and the output is always written to stdout.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-write=true` - Write formatted policy to the named source file. If false,
+ output will go to stdout. If multiple files are specified, the output will
+ be concatenated directly.
+
+- `-check=false` - Don't format, only check if formatting is necessary. Files
+ that require formatting are printed, and a non-zero exit code is returned if
+ changes are required.
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/commands/index.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/commands/index.mdx
new file mode 100644
index 0000000000..7177093cec
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/commands/index.mdx
@@ -0,0 +1,23 @@
+---
+page_title: Commands (CLI)
+sidebar_current: docs-commands
+description: >-
+ Sentinel provides a `sentinel` command-line interface (CLI) for developing and
+ testing policies.
+layout: docs
+---
+
+# Sentinel CLI Commands
+
+The Sentinel command-line interface (CLI) allows for the developing and testing
+of policies outside of a particular Sentinel implementation. Having a standard
+workflow to develop policies is critical for our mission of [policy as
+code](/sentinel/concepts/policy-as-code).
+
+The CLI takes a subcommand to execute. The complete list of subcommands is in
+the navigation to the left.
+
+The Sentinel CLI is a well-behaved command line application. In erroneous cases,
+a non-zero exit status will be returned. It also responds to -h and --help as
+you'd expect. To view a list of the available commands at any time, just run
+`sentinel` with no arguments.
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/commands/test.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/commands/test.mdx
new file mode 100644
index 0000000000..dc8de617ab
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/commands/test.mdx
@@ -0,0 +1,46 @@
+---
+page_title: 'Command: test'
+sidebar_current: docs-commands-test
+description: The `sentinel test` command runs policies against the Sentinel test framework.
+layout: docs
+---
+
+# Command: `test`
+
+The `sentinel test` command runs policies against the Sentinel test framework.
+
+To learn more about testing, see the [testing
+policies](/sentinel/writing/testing) page.
+
+## Usage
+
+Usage: `sentinel test [options] [PATH] ...`
+
+This command runs the defined test cases against a set of policies.
+
+The test cases are expected to live at the path `test//*.[hcl|json]` relative
+to the policy being tested. `` should be the name of the policy
+excluding the file extension. The files should be in the [configuration
+file structure](/sentinel/configuration). The `test` key is used for
+assertions.
+
+The PATH arguments specified may be a list of zero or more files or directories.
+When no arguments are given, it defaults to the current directory. When a
+directory is given, all policies ending with the extension `.sentinel` are
+tested.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-json` - Suppress normal output and output test results as a JSON document.
+ See the [testing JSON output](/sentinel/writing/testing#test-json-output)
+ section for more details.
+
+- `-run=regexp` - Run only tests matching the regular expression pattern.
+ A `/` splits policy name and test case name.
+
+- `-verbose` - Show tracing and log output for tests that succeed in addition
+ to tests that fail. By default, traces and logs are only shown for tests that
+ fail.
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/concepts/enforcement-levels.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/concepts/enforcement-levels.mdx
new file mode 100644
index 0000000000..fc4b8062d0
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/concepts/enforcement-levels.mdx
@@ -0,0 +1,43 @@
+---
+page_title: Enforcement Levels
+sidebar_current: docs-concepts-levels
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Enforcement Levels
+
+Enforcement levels are a first class concept in Sentinel allowing pass/fail
+behavior to be associated separately from the policy logic. This enables
+any policy to be a warning, allow overrides, or be absolutely mandatory.
+Because this level is not part of the policy body itself, different uses of
+the same policy can have different enforcement levels.
+
+Sentinel has three enforcement levels:
+
+- **Advisory:** The policy is allowed to fail. However, a warning should be
+ shown to the user or logged. Advisory is the default enforcement level.
+
+- **Soft Mandatory:** The policy must pass unless an override is specified.
+ The semantics of "override" are specific to each Sentinel-enabled application.
+ The purpose of this level is to provide a level of privilege separation
+ for a behavior. Additionally, the override provides non-repudiation since
+ at least the primary actor was explicitly overriding a failed policy.
+
+- **Hard Mandatory:** The policy must pass no matter what. The only way to
+ override a hard mandatory policy is to explicitly remove the policy.
+ It should be used in situations where an override is not possible.
+
+## Configuring Enforcement Levels
+
+Enforcement levels are configured when a policy is deployed to a
+Sentinel-enabled application. The exact mechanism that the level is specified
+is determined by each application. Please reference the documentation for
+your Sentinel-enabled application for more information.
+
+Enforcement levels are not configured and are not known by the policy body
+itself. All policies should be written to describe exactly the behavior
+they're attempting to control. For example, a policy that restricts deploys
+to business hours should be written exactly like so. When that policy is
+configured on an application, the operator may specify that it is advisory,
+soft, or hard mandatory.
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/concepts/imports.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/concepts/imports.mdx
new file mode 100644
index 0000000000..4d41652def
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/concepts/imports.mdx
@@ -0,0 +1,32 @@
+---
+page_title: Imports
+sidebar_current: docs-concepts-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom import](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only
+local context for making policy decisions.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that the
+import calendar allows access to engineer calendars. This import only exists
+for the purpose of this example and at the time of writing is not a real
+available import.
+
+```json sentinel
+{
+ "policy": "import \"calendar\"\n// Get the calendar for Bob for today\nbob_calendar = calendar.of(\"bob\").today\n\n// Allow this policy to pass if Bob is not on vacation.\nmain = rule { not bob_calendar.has_event(\"vacation\") }",
+ "mocks": {
+ "calendar": "has_event = func(event) {\n\treturn true\n}\nof = func(name) {\n\treturn { \"today\": { \"has_event\": has_event } }\n}"
+ }
+}
+```
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/concepts/index.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/concepts/index.mdx
new file mode 100644
index 0000000000..ce7ac51454
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/concepts/index.mdx
@@ -0,0 +1,15 @@
+---
+page_title: Basic Concepts
+sidebar_current: docs-concepts
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Basic Concepts
+
+This section covers some high level basic concepts that are important
+to understand for day to day Sentinel usage. Every page in
+this section is recommended reading for anyone consuming
+or extending Sentinel.
+
+Please use the navigation to the left to learn more about a topic.
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/concepts/language.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/concepts/language.mdx
new file mode 100644
index 0000000000..ce91f3eb8f
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/concepts/language.mdx
@@ -0,0 +1,91 @@
+---
+page_title: Policy Language
+sidebar_current: docs-concepts-language
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Policy Language
+
+Sentinel defines and uses its own [policy language](/sentinel/language).
+
+The language was designed to be approachable by non-programmers, since
+there are many use cases where the individual defining policy may not
+be a developer. However, the language includes constructs that
+are familiar to developers to enable powerful policies.
+
+To learn more about the language, please see the
+[writing policy section](/sentinel/writing)
+or the [language reference](/sentinel/language).
+
+An example of the policy language is shown below:
+
+```sentinel playground
+import "time"
+
+# Validate time is between 8 AM and 4 PM
+valid_time = rule { time.now.hour >= 8 and time.now.hour < 16 }
+
+# Validate day is M - Th
+valid_day = rule {
+ time.now.weekday_name in ["Monday", "Tuesday", "Wednesday", "Thursday"]
+}
+
+main = rule { valid_time and valid_day }
+```
+
+## Why?
+
+To explain why Sentinel defines and uses its own language, the question
+can be more easily split into roughly two types of languages: _configuration_ languages
+and _programming_ languages.
+
+### Configuration Languages
+
+Configuration languages are formats such as JSON, YAML, XML, etc. Many applications
+use configuration languages as their ACL format. Configuration languages
+are good for static, declarative information. ACL systems are a good fit
+for this. ACL systems are focused, providing the limiting options necessary
+to restrict access to a system.
+
+Configuration languages are not good for dynamic or logical rules. They
+typically only contain limited conditions, loops, or functions. Further
+configuration is often declarative, which can introduce complexities to
+logical statements that depend on ordering.
+
+The use cases that Sentinel was built for require the ability to perform
+complex behavior that didn't model well into a configuration format or a
+declarative form.
+
+### Programming Languages
+
+The Sentinel language was designed with the following goals:
+
+- **Non-programmer friendly.** Sentinel was built to be used by non-programmers.
+ For the use cases we discovered, non-programmers needed the ability to
+ enforce certain rules within a system. For example, a person responsible for
+ compliance may need to insert rules into a system.
+
+- **Programmer friendly.** At the same time, the language needed to support
+ programmer-friendly constructs such as conditionals, loops, and functions
+ for complex policies that a programmer may be writing.
+
+- **Embeddable.** Sentinel is embedded in existing software. The language
+ itself needs to be easily embeddedable. Further, Sentinel was designed
+ to be embedded in [HashiCorp](https://www.hashicorp.com) software
+ written in Go, so it needed to be easily embeddable in Go.
+
+- **Safe.** The language is used in highly security-sensitive environments.
+ It must not be able to crash its host system or access system resources
+ without explicit approval.
+
+Prior to building our own language, Sentinel evaluated other languages.
+Some languages worked quite well. As we continued to develop Sentinel, we
+found that the flexibility and control over designing our own language
+outweighed the costs.
+
+Because the language itself doesn't need to be general purpose, building
+a language focused on policy proved to be the best route for Sentinel.
+It allows us to build powerful tracing capabilities for debugging, make
+interesting performance choices, and extend the language as needed in the
+future.
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/concepts/policy-as-code.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/concepts/policy-as-code.mdx
new file mode 100644
index 0000000000..6dac593e18
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/concepts/policy-as-code.mdx
@@ -0,0 +1,72 @@
+---
+page_title: Policy as Code
+sidebar_current: docs-concepts-pac
+description: >-
+ Policy as code is the idea of writing code in a high-level language to manage
+ and automate policies. By representing policies as code in text files, proven
+ software development best practices can be adopted such as version control,
+ automated testing, and automated deployment.
+layout: docs
+---
+
+# Policy as Code
+
+Policy as code is the idea of writing code in a high-level language to
+manage and automate policies. By representing policies as code in text files,
+proven software development best practices can be adopted such as version
+control, automated testing, and automated deployment.
+
+Many existing policy or ACL systems do not practice policy as code. Many
+policies are set by clicking in a GUI, which isn't easily repeatable nor
+versionable. They usually don't provide any system for testing policies
+other than testing an action that would violate the policy. This makes it
+difficult for automated testing. And the policy language itself varies by
+product.
+
+Sentinel is built around the idea and provides all the benefits of policy as code.
+
+## Benefits
+
+Policy as code provides a number of benefits:
+
+- **Sandboxing.** Policies provide the guardrails for other automated systems.
+ As the number of automated systems grow, there is also a growing need to
+ protect those automated systems from performing dangerous actions. Manual
+ verification is too slow; policies need to be represented as code to
+ keep up with other automated systems.
+
+- **Codification.** By representing policy logic as code, the information
+ and logic about a policy is directly represented in code and can be augmented
+ with comments rather than relying on oral tradition to learn about the
+ reason for policies.
+
+- **Version Control.** Policies are encouraged to be stored as simple text
+ files managed by a version control system. This lets you gain all the
+ benefits of a modern VCS such as history, diffs, pull requests, and more.
+
+- **Testing.** Policies are just code. Their syntax and behavior can be
+ [easily validated with Sentinel](/sentinel/commands/test). This also encourages
+ automated testing such as through a CI. Paired with a VCS system, this
+ allows a pull request workflow to verify that a policy keeps the system
+ behavior as expected before merging.
+
+- **Automation.** With all policies as code in simple text files, various
+ automation tools can be used. For example, it is trivial to create tools
+ to automatically deploy the policies into a system.
+
+## Sentinel and Policy as Code
+
+Sentinel fully embraces policy as code in a number of ways:
+
+- **Language.** All Sentinel policies are written using the
+ [Sentinel language](/sentinel/concepts/language). This language is
+ made to be inputted directly to text files. As an additional benefit,
+ all Sentinel-enabled applications share the same policy language.
+
+- **Development.** Sentinel provides a [CLI](/sentinel/commands)
+ for development and testing. This local CLI can be used to verify policies
+ before deploying them to a system.
+
+- **Testing.** Sentinel provides a [test framework](/sentinel/commands/test)
+ designed specifically for automation. This allows developers and CI systems
+ to further verify policies.
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/configuration/index.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/configuration/index.mdx
new file mode 100644
index 0000000000..6c1de93c7e
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/configuration/index.mdx
@@ -0,0 +1,342 @@
+---
+page_title: Sentinel CLI Configuration File Syntax
+sidebar_current: docs-configuration
+description: >-
+ The Sentinel CLI's configuration file can be used to control the
+ behavior of the simulator during apply and test operations.
+layout: docs
+---
+
+# Sentinel CLI Configuration File Syntax
+
+The Sentinel CLI's configuration file can be used to control the behavior
+of the simulator during [`apply`](/sentinel/commands/apply) and
+[`test`](/sentinel/commands/test) operations.
+
+## Usage
+
+The configuration file is used in different ways depending on what operation you
+are trying to execute.
+
+### Apply
+
+When using `sentinel apply`, supply the configuration file by using the
+`-config=FILE` flag, where `FILE` is the path to the configuration file. The
+default is `sentinel.[hcl|json]`.
+
+See the [apply command](/sentinel/commands/apply) reference for more
+details.
+
+### Test
+
+When using `sentinel test`, each file matching `test//*.[hcl|json]` is a
+configuration file representing a single test case, where `` is the name
+of the policy being tested. Configure assertions for each [test
+case](#test-cases) using the test section.
+
+See the [test command](/sentinel/commands/test) reference for more details.
+
+#### Remote sources
+
+When declaring a `policy` or `module` block within the configuration, a
+`source` attribute must be supplied. This `source` should declare the location
+of the resource, which can be either a local file or a URL pointing to a remote
+file. Examples of local `source` attributes are detailed both in the
+[Policies](#policies) and [Modules](#modules) sections of this page. Below
+are a few examples of remote `source` attributes.
+
+Example:
+
+```hcl
+policy "foo" {
+ source = "git::https://github.com/hashicorp/sentinel-example.git//main.sentinel"
+ enforcement_level = "hard-mandatory"
+}
+```
+
+The above example will fetch the policy from the provided URL and place it into
+the cache ready for use. Be sure to read the
+[Remote Sources](/sentinel/configuration/remote-sources) page for an
+in-depth overview.
+
+## Configuration File Reference
+
+The format of the configuration file is either JSON or HCL. The available
+blocks are:
+
+- `mock` - A mock import specification.
+- `policy` - Configuration for a [policy](/sentinel/writing).
+- `module` - Configuration for a [module](/sentinel/extending/modules).
+- `import` - Configuration for a [plugin](/sentinel/extending/plugins).
+- `global` - Data that is inserted into the global scope.
+- `param` - Values for [parameters](/sentinel/language/parameters) defined in the policy.
+- `test` - Test cases for the [test command](/sentinel/commands/test).
+
+### Mock Imports
+
+Mock imports allow running a policy with an
+[import](/sentinel/concepts/imports) that you may not have access to or is
+too difficult to run. For example, it can be easier to mock data for [Terraform
+Enterprise](https://www.terraform.io/docs/enterprise/sentinel/index.html)
+locally instead of having to run a workspace through a plan/policy check cycle.
+
+Mock imports are specified using the `mock` block, labelled by the import
+name that you want to mock. For example, if you wanted to mock the `time`
+import, you could create an entry with the `time` label pointing to the data
+you want to mock.
+
+The data can take one of two forms:
+
+#### Mocking static data
+
+Static data can be mocked directly via HCl using the `data` attribute on the
+`mock` block.
+
+Example:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ hour = 9
+ minute = 42
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"time\"\n\nmain = rule { time.now.hour is 9 }",
+ "mocks": {
+ "time": "now = { \"hour\": 9, \"minute\": 42 }"
+ }
+}
+```
+
+#### Mocking with Sentinel code
+
+There are some Sentinel types that raw data cannot mock, such as functions,
+and maps that don't have string keys. To mock this data, you can use
+a Sentinel file itself. In this case, you can set the `module` attribute to
+a file with the Sentinel code in it, relative to the current working directory in
+the event of `apply`, or relative to the configuration file location in the
+event of `test`.
+
+Note that `main` is not required in a mock data file.
+
+Example:
+
+```hcl
+mock "foo" {
+ module {
+ source = "mock-foo.sentinel"
+ }
+}
+```
+
+`mock-foo.sentinel` would contain:
+
+```sentinel
+bar = func() {
+ return "baz"
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"foo\"\n\nmain = rule { foo.bar() is \"baz\" }",
+ "mocks": {
+ "foo": "bar = func() { return \"baz\" }"
+ }
+}
+```
+
+### Policies
+
+The `policy` block allows for the the configuration of policies to assist
+with integrating into other commands.
+
+Each `policy` block has a label to identify the policy, with the block
+providing configuration of the policy. The available configuration options for
+policy are `source` and `enforcement_level`. The `source` key provides the
+location of the policy, while `enforcement_level` is currently used by
+integrations such as [Terraform Cloud](https://www.terraform.io/docs/cloud/sentinel/manage-policies.html#enforcement-levels). For more information on the `source`
+value, see [Policy and Module Sources](#policy-and-module-sources).
+
+```hcl
+policy "foo" {
+ source = "foo.sentinel"
+ enforcement_level = "hard-mandatory"
+}
+```
+
+If `enforcement_level` is not supplied, it will fallback to the `advisory` level.
+
+### Modules
+
+The `module` block allows you to map a Sentinel import to a
+[module](/sentinel/extending/modules). The Sentinel CLI will parse the module
+source and make it available for evaluation with the policy.
+
+Each `module` block has a label aligning to the name of the import, with
+the block set to the configuration object for the module. Currently, the only
+value within the configuration object is `source`, which points to the
+location of the module. For more information on the `source` value, see
+[Policy and Module Sources](#policy-and-module-sources).
+
+```hcl
+module "foo" {
+ source = "modules/foo.sentinel"
+}
+
+module "bar" {
+ source = "modules/bar.sentinel"
+}
+```
+
+Note when using [`sentinel test`](/sentinel/commands/test), module paths must be
+specified relative to the location of the configuration file.
+
+```hcl
+module "foo" {
+ source = "../../modules/foo.sentinel"
+}
+
+module "bar" {
+ source = "../../modules/bar/sentinel"
+}
+```
+
+See [Writing and Using a
+Module](/sentinel/extending/modules#writing-and-using-a-module) for more details
+on using a module with this configuration.
+
+### Plugins
+
+Plugins allow you to run a real [import plugin](/sentinel/extending/plugins)
+with a policy. The Sentinel CLI will launch the plugin, connect to it, configure
+it, and execute it as needed by the policy.
+
+Import plugins are specified by the `import` key. The value of this is a map
+where the key is the name of the import and the value is another map with
+configuration about the plugin. The configuration map has the following keys:
+
+- `path` (string, required) - Path to the import plugin executable.
+- `args` (list of string, optional) - A list of arguments to pass to the
+ executable when starting it.
+- `env` (map of string to string, optional) - A set of environmental variables
+ to set when launching the plugin.
+- `config` (map, optional) - Configuration for the plugin itself. This is
+ specific to each individual plugin. Please reference the documentation for
+ the plugin for more information.
+
+Example:
+
+```hcl
+import "time" {
+ path = "/path/to/sentinel-import-time"
+ config = { "fixed_time": 1504155600 }
+}
+```
+
+With the above configuration, the following policy would pass assuming the
+plugin configuration is valid:
+
+```json sentinel
+{
+ "policy": "import \"time\"\n\nmain = time.now.day == 31",
+ "mocks": {
+ "time": "now = { \"day\": 31 }"
+ }
+}
+```
+
+### Global
+
+Global data is injected directly into the global scope of the policy.
+This can be used to simulate global data that may be injected by a
+Sentinel-enabled application.
+
+Global data is specified by the `global` key. The value is a map of
+variables to inject directly into the running policy.
+
+Example:
+
+```hcl
+global "time" {
+ value = {
+ now = {
+ day = 31
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass. Notice
+that we don't have to do any `import`. The values of `global` are
+injected directly into the global scope.
+
+```json sentinel
+{
+ "policy": "main = time.now.day == 31",
+ "globals": {
+ "time": {
+ "now": {
+ "day": 31
+ }
+ }
+ }
+}
+```
+
+### Parameters
+
+The `param` section allows you to supply values for the
+[parameters](/sentinel/language/parameters) found in a policy. Values
+entered here will satisfy required parameters in a policy, in addition to
+override any defaults. Note that any of the manual parameter value methods
+supported by `sentinel apply` will override the values set here.
+
+```hcl
+param "foo" {
+ value = "bar"
+}
+```
+
+### Test Cases
+
+Test cases specify the test cases for the [test command](/sentinel/commands/test).
+
+Tests are specified by the `test` key. The value of this is a map of
+string to boolean. The key is the name of a rule and the value is the
+expected value of that rule. If a rule is not specified, than any value is
+allowed.
+
+Example:
+
+```hcl
+test {
+ rules = {
+ main = true
+ days = []
+ }
+}
+```
+
+For the policy:
+
+```json sentinel
+{
+ "policy": "valid_days = rule {\n\tfilter days as d {\n\td is \"monday\"\n\t}\n}\n\nmain = rule { valid_days is empty }",
+ "globals": {
+ "days": []
+ }
+}
+```
+
+For more information, read about the [test command](/sentinel/commands/test).
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/configuration/remote-sources.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/configuration/remote-sources.mdx
new file mode 100644
index 0000000000..f92ca9720f
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/configuration/remote-sources.mdx
@@ -0,0 +1,165 @@
+---
+page_title: Remote Sources
+sidebar_current: docs-configuration-remote-sources
+description: >-
+ There are a number of options for formatting the URL provided to the
+ source attribute on policy and module blocks.
+layout: docs
+---
+
+# Remote Sources
+
+There are a number of options for formatting the URL provided to the
+`source` attribute on `policy` and `module` blocks. This flexibility
+should solve most use cases and allow for reusable policies and modules.
+
+### Supported Protocols
+
+-> **NOTE:** All protocols are supported on the CLI, however each production
+Sentinel integration may not. Be sure to read the appropriate integration
+documentation to check the supported protocols.
+
+- Local filesystem
+- Git
+- Mercurial
+- HTTP
+- Amazon S3
+- Google GCP
+
+### Forced Protocols
+
+Due to the ambiguous nature of URLs, it is possible to force a particular
+protocol to be used during the fetch. To achieve this, simply prefix the url
+with the appropriate protocol as listed below:
+
+- `file` - Local filesystem
+- `git` - Git
+- `hg` - Mercurial
+- `http` or `https` - HTTP
+- `s3` - Amazon S3
+- `gcp` - Google GCP
+
+Example:
+
+```
+git::http://github.com/hashicorp/sentinel.git
+```
+
+The above would download the provided HTTP URL using the Git protocol.
+
+### Protocol Options
+
+In addition to some global options, there are options available to specific
+protocols to perform features such as authentication.
+
+#### Subdirectories / File Pathing
+
+When supplying URLs that point to a directory (eg. `git`), a subdirectory and
+file can be provided by suffixing the URL with `//` and the path. For example,
+if we have a git repository that has a policy, `main.sentinel` in the root, we
+could directly fetch the file by using the following:
+
+```
+git::https://github.com/hashicorp/sentinel-docs-example.git//main.sentinel
+```
+
+Or, if the file was nested in the `policies` folder:
+
+```
+git::https://github.com/hashicorp/sentinel-docs-example.git//policies/main.sentinel
+```
+
+#### Checksumming
+
+For downloads of any protocol, you can automatically verify a checksum. To
+checksum a file, append a `checksum` query parameter to the URL. The parameter
+value can be in the format of type:value or just value, where type is "md5",
+"sha1", "sha256", "sha512" or "file" . The "value" should be the actual
+checksum value or download URL for "file". When type part is omitted, type will
+be guessed based on the length of the checksum string.
+
+```
+./foo.sentinel?checksum=md5:b7d96c89d09d9e204f5fedc4d5d55b21
+
+./foo.sentinel?checksum=b7d96c89d09d9e204f5fedc4d5d55b21
+
+./foo.sentinel?checksum=file:./foo.txt.sha256sum
+```
+
+#### Unarchiving
+
+An `archive` query parameter can be supplied to explicitly state what format
+to attempt to extract, if required. The supported formats are:
+
+- `tar.gz` and `tgz`
+- `tar.bz2` and `tbz2`
+- `tar.xz` and `txz`
+- `zip`
+- `gz`
+- `bz2`
+- `xz`
+
+If a URL has an extension that matches one of the supported formats, it will
+use that format to unarchive.
+
+```
+./file.zip
+```
+
+The above would use the `zip` format to unarchive.
+
+```
+./path/to/file?archive=zip
+```
+
+Would force the `zip` format. If for some reason you required archiving to be
+disabled, this can also be achieved by using the `false` value.
+
+```
+./path/to/file?archive=false
+```
+
+#### Git (`git`)
+
+- `ref` - The Git ref to checkout. This is a ref, so it can point to a commit
+ SHA, a branch name, etc. If it is a named ref such as a branch name, it will
+ be updated to the latest on each get.
+- `sshkey` - An SSH private key to use during clones. The provided key must be
+ a base64-encoded string. For example, to generate a suitable sshkey from a
+ private key file on disk, you would run base64 -w0 \.
+ **Note:** Git 2.3+ is required to use this feature.
+- `depth` - The Git clone depth. The provided number specifies the last `n`
+ revisions to clone from the repository.
+
+The git getter accepts both URL-style SSH addresses like
+`git::ssh://git@example.com/foo/bar`, and "scp-style" addresses like
+`git::git@example.com/foo/bar`. In the latter case, omitting the `git::` forced
+prefix is allowed if the username prefix is exactly git@.
+
+The "scp-style" addresses cannot be used in conjunction with the `ssh://`
+scheme prefix, because in that case the colon is used to mark an optional port
+number to connect on, rather than to delimit the path from the host.
+
+#### Mercurial (`hg`)
+
+- `rev` - The Mercurial revision to checkout.
+
+#### HTTP (`http` or `https`)
+
+Basic Authentication
+
+To use HTTP basic authentication, simply prepend `username:password@` to the
+hostname in the URL such as `https://Aladdin:OpenSesame@www.example.com/index.html`.
+All special characters, including the username and password, must be URL
+encoded.
+
+#### S3 (`s3`)
+
+The following query parameters are available and will take priority when
+authenticating against an S3 bucket.
+
+- `aws_access_key_id` - AWS access key.
+- `aws_access_key_secret` - AWS access key secret.
+- `aws_access_token` - AWS access token if this is being used.
+- `aws_profile` - Use this profile from local ~/.aws/ config. Takes priority
+ over the other three.
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/consul.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/consul.mdx
new file mode 100644
index 0000000000..7a3aaaa697
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/consul.mdx
@@ -0,0 +1,47 @@
+---
+page_title: Consul and Sentinel
+sidebar_current: docs-app-consul
+description: >-
+ Consul Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies are applied during writes to
+ the KV Store.
+layout: docs
+---
+
+# Consul
+
+[Consul Enterprise](https://www.hashicorp.com/products/consul/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies are applied during writes to the KV Store.
+
+Sentinel policies have access to the key/value being written. They can be used to
+allow or deny the modification. The information that Sentinel policies have access
+to will expand over time.
+
+The Consul integration with Sentinel is documented in depth in the
+[Consul Enterprise documentation](https://www.consul.io/docs/guides/sentinel.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Input validation depending on the name of the key.**
+
+```sentinel
+main = rule { valid_key() }
+
+required = [
+ ["port", "\\d+"], # ports must be integers
+ ["name", "\\w+"], # name must be a word
+]
+
+valid_key = func() {
+ for required as v {
+ if key is v[0] {
+ return value matches v[1]
+ }
+ }
+
+ return false
+}
+```
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/extending/index.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/extending/index.mdx
new file mode 100644
index 0000000000..f3843470f8
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/extending/index.mdx
@@ -0,0 +1,44 @@
+---
+page_title: Extending Sentinel
+sidebar_current: docs-extending
+description: Learn how to create your own Sentinel imports to extend Sentinel's functionality.
+layout: docs
+---
+
+# Extending Sentinel
+
+-> **NOTE:** Sentinel's extensibility is currently undergoing large
+improvements - watch this space for future updates!
+
+Sentinel can be extended by writing additional
+[imports](/sentinel/language/imports). These imports can bring new functionality
+to Sentinel by allowing access to new data or by the addition of new functions.
+This section is designed to show you how to accomplish this extensibility and
+describe how it works.
+
+Currently, the only usable way to add additional imports is via
+[modules](#modules). While [plugins](#plugins) currently work under the Sentinel
+CLI, no Sentinel integration currently supports their use.
+
+-> You may also be interested in advanced details on [import
+internals](/sentinel/extending/internals).
+
+## Modules
+
+Modules allow you to re-use Sentinel code as an import. Modules are loaded
+external of policies and mapped to imports. This is usually configured via a
+file such as the [CLI configuration file](/sentinel/configuration).
+
+See the [modules](/sentinel/extending/modules) section for more details.
+
+## Plugins
+
+-> **NOTE:** Plugins are currently only supported on the CLI and not in any
+production Sentinel integration, with the exception of existing configuration
+in [Nomad](https://www.nomadproject.io/docs/configuration/sentinel).
+
+Imports can also be implemented as plugins when Sentinel code itself is too
+restrictive to represent the import, or if you need access to features not
+normally available to the Sentinel runtime.
+
+See the [plugins](/sentinel/extending/plugins) section for more details.
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/extending/internals.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/extending/internals.mdx
new file mode 100644
index 0000000000..11be9d3d67
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/extending/internals.mdx
@@ -0,0 +1,252 @@
+---
+page_title: >-
+ Extending Sentinel: Internals
+sidebar_current: docs-extending-internals
+description: This Section covers some details about Sentinel's import internals.
+layout: docs
+---
+
+# Extending Sentinel: Internals
+
+-> **Advanced Topic!** This page covers low-level technical details of Sentinel.
+You don't need to understand these details to effectively use Sentinel. The
+details are documented here for those who wish to learn about them.
+
+This section covers some of the internal details of Sentinel's import system.
+The goal of this section is to help remove notion of "magic" from Sentinel, to
+allow you to trust and understand what Sentinel is doing with imports, and also
+to help practitioners and developers alike understand the differences between
+the ways that imports can be loaded into Sentinel.
+
+## Language and Runtime
+
+Understanding the import system in Sentinel generally requires understanding of
+two key concepts within Sentinel: the _language_, and the _runtime
+implementation_ of the language.
+
+The Sentinel language and the rules governing it are detailed in the [Sentinel
+Language Specification](/sentinel/language/spec). A runtime implementation must
+conform to the rules laid out in the specification at a minimum. However, to
+meet the design goals of a particular implementation, the runtime may implement
+features on top of the language. The subtle implementation details within the
+runtime may also vary while still being compliant with the specification.
+
+The core runtime included within the [Sentinel CLI](/sentinel/commands) is
+sometimes referred to as the _reference implementation_ of the Sentinel
+language. This runtime is also the main runtime embedded within each
+Sentinel-enabled HashiCorp product such as Terraform Cloud and Enterprise, Vault
+Enterprise, Nomad Enterprise, and Consul Enterprise. The runtime includes an API
+to allow these integrations implement Sentinel in as robust or as restrictive of
+a way as possible, so depending on the implementation, your experience may vary.
+
+## Sentinel's Import Model
+
+The concept of Imports within Sentinel is a _language_ feature. You can see the
+exact rules governing imports within the
+[Imports](/sentinel/language/spec#imports) section of the specification.
+
+Within the runtime, there are currently two major classifications of imports:
+
+- **Modules:** The mapping of Sentinel code to an import, essentially mapping a
+ scope of values to a particular package.
+- **Binary Imports:** A grouping of embedded and external plugins written using
+ the Sentinel SDK. These are comprised of internal or embedded imports (usually
+ the [standard imports](/sentinel/imports) and imports included by an
+ integration) and import plugins.
+
+Below is a diagram explaining in brief the relationship between the langauge
+and runtime features.
+
+
+
+This kind of topology ultimately minimizes the visibility of implementation
+internals to the actual policy language. This allows us to keep the Sentinel
+language itself simple and keep concerns such as module or plugin management,
+validation, and configuration out of the language. These kinds of things would
+impact the readability of a policy, possibly present security challenges, and
+would ultimately be of no use to more minimal embedded applications.
+
+### Implementation Differences Between Import Types
+
+As one would imagine, both modules and binary imports are loaded in much
+different ways, and the methods that they expose data to Sentinel differ as
+well. The effect is generally the same however across both, and we take care to
+ensure that both modules and binary imports behave as close as possible to each
+other, however there are some subtle differences:
+
+- Modules currently support the exporting of rules, but do not support function
+ calls with object receiver data (as seen in some standard imports such as
+ [`decimal`](/sentinel/imports/decimal), [`http`](/sentinel/imports/http), and
+ [`time`](/sentinel/imports/time)). This will be coming in a later release.
+- Binary imports cannot export rules. However, they do support object receiver
+ data (see [`framework.New`](/sentinel/extending/plugins#framework-new) in
+ [Extending Sentinel: Plugins](/sentinel/extending/plugins)).
+
+## Modules
+
+_Modules_ are essentially Sentinel code that is intended to be re-used in multiple
+policies as an import.
+
+The mechanism of module loading is fairly straightforward: during initialization
+of the Sentinel runtime, modules are parsed along with policies. The parsed
+_abstract syntax tree_ (AST) for each module is loaded into the runtime under
+the specified _import path_. These are then passed to the lower-level
+interpreter during the evaluation of each policy.
+
+As with policy code, during evaluation, a module's AST is walked to execute the
+code within that specific module. The module data is then stored within an
+specific scope mapped to the import name. The module data can then be accessed
+through the import via selector expressions and function calls (e.g.,
+`foo.some_map` or `foo.some_func()` for a module loaded via `import "foo"`).
+
+The AST for a module is only walked if a policy imports it, and it is _only
+walked once_. That means if a policy and a module import the same module, or a
+policy imports the same module twice (using [import
+aliases](/sentinel/language/imports#aliases)), those instances will share state.
+If you call a function that alters a singleton variable within the module, that
+singleton will be modified for all instances of the import.
+
+-> **Fun fact!** When you [mock an import with Sentinel
+code](/sentinel/configuration#mocking-with-sentinel-code), you are actually
+using a module. The module is loaded with a flag that allows it to override an
+existing import, which would normally give a runtime error.
+
+### Map and List Cloning for Module Calls
+
+It's also worthwhile noting that map and list values are cloned for modules.
+
+Consider the case where you have a module with a map singleton.
+
+```sentinel
+// modules/foo.sentinel
+a_map = {"a": "b"}
+```
+
+Normally, assignment to the singleton, even when using an index expression, is
+blocked, so `foo.a_map["c"] = "d"` would not pass semantic checking.
+
+However, even when you try to do assignment via an intermediary, you will still
+be only modifying the copy, and the original will not be affected:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+a_map_copy = foo.a_map
+a_map_copy["c"] = "d" // Only modifies copy, does not modify module singleton
+print(foo.a_map) // Still only prints {"a": "b"}
+```
+
+This is to ensure that modules are consistent with binary imports in how return
+data is handled, and the expectation that most non-object data within an import
+is read-only, with the exception of modification of singleton data via
+functions. As return of complex object and collection data in a binary import is
+always via copy, it's not possible to modify any value elements within the
+import in a similar fashion.
+
+There are also some other implications of this cloning that are of note:
+
+- Rules are not cloned, either within a list or map, or by themselves. This is
+ to ensure that [memoization](/sentinel/language/rules#lazy-and-memoized)
+ functions correctly. As only a module can export rules at this time, there is no
+ parity for this in binary imports.
+- Functions are _not_ cloned. This mainly has implications for scope - for
+ example, if you assign a function to another value, or assign an object to a
+ value that has a method that utilizes a global module variable, those calls will
+ reference and/or modify those values.
+
+Functions, while represented differently in binary imports, generally follow the
+same logic here - as they would be invoked in the same package within the binary
+import, any singleton values they accessed there would be affected in a similar
+fashion. As rules are pseudo-functions, this generally makes sense here too.
+
+## Binary Imports
+
+_Binary imports_ is a grouping referring to all imports that are designed with
+the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). These can be
+either internally embedded, or executed via a plugin.
+
+All imports found in the [standard library](/sentinel/imports) are binary
+imports, embedded internally.
+
+The main difference between internally embedded imports and external plugins is
+whether or not the runtime needs to execute a plugin binary as part of
+[lifecycle management](#lifecycle), and whether or not it needs to
+[communicate](#communication) over RPC. Internal binary imports do not require
+these steps, so their execution model is somewhat simpler; however, technically,
+they still are the same as external plugins in terms of design model and value
+conversion. Most standard imports are even tested using the [SDK test
+suite](/sentinel/extending/plugins#testing), which builds the import as an
+external plugin.
+
+The remainder of this section discusses topics mostly relevant to external
+plugins. A large amount of technical detail regarding binary import development
+(also applicable to embedded binary imports) can be seen on the
+[Plugins](/sentinel/extending/plugins) page.
+
+-> **NOTE:** At this time, plugins can only be written for the Sentinel CLI, and
+cannot be used with any of Sentinel's integrations.
+
+### Plugin Basics
+
+Sentinel plugins are built on top of the HashiCorp [go-plugin
+system](https://github.com/hashicorp/go-plugin). This is the same plugin system
+powering all pluggable HashiCorp tools such as
+[Terraform](https://www.terraform.io), [Vault](https://www.vaultproject.io), and
+more. go-plugin is a system that has been used in production for millions of
+users for over 5 years.
+
+Plugins are executable binaries. Sentinel is responsible for launching and
+managing the lifecycle of plugins. Once a plugin is running, Sentinel
+communicates with the plugin via [gRPC](https://grpc.io/). The [protocol is open
+source](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/import.proto)
+to allow anyone to write a Sentinel plugin.
+
+### Lifecycle
+
+Sentinel launches plugins and is responsible for plugin lifecycle.
+
+The runtime optimizes for latency by launching the plugin as soon as it is
+configured, rather than when a policy requires it. This ensures that the plugin
+is ready to be used immediately. A plugin is only closed when Sentinel is
+reconfigured to no longer allow that plugin or if the Sentinel-enabled
+application is closing.
+
+When a plugin is removed from the configuration, Sentinel may wait to shut down
+the plugin until all currently executing policies that are using that plugin
+complete. New policy executions will not be allowed to use the old plugins.
+
+Plugins are automatically restarted if they shut down unexpectedly. Policies
+that were executing while this happens may fail. The Sentinel system is
+improving to more gracefully understand locations where it is safe to retry an
+execution.
+
+### Communication
+
+An [initial
+handshake](https://github.com/hashicorp/go-plugin/blob/master/docs/internals.md)
+is done via stdout to determine the main communication location. Following the
+handshake, communication will either occur on a local Unix domain socket or via
+a local-only TCP socket. This communication is done mostly over the low-level
+[`Get`](https://github.com/hashicorp/sentinel-sdk/blob/2b4db07dd12b55142f0c016f301af80aa4422520/proto/import.proto#L12)
+RPC call.
+
+#### Value Conversion
+
+As can generally be seen in [Developing an Import
+Plugin](/sentinel/extending/plugins#developing-an-import-plugin), the Sentinel
+type system is not exposed to binary imports. While explicit values for null and
+undefined exist, values are mostly converted over the wire from their Go values
+to their Sentinel equivalents.
+
+This has a few implications:
+
+- As discussed in [Map and List Cloning for Module
+ Calls](#map-and-list-cloning-for-module-calls), Map and list data returned from
+ binary import value lookups or function calls is always cloned. These can be
+ freely modified without affecting anything within the binary import.
+- It's currently impossible to represent certain Sentinel types such as
+ [rules](/sentinel/language/rules) within a binary import. This is not a major
+ issue at this point in time as practitioners generally do not look to binary
+ imports for rules; we may examine this as a later time if this situation
+ changes.
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/extending/modules.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/extending/modules.mdx
new file mode 100644
index 0000000000..c49e0c741a
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/extending/modules.mdx
@@ -0,0 +1,136 @@
+---
+page_title: >-
+ Extending Sentinel: Modules
+sidebar_current: docs-extending-modules
+description: >-
+ Modules allow you to re-use Sentinel code as an import.
+layout: docs
+---
+
+# Extending Sentinel: Modules
+
+Modules allow you to re-use Sentinel code as an import. This allows for the
+export of any general construct that Sentinel supports, including values,
+functions, and even rules. As such, it's a great way to package code that is
+useful across multiple policies, reducing boilerplate and making final policy
+code simpler.
+
+You may want to use modules for:
+
+- **Writing a simple re-usable helper library.** If you mostly know Sentinel or
+ have helpers that you have written in Sentinel, moving these helpers to a module
+ will offer a low-friction way of facilitating their re-use.
+- **Writing re-usable rules.** If you have a set of
+ [rules](/sentinel/language/rules) you know you want to use across multiple
+ policies, bundling them into a module is a great way of abstracting the logic
+ away.
+- **Abstracting existing Sentinel imports.** Using a module is a great way to
+ extend existing Sentinel imports by abstracting the common functionality that
+ you use within an import to your own workflow.
+
+This page describes how you can use modules within the context of the Sentinel
+CLI. For instructions for a particular integration, see the documentation for
+that product.
+
+-> **Not all Sentinel-enabled applications support modules.** Please refer
+to the documentation of the specific Sentinel-enabled application. Some
+applications disable modules for security reasons.
+
+## Configuring a Module
+
+A module is configured within the Sentinel CLI by adding an entry for it in
+within the `modules` section of the [CLI configuration
+file](/sentinel/configuration). The key for each entry specifies the path that
+the module is imported as. The `path` value within each entry specifies the path
+to the module file on the local filesystem.
+
+```hcl
+module "foo" {
+ source = "modules/foo.sentinel"
+}
+
+module "bar" {
+ source = "modules/bar.sentinel"
+}
+```
+
+In this example, we have two modules:
+
+- Import path `foo`, being loaded from the code within `modules/foo.sentinel`.
+- Import path `bar`, being loaded from the code within `modules/bar.sentinel`.
+
+See the [modules](/sentinel/configuration#modules) section within the CLI
+configuration file syntax page for more details.
+
+### Remote Modules
+
+In addition to loading from a local source, modules can be loaded from a remote
+location. This can increase reusability and allow for sharing modules across
+multiple configurations.
+
+### Testing Using Modules
+
+As with [mocks](/sentinel/configuration#mocking-with-sentinel-code), modules
+are loaded relative to the configuration file location when using `sentinel test`. As such, you need to account for this in your test configuration files:
+
+```hcl
+module "foo" {
+ source = "../../modules/foo.sentinel"
+}
+
+module "bar" {
+ source = "../../modules/bar.sentinel"
+}
+```
+
+This could be a test file for a policy (example: `policy.sentinel`), residing
+under the correct test file directory for this policy (example:
+`test/policy/pass.json`).
+
+## Writing and Using a Module
+
+Writing a module is nearly the same as writing a Sentinel policy, except for the
+following two exceptions:
+
+- Modules do not require [`main`](/sentinel/writing/basic#the-simplest-policy)
+ to be present. It can be, but it will not carry any extra significance as it
+ does within a policy.
+- [`param`](/sentinel/language/parameters) declarations cannot be present in a module. If they are encountered,
+ a runtime error is given.
+
+Once you have a complete module ready for use and configured, using the module
+is as simple as importing it.
+
+Building on the above [configuration example](#configuring-a-module), we can
+export a simple test function in the module `foo` by adding this code to
+`modules/foo.sentinel`:
+
+```sentinel
+// modules/foo.sentinel
+hello = func() {
+ print("hello world!")
+ return undefined
+}
+```
+
+We can then import it within our policy and use it:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+// prints "hello world" in the trace
+foo.hello()
+
+main = true
+```
+
+Note that modules are considered [imports](/sentinel/language/spec#imports) by
+the Sentinel runtime and as such conform to the specification. This means that
+you cannot directly assign values to anything behind a module scope. You can,
+however, use functions to change state.
+
+-> **NOTE:** At this time, modules do not support object receiver data in the
+way that some imports do, like [`time`](/sentinel/imports/time),
+[`decimal`](/sentinel/imports/decimal), and [`http`](/sentinel/imports/http).
+Support for this will be coming in later versions of Sentinel.
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/extending/plugins.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/extending/plugins.mdx
new file mode 100644
index 0000000000..c96f4c2c07
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/extending/plugins.mdx
@@ -0,0 +1,521 @@
+---
+page_title: >-
+ Extending Sentinel: Plugins
+sidebar_current: docs-extending-plugins
+description: >-
+ Plugins allow you to write imports as standalone executables, allowing you to
+ extend Sentinel in ways not normally possible with policy code alone.
+layout: docs
+---
+
+# Extending Sentinel: Plugins
+
+-> **NOTE:** Plugins are currently only supported on the CLI and not in any
+production Sentinel integration, with the exception of existing configuration
+in [Nomad](https://www.nomadproject.io/docs/configuration/sentinel).
+
+Plugins allow you to write imports as standalone executables, allowing you to
+extend Sentinel in ways not normally possible with policy code alone.
+
+You might want to write or use a plugin when you need to:
+
+- **Use a provider or API integration for Sentinel.** In this case, you will
+ probably be working with a provider SDK, or other libraries that will be making
+ complex network requests to external services. Sentinel only provides limited
+ capabilities for these kinds of operations in the standard library, so writing
+ this kind of import as a module is not optimal.
+- **Introduce novel functionality not currently supported by Sentinel.**
+ Sentinel's policy language is limited by design to encourage simple policies,
+ reduce its runtime footprint, and to keep it efficient and safe. This will
+ mean that some functionality may be difficult or impossible to express as
+ Sentinel code, and would require a plugin to write.
+- **Extend a complex module.** Additionally, modules are restricted to a single
+ file of Sentinel code, so these will naturally become more and more unwieldy as
+ you continue to add to them. Eventually, there might be a time where a plugin is
+ better suited for the functionality, especially if it's a general-purpose
+ library.
+
+This section details how import plugins are currently configured in the Sentinel
+CLI, and some detail on how they are written. As we continue to build on the
+ability to use import plugins, we will extend this section with more details.
+
+-> **Not all Sentinel-enabled applications will support plugins.** Some
+applications will disable plugins for security reasons. For those that do enable
+plugins, the method to configure plugins will vary.
+
+## Installing Plugins
+
+Sentinel plugins are standalone executables. Sentinel-enabled applications such
+as the CLI launch these plugins and communicate with them via
+[RPC](https://en.wikipedia.org/wiki/Remote_procedure_call). To install a plugin,
+the first step is to download the plugin executable.
+
+After downloading the plugin, you must add it in the CLI configuration file,
+setting its name, path, and any arguments, environment variables, or
+configuration. An example is below:
+
+```json
+{
+ "imports": {
+ "time": {
+ "path": "/path/to/sentinel-import-time",
+ "config": { "fixed_time": 1504155600 }
+ }
+ }
+}
+```
+
+After configuring the plugin, it will be available on the next `sentinel apply`
+or `sentinel test`.
+
+For more details on configuration, see the
+[plugins](/sentinel/configuration#plugins) section of the [CLI configuration
+file syntax](/sentinel/configuration).
+
+## Debugging Plugins
+
+The Sentinel CLI logs when it launches, configures, and closes a plugin. The
+plugin may also log additional information when it is running.
+
+To debug issues with a plugin, you can usually refer to these logs. Depending on
+the plugin configuration, the logs you see may vary - refer to the plugin
+documentation on how to enable logs for a particular plugin.
+
+## Developing an Import Plugin
+
+Anyone can develop a Sentinel import plugin using the [Sentinel
+SDK](https://github.com/hashicorp/sentinel-sdk). The SDK contains a high-level
+framework for writing plugins in [Go](https://golang.org) including a test
+framework. Additionally, the SDK contains the low-level [gRPC protocol buffers
+definition](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/import.proto)
+for writing plugins in other languages.
+
+The rest of this page will document how to write a new Sentinel import in Go
+using the Sentinel SDK and the high-level framework provided. You are expected
+to already know the basics of Go and have a properly configured Go installation.
+
+Throughout this page, we'll be developing a simple import to access time values.
+
+-> **NOTE:** The example here is not reflective of the actual implementation of
+the [`time`](/sentinel/imports/time) standard import, so make sure to not
+get the two confused.
+
+### Import Framework
+
+The Sentinel SDK provides a high-level
+[framework](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework)
+for writing imports. We recommend using this framework.
+
+#### Basic Concepts
+
+This framework works by implementing the correct Go interfaces.
+
+As a general overview:
+[`framework.Import`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Import)
+implements the
+[`sdk.Import`](https://godoc.org/github.com/hashicorp/sentinel-sdk#Import)
+interface and therefore is a valid import plugin. You must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface to configure the import. The root may then delegate nested access to
+various
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+implementations. Other interfaces are implemented to provide functionality past
+what is provided by the basic `framework.Namespace` implementation - these are
+documented below.
+
+This all may sound like a lot of interfaces, but each interface typically only
+requires a single function implementation and you're only required to implement
+a single namespace (`framework.Namespace`).
+
+As we move on, we'll use real examples to make this clear.
+
+#### Implementing framework.Root
+
+To begin, you must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface. This is the interface representing the root of your import. The root
+itself must be implement
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace),
+which allows values to be accessed. Note that the root can optionally implement
+other interfaces, but they're more advanced and omitted here for simplicity.
+
+For our time example, our root may look like this:
+
+```sentinel
+type root struct {
+ time time.Time
+}
+
+// framework.Root impl.
+func (m *root) Configure(raw map[string]interface{}) error {
+ if _, ok := raw["timestamp"]; !ok {
+ raw["timestamp"] = time.Now().Unix()
+ }
+
+ v := raw["timestamp"]
+ timestamp, ok := v.(int)
+ if !ok {
+ return fmt.Errorf("invalid timestamp type %T", v)
+ }
+
+ m.time = time.Unix(timestamp, 0).UTC()
+ return nil
+}
+
+// framework.Namespace impl.
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "minute":
+ return m.time.Minute(), nil
+ }
+
+ return nil, nil
+}
+```
+
+This example implements `framework.Root` and `framework.Namespace` and would
+enable the following within a Sentinel policy once the completed import is
+installed.
+
+```sentinel
+import "time"
+
+print(time.minute)
+main = true
+```
+
+#### framework.Namespace
+
+The
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+interface implements a namespace of values. You saw above that
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+must itself be a namespace. However, a namespace `Get` implementation may
+further return namespaces to access nested values.
+
+This enables behavior such as `time.month.string` vs. `time.month.index`.
+Notice each of these examples accesses a nested value within `month`. This can
+be modeled as namespaces within the framework.
+
+In the example below, we return a new namespace for `month` to do just this:
+
+```sentinel
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "month":
+ return &namespaceMonth{Month: m.time.Month()}, nil
+ }
+
+ // ...
+}
+
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil nil
+}
+```
+
+#### Primitive Types and Structs
+
+A namespace may also return any primitive Go type as well as structs. The
+framework automatically exposes primitive values as you would expect. For
+structs, exported fields are lowercased and exposed to the policies. The
+`sentinel` struct tag can be used to control how Sentinel can access the field.
+
+In the example below, we expose a struct directly from the root namespace:
+
+```sentinel
+type Location struct {
+ Name string
+ TimeZone string `sentinel:"time_zone"`
+}
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "location":
+ return &Location{Name: "somewhere", TimeZone: "some zone"}, nil
+ }
+
+ // ...
+}
+```
+
+This makes the following values work within a Sentinel policy:
+`time.location.name`, `time.location.time_zone`.
+
+If a field has an empty `sentinel` struct tag (example: `sentinel:""`),
+then that field will not be accessible from a policy.
+
+#### Optional Interfaces
+
+The following are optional interfaces that can be implemented on top of
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace).
+All of these interfaces can be implemented at any level, except for
+[`framework.New`](#framework-new), which only works at the root level.
+
+##### `framework.Call`
+
+The
+[`framework.Call`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Call)
+interface can be implemented to support function calls.
+
+Implementing this interface is very similar to attribute access, except instead
+of returning the attribute value, you return a function. The framework uses Go
+reflection to determine the argument and result types and calls it. If the
+argument types do not match or the signature is otherwise invalid, an error is
+returned.
+
+For example, let's implement a function to add months to the current month:
+
+```sentinel
+func (m *root) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *root) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.time.AddDate(0, n, 0).Month()}
+}
+```
+
+You can now call the function. If today was in the month of September,
+the policy below would pass:
+
+```sentinel
+import "time"
+
+main = time.add_month(4).string == "January"
+```
+
+##### `framework.Map`
+
+The optional
+[`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+interface allows an alternative method to to present a namespace back to a
+Sentinel policy as a map.
+
+`framework.Map` is best paired with
+[`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys),
+which allows you to quickly fetch the necessary data via `Get` calls on the
+namespace:
+
+```
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil, nil
+}
+
+func (m *namespaceMonth) Map() (map[string]interface{}, error) {
+ return framework.MapFromKeys(m, []string{"string", "index"})
+}
+```
+
+##### `framework.New`
+
+The optional
+[`framework.New`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#New)
+interface can be implemented on a root namespace to add _methods_ to your
+namespaces.
+
+Data returned from a namespace is memoized and returned as a map, and is
+normally not callable - to call a function to operate on the data, you would
+need to create a new namespace from the top-level of the import, and then make a
+function call on the result within the same expression.
+
+Let's consider the [root example](#implementing-framework-root). Let's say,
+instead of hard-coding the time value at configuration, we wanted to load it
+on-demand using a `time.now` key, and allow `add_month` to be callable on that
+value. Our root and de-coupled time namespace would now look like:
+
+```
+type root struct{}
+
+func (m *root) Configure(raw map[string]interface{}) error { return nil }
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "now":
+ return &namespaceTime{Time: time.Now()}
+ }
+
+ return nil
+}
+
+func (m *root) New(data map[string]interface{}) (framework.Namespace, error) {
+ if v, ok := data["unix"]; ok {
+ if t, ok := v.(int64); !ok {
+ return &namespaceTime{Time: time.Unix(t, 0)}
+ }
+
+ return nil, fmt.Errorf("expected timestamp to be int64, got %T", v)
+ }
+
+ return nil, nil
+}
+
+type namespaceTime struct {
+ Time time.Time
+}
+
+func (m *namespaceTime) Get(key string) interface{} {
+ switch key {
+ case "unix":
+ return m.Time.Unix()
+
+ // case ...
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) Get(key string) (interface{}, error) {
+ return framework.MapFromKeys(m, []string{"unix", "..."})
+}
+
+func (m *namespaceTime) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.Time.AddDate(0, n, 0).Month()}
+}
+```
+
+Via this example, we can now create and assign a `namespaceTime` using `t = time.now`, and then call `t.add_month(months_to_add)` to return a
+`namespaceMonth` for the corresponding month.
+
+Methods on a namespace can also _modfiy_ the receiver data. So you can, for
+example, add a method named `increment_month` that takes no arguments, but
+increments the time stored in `namespaceTime.Time` by a month. Subsequent
+statements using the receiver would see the new value.
+
+Note that there are some restrictions and guidelines that you should take into
+account when using `framework.New`:
+
+- Only data that makes it back to a policy can be used as receiver data to
+ instantiate new namespaces. As such it's recommended to make use of
+ [`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+ and
+ [`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys)
+ to return all of the attribute data you need to construct new objects.
+- `framework.New` is only supported on the root namespace and has no effect on
+ namespaces below the root. Check all possible cases of receiver data within
+ the top-level `New` function and return the appropriate namespace based on the
+ input data.
+- Do not return general errors from your `New` method. When an unknown lookup is
+ made off of memoized data, it will hit your `New` method for possible
+ instantiation and key calls. This will ensure `undefined` is correctly
+ returned for these cases.
+- `framework.New` is designed to add utility to imports where calling methods on
+ a value is more intuitive than just making a function call, or just returning
+ all of a data set as a memoized map. Use it sparingly and avoid using it on
+ recursively complex data sets.
+
+### Testing
+
+The SDK exposes a testing framework to verify your plugin works as
+expected. This test framework integrates directly into `go test` so it
+is part of a familiar workflow.
+
+The test framework works by dynamically building your import and
+running it against the `sentinel` CLI to verify it behaves as expected.
+This ensures that your plugin builds, your plugin communicates via RPC
+correctly, and that the behavior within a policy is also correct.
+
+The example below shows a complete ready table-driven test for our time plugin.
+You can run this with a normal `go test`.
+
+```sentinel
+package main
+
+import (
+ "os"
+ "testing"
+
+ "github.com/hashicorp/sentinel-sdk"
+ plugintesting "github.com/hashicorp/sentinel-sdk/testing"
+)
+
+func TestMain(m *testing.M) {
+ exitCode := m.Run()
+ plugintesting.Clean()
+ os.Exit(exitCode)
+}
+
+func TestImport(t *testing.T) {
+ cases := []struct {
+ Name string
+ Data map[string]interface{}
+ Source string
+ }{
+ {
+ "month",
+ map[string]interface{}{"timestamp": 1495483674},
+ `main = subject.month.string == "September"`,
+ },
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.Name, func(t *testing.T) {
+ plugintesting.TestImport(t, plugintesting.TestImportCase{
+ Config: tc.Data,
+ Source: tc.Source,
+ })
+ })
+ }
+}
+```
+
+### Building Your Import
+
+To build your import, you must first implement the `main` function.
+The main function should just use the `rpc.Serve` method to serve the
+plugin over the Sentinel RPC layer:
+
+```sentinel
+package main
+
+import (
+ "github.com/hashicorp/sentinel-sdk"
+ "github.com/hashicorp/sentinel-sdk/rpc"
+)
+
+func main() {
+ rpc.Serve(&rpc.ServeOpts{
+ ImportFunc: func() sdk.Import {
+ return &framework.Import{Root: &root{}}
+ },
+ })
+}
+```
+
+You can then build this using `go build`. The output can be named
+anything. Once your plugin is built, [install it](#installing-plugins)
+and try it out!
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/functions/append.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/functions/append.mdx
new file mode 100644
index 0000000000..81395154f6
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/functions/append.mdx
@@ -0,0 +1,46 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Append'
+sidebar_current: docs-funcs-append
+description: The built-in function `append` appends a value to the end of a list.
+layout: docs
+---
+
+# Builtin Function: append
+
+The built-in function `append` appends a value to the end of a list. The list
+is modified in-place. The return value will always be [undefined](/sentinel/language/undefined).
+
+`append` called on any value other than a list or undefined will result
+in an immediate error.
+
+Examples:
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+### Why does this function return undefined?
+
+This function returns `undefined` to avoid unintended side effects of the function in the context
+of a [rule](/sentinel/language/rules).
+
+For example, if `append` returned the list value as a result, the following policy would depend
+on the order in which rules are referenced:
+
+```sentinel
+list = []
+a = rule { append(list, 1) contains 1 }
+b = rule { append(list, 2) contains 2 }
+
+// Scenario 1: list becomes [1, 2]
+main = rule { a and b }
+
+// Scenario 2: list becomes [2, 1]
+main = rule { b and a }
+```
+
+This sort of side effect behavior introduces complex and difficult to track logical errors in programs.
+As a result, functions like this return `undefined` and modify values in-place.
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/functions/delete.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/functions/delete.mdx
new file mode 100644
index 0000000000..3b60803997
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/functions/delete.mdx
@@ -0,0 +1,25 @@
+---
+page_title: 'Sentinel Language - Builtin Function: delete'
+sidebar_current: docs-funcs-delete
+description: The built-in function `delete` deletes an element from a map.
+layout: docs
+---
+
+# Builtin Function: delete
+
+The built-in function `delete` deletes an element from a map.
+
+If the element to delete does not exist, the map is left unchanged.
+Calling `delete` on a value that is not a map or
+[undefined](/sentinel/language/undefined)
+is an immediate error. The result of `delete` is always `undefined`.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/functions/error.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/functions/error.mdx
new file mode 100644
index 0000000000..96d253b662
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/functions/error.mdx
@@ -0,0 +1,15 @@
+---
+page_title: 'Sentinel Language - Builtin Function: error'
+sidebar_current: docs-funcs-error
+description: >-
+ The built-in function `error` is used to exit immediately with an error
+ message.
+layout: docs
+---
+
+# Builtin Function: error
+
+The built-in function `error` is used to exit immediately with an error message.
+
+Please see the [page on errors](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/functions/index.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/functions/index.mdx
new file mode 100644
index 0000000000..4e12143ad0
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/functions/index.mdx
@@ -0,0 +1,13 @@
+---
+page_title: Sentinel Language - Builtin Functions
+sidebar_current: docs-funcs
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Language: Builtin Functions
+
+Builtin functions are
+[functions](/sentinel/language/functions)
+that are available in all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in function.
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/functions/keys.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/functions/keys.mdx
new file mode 100644
index 0000000000..a96cafe897
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/functions/keys.mdx
@@ -0,0 +1,22 @@
+---
+page_title: 'Sentinel Language - Builtin Function: keys'
+sidebar_current: docs-funcs-keys
+description: The built-in function `keys` returns the keys of a map.
+layout: docs
+---
+
+# Builtin Function: keys
+
+The built-in function `keys` returns the keys of a map.
+
+`keys` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned keys are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+```
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/functions/length.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/functions/length.mdx
new file mode 100644
index 0000000000..8b874b14c4
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/functions/length.mdx
@@ -0,0 +1,23 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Length'
+sidebar_current: docs-funcs-length
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Builtin Function: length
+
+The built-in function `length` returns the length of a collection or string.
+
+The length of a string is the number of bytes in the string. It is not
+necessarilly the number of characters. The length of a collection is the
+number of elements in that collection. The length of
+[undefined](/sentinel/language/undefined) is `undefined`.
+
+Examples:
+
+```sentinel
+length("foo") // 3
+length([1, 2, 3, 4]) // 4
+length({ "key": "value" }) // 1
+```
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/functions/print.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/functions/print.mdx
new file mode 100644
index 0000000000..bcf441d09d
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/functions/print.mdx
@@ -0,0 +1,13 @@
+---
+page_title: 'Sentinel Language - Builtin Function: print'
+sidebar_current: docs-funcs-print
+description: The built-in function `print` is used for logging and debugging.
+layout: docs
+---
+
+# Builtin Function: print
+
+The built-in function `print` is used for logging and debugging.
+
+Please see the [page on logging](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/functions/range.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/functions/range.mdx
new file mode 100644
index 0000000000..0f86bfc402
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/functions/range.mdx
@@ -0,0 +1,49 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Range'
+sidebar_current: docs-funcs-range
+description: The built-in function `range` returns a list of numbers in a range.
+layout: docs
+---
+
+# Builtin Function: range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it
+defaults to `1`.
+
+Negative steps are permitted and count down from `start` towards `end`, instead
+of up.
+
+The range ends when the next value given by the sum of the last value and `step`
+would exceed `end`, regardless of if it has been reached.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4] - starts at 1 instead of 0
+range(1, 5, 2) // [1,3] - 3+2 does not satisfy r[-1] < end
+range(0, -3, -1) // [0,-1,-2] - example of negative range
+```
+
+Impossible ranges, or ranges where `start` will never reach `end` given `step`,
+yield an empty list.
+
+```
+range(1, 1) // [] - already starting at 1
+range(0, 1, -1) // [] - negative step will never reach 1
+```
+
+Supplying an undefined value to any argument of `range` yields an undefined
+value back.
+
+A range with a zero step is a runtime error.
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/functions/values.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/functions/values.mdx
new file mode 100644
index 0000000000..8f98d3ab56
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/functions/values.mdx
@@ -0,0 +1,22 @@
+---
+page_title: 'Sentinel Language - Builtin Function: values'
+sidebar_current: docs-funcs-values
+description: The built-in function `values` returns the values of a map.
+layout: docs
+---
+
+# Builtin Function: values
+
+The built-in function `values` returns the values of a map.
+
+`values` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned values are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+values(data) // [3, 2]
+```
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/imports/base64.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/imports/base64.mdx
new file mode 100644
index 0000000000..4e58429268
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/imports/base64.mdx
@@ -0,0 +1,46 @@
+---
+page_title: 'Import: base64'
+sidebar_current: docs-imports-base64
+description: The base64 import enables a Sentinel policy to encode and decode Base64 values.
+layout: docs
+---
+
+# Import: base64
+
+The base64 import enables a Sentinel policy to encode and decode Base64 values.
+
+### base64.encode(str)
+
+Encodes the string `str` into a Base64 encoded string, using the standard
+encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-4).
+
+```sentinel
+enc = base64.encode("foo") // "Zm9v"
+```
+
+### base64.decode(str)
+
+Decodes the Base64 encoded string `str`, returning the string result,
+using the standard encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-4).
+
+```sentinel
+dec = base64.decode("Zm9v") // "foo"
+```
+
+### base64.urlencode(str)
+
+Encodes the string `str` into a Base64 encoded string, using the alternate
+encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-5).
+
+```sentinel
+enc = base64.urlencode("foo") // "Zm9v"
+```
+
+### base64.urldecode(str)
+
+Decodes the Base64 encoded string `str`, returning the string result, using the
+alternate encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-5).
+
+```sentinel
+dec = base64.urldecode("Zm9v") // "foo"
+```
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/imports/decimal.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/imports/decimal.mdx
new file mode 100644
index 0000000000..a6633a38e0
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/imports/decimal.mdx
@@ -0,0 +1,318 @@
+---
+page_title: 'Import: decimal'
+sidebar_current: docs-imports-decimal
+description: |-
+ The decimal import provides functions for operating on numbers represented
+ as decimals.
+layout: docs
+---
+
+# Import: decimal
+
+The decimal import provides functions for operating on numbers represented
+as decimals.
+
+Binary floating-point numbers provided by the `float` type are unable to
+fully represent numbers like `1.1` and `2.2`. The decimal import can
+represent these numbers exactly, and so should be used when exactness is
+required.
+
+Decimals are created through the `new` function, which takes any type that
+can represent a number. Arguments to the decimal operators can also take
+a value of any of these types. The full list is:
+
+- float
+- int
+- string
+- existing decimal value
+
+### decimal.infinity(sign)
+
+Creates an infinite-value decimal. Infinite-value decimals are always larger
+or smaller, depending on sign, than any non-infinite decimals.
+
+```sentinel
+decimal.infinity(1) // +Infinity
+decimal.infinity(-1) // -Infinity
+```
+
+Also available as `decimal.inf`.
+
+### decimal.nan
+
+A decimal representing a "not-a-number" value. NaNs are not equal to any
+other number, even themselves.
+
+```sentinel
+decimal.new(-1).sqrt() // NaN
+decimal.new(0).mul(decimal.inf(1)) // NaN
+decimal.nan.is(decimal.nan) // false
+```
+
+### decimal.is_infinite(d)
+
+Evaluates to true if the decimal is infinite.
+
+```sentinel
+decimal.is_infinite(100) // false
+decimal.is_infinite("Infinity") // true
+```
+
+Also available as `decimal.is_inf`, `decimal.isinf` and `decimal.isinfinite`.
+
+### decimal.is_nan(d)
+
+Evaluates to true if the decimal is not-a-number.
+
+```sentinel
+decimal.is_nan("NaN") // true
+```
+
+Also available as `decimal.isnan`.
+
+### decimal.new(v)
+
+Constructs a decimal from another value.
+
+```sentinel
+decimal.new("1.1") // Constructs a decimal from a string.
+decimal.new(2.2) // Constructs a decimal from a float.
+decimal.new(3) // Constructs a decimal from an integer.
+decimal.new("1.1234E+400") // Constructs a decimal from a string using E-notation.
+```
+
+## Type: number
+
+Numbers are represented internally by a sign, coefficient, and exponent.
+The value is calculated with the formula `sign * coefficient * 10^exponent`.
+Exponent is limited to 32 bits, and the coefficient is limited by the total
+precision.
+
+The maximum precision a number can represent is 100 digits. This includes
+both before and after the decimal place.
+
+### number.string
+
+A string representation of the decimal.
+
+```sentinel
+decimal.new(1.5).string // 1.5
+```
+
+### number.sign
+
+A number between `-1` and `+1` representing the sign of the decimal.
+
+```sentinel
+decimal.new(1.5).sign // 1
+```
+
+### number.coefficient
+
+The coefficient component of the decimal.
+
+```sentinel
+decimal.new(1.5).coefficient // 15
+```
+
+### number.exponent
+
+The exponent component of the decimal.
+
+```sentinel
+decimal.new(1.5).exponent // -1
+```
+
+### number.float
+
+A floating-point representation of the decimal.
+
+```sentinel
+decimal.new(1.5).float // 1.500000
+```
+
+### number.int
+
+An integer representation of the decimal. This always rounds down to the nearest integer.
+
+```sentinel
+decimal.new(1.5).int // 1
+```
+
+### number.is(v)
+
+Test for equality with another value.
+
+```sentinel
+decimal.new(1.5).is(1) // false
+```
+
+### number.is_not(v)
+
+Test for inequality with another value.
+
+```sentinel
+decimal.new(1.5).is_not(1) // true
+```
+
+### number.less_than(v)
+
+Test that the decimal is less than another value.
+Can also be called as `number.lt(v)`
+
+```sentinel
+decimal.new(1.5).less_than(1) // false
+```
+
+### number.less_than_or_equals(v)
+
+Test that the decimal is less than or equals to another value.
+Can also be called as `number.lte(v)`
+
+```sentinel
+decimal.new(1.5).less_than_or_equals(1) // false
+```
+
+### number.greater_than(v)
+
+Test that the decimal is greater than another value.
+Can also be called as `number.gt(v)`
+
+```sentinel
+decimal.new(1.5).greater_than(1) // true
+```
+
+### number.greater_than_or_equals(v)
+
+Test that the decimal is greater than or equals to another value.
+Can also be called as `number.gte(v)`
+
+```sentinel
+decimal.new(1.5).greater_than_or_equals(1) // true
+```
+
+### number.add(v)
+
+Add another number to the decimal.
+
+```sentinel
+decimal.new(1.5).add(1) // 2.5
+```
+
+### number.subtract(v)
+
+Subtract another number from the decimal.
+Can also be called as `number.sub(v)`
+
+```sentinel
+decimal.new(1.5).subtract(1) // 0.5
+```
+
+### number.multiply(v)
+
+Multiply the decimal by a number.
+Can also be called as `number.mul(v)`
+
+```sentinel
+decimal.new(1.5).multiply(2) // 3.0
+```
+
+### number.divide(v)
+
+Divide the decimal by a number.
+Can also be called as `number.div(v)`
+
+```sentinel
+decimal.new(3.0).divide(1.5) // 2
+```
+
+### number.modulo(v)
+
+Find the remainder after dividing the decimal by a number.
+Can also be called as `mod`, `remainder`, or `rem`.
+
+```sentinel
+decimal.new(1.5).modulo(1) // 0.5
+```
+
+### number.power(v)
+
+Raise the decimal to a power.
+Can also be called as `number.pow(v)`
+
+```sentinel
+decimal.new(1.5).power(2) // 2.25
+```
+
+### number.exp()
+
+The natural exponent of the decimal. This calculates `e^number`.
+
+```sentinel
+decimal.new(1.5).exp() // 4.4816...
+```
+
+### number.loge()
+
+The natural logarithm of the decimal.
+Can also be called as `number.ln()`
+
+```sentinel
+decimal.new(1.5).loge() // 0.4054...
+```
+
+### number.log10()
+
+The base-10 logarithm of the decimal.
+Can also be called as `number.log()`
+
+```sentinel
+decimal.new(1.5).log10() // 0.1760...
+```
+
+### number.square_root()
+
+The square root of the decimal.
+Can also be called as `number.sqrt()`
+
+```sentinel
+decimal.new(1.5).square_root() // 1.2247...
+```
+
+### number.ceiling()
+
+Round the decimal to the smallest integer greater or equal to itself.
+Can also be called as `number.ceil()`
+
+```sentinel
+decimal.new(1.5).ceiling() // 2
+```
+
+### number.floor()
+
+Round the decimal to the largest integer less than or equal to itself.
+
+```sentinel
+decimal.new(1.5).floor() // 1
+```
+
+### number.absolute()
+
+The absolute value of the decimal.
+Can also be called as `number.abs()`
+
+```sentinel
+decimal.new(1.5).absolute() // 1.5
+decimal.new(-1.5).absolute() // 1.5
+```
+
+### number.negate()
+
+The negated value of the decimal. A positive decimal will become negative,
+and a negative decimal will become positive.
+Can also be called as `number.neg()`
+
+```sentinel
+decimal.new(1.5).negate() // -1.5
+decimal.new(-1.5).negate() // 1.5
+```
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/imports/http.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/imports/http.mdx
new file mode 100644
index 0000000000..f94ae98c27
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/imports/http.mdx
@@ -0,0 +1,370 @@
+---
+page_title: 'Import: http'
+sidebar_current: docs-imports-http
+description: The http import enables the use of HTTP-accessible data from outside the runtime in Sentinel policy rules.
+layout: docs
+---
+
+# Import: http
+
+The http import enables the use of HTTP-accessible data from outside
+the runtime in Sentinel policy rules.
+
+The simplest example of the import in use would be:
+
+```sentinel
+import "http"
+
+resp = http.get("https://example.hashicorp.com")
+main = rule { resp.body contains "something" }
+```
+
+By default, any request response that is not a successful "200 OK" HTTP
+response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information and
+how to customize this behavior.
+
+For more advanced requests which require setting request headers, use a
+[`request` type](#type-request):
+
+```sentinel
+import "http"
+import "json"
+
+param token
+
+req = http.request("https://example.hashicorp.com").with_header("Authorization", "token "+token)
+resp = json.unmarshal(http.get(req).body)
+main = rule { resp["some_key"] is true }
+```
+
+The `with_header` function above returns the `request` object with the
+`Authorization` HTTP header set to value of the variable `some_value`. Many
+of the methods within the http import API are designed around this concept
+of method chaining, allowing for complex building of HTTP requests
+encapsulated in functions. The following is an idiomatic example further
+demonstrating this pattern:
+
+```sentinel
+import "http"
+import "json"
+
+param github_user
+param github_token
+
+client = func(req) {
+ return http.with_timeout(5).with_retries(3)
+}
+
+github_request = func(path) {
+ full_url = "https://api.github.com" + path
+ return http.request(full_url).
+ with_basic_auth(github_user, github_token).
+ with_header("Accept", "application/vnd.github.v3+json").
+ with_header("Custom", "foo"),
+}
+
+default_response = client.get(github_request("/example"))
+
+# Override the Custom header for this single request
+custom_header_response = json.unmarshal(
+ client.get(
+ github_request("/example").with_header("Custom", "bar"),
+ ),
+)
+
+# Override the timeout value for a known slower request
+slow_response = json.unmarshal(
+ client.with_timeout(15).get(github_request("/slow-endpoint")),
+)
+
+some_key_is_true = rule { json.unmarshal(default_response.body)["some_key"] is true }
+body_contains_text = rule { custom_header_response.body contains "something" }
+response_is_ok = rule { slow_response.status_code is 200 }
+
+main = rule { some_key_is_true and body_contains_text and response_is_ok }
+```
+
+### http.client
+
+Retrieve the base HTTP client with default settings. The returned client may be
+configured by calling configuration functions on it; all of these configuration
+functions on are also aliased as top-level functions in this namespace. See
+[`client`](#type-client) below.
+
+### http.request(url)
+
+Create a new [`request`](#type-request) to the specified `url` (string). A
+`request` type enables customization of headers and other concerns before then
+providing the constructed `request` to [`get()`](#client-get-url_or_request),
+[`post()`](#client-post-url_or_request) or [`send()`](#client-send-request).
+
+```sentinel
+req = http.request("example.hashicorp.com/foo").with_header("Foo", "bar")
+resp = http.get(req)
+```
+
+### http.methodPost
+
+Returns a string containing the accepted verb for POST requests, `"POST"`.
+
+### http.methodGet
+
+Returns a string containing the accepted verb for GET requests, `"GET"`.
+
+## Type: client
+
+All of the following configuration functions on the default client are also
+aliased as top-level functions in the import. This allows a short-hand (and
+idiomatic) way of configuring a new client without having to directly access
+`http.client` each time:
+
+```sentinel
+# The following two lines are semantically the same:
+resp = http.client.with_timeout(5).get(url)
+resp = http.with_timeout(5).get(url)
+```
+
+### url_or_request
+
+The [`get()`](#client-get-url_or_request) and [`post()`](#client-post-url_or_request)
+functions accept either a URL string or a request object, noted as
+`url_or_request` throughout the documentation.
+
+When `url_or_request` is a string, a request is made with the URL
+specified by the string. To customize HTTP headers and other settings, pass
+a request. By default, a request has a timeout value of 10 seconds and 1
+retry. These settings can be adjusted with [`with_timeout()`](#clientwith_timeoutinteger) and
+[`with_retries()`](#clientwith_retriesinteger), respectively.
+
+### Redirects
+
+If the response is one of the following redirect codes, the client follows the
+redirect, up to a maximum of 10 redirects:
+
+- 301 (Moved Permanently)
+- 302 (Found)
+- 303 (See Other)
+- 307 (Temporary Redirect)
+- 308 (Permanent Redirect)
+
+If the redirect limit is exceeded, the result is a policy error.
+
+By default, after any redirects are followed (up to the maximum), any request
+response that is not a successful "200 OK" response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information
+and how to customize this behavior.
+
+### client.get(url_or_request)
+
+Issue a GET request with a URL string or a `request` type. Returns a `response`
+type.
+
+```sentinel
+import "http"
+
+resp = http.get("https://example.hashicorp.com")
+main = rule { resp.body contains "something" }
+```
+
+### client.post(url_or_request)
+
+Issue a POST request with a URL string or a `request` type. Returns a `response`
+type.
+
+```sentinel
+import "http"
+
+req = http.request("https://example.hashicorp.com")
+ .with_body(json.marshal({"foo": "bar"}))
+resp = http.post(req)
+main = rule { resp.body contains "something" }
+```
+
+### client.send(request)
+
+Make a request using the supplied `request` type. Returns a `response`.
+
+```sentinel
+import "http"
+
+req = http.request("https://example.hashicorp.com")
+resp = http.send(req)
+main = rule { resp.body contains "something" }
+```
+
+### client.accept_status_codes(list)
+
+Configures a client to not automatically cause a policy failure if
+the resulting response's status code is in `list`. Any status code received
+that is not present in this list will trigger a runtime error.
+
+By default, any request response that is not a successful "200 OK" response
+causes a policy error. This is for convenience, as the most common scenario
+by far is to receive a response and immediately signal a policy error if the
+status code is not 200. Use this scoping to specify a list of non-redirect
+HTTP codes that you wish to accept without error and evaluate the response
+yourself.
+
+Redirects are not considered final status codes in this context. That is,
+redirects are always followed up to the maximum allowed value (see [`Redirects`](#redirects))
+and the final response code in the redirect chain is validated against
+`list`.
+
+```sentinel
+resp = http.accept_status_codes([200, 404]).get(url)
+main = rule { resp.status_code is 404 }
+```
+
+### client.accepted_status_codes
+
+Returns a list of the client's accepted status codes.
+
+```sentinel
+client = http.accept_status_codes([200, 404])
+main = rule { client.accepted_status_codes contains 404 }
+```
+
+### client.with_retries(integer)
+
+Sets the maximum number of retries, specified by `integer`, that should be
+attempted on an unsuccessful request. An unsuccessful request is considered
+to be any 5xx response except `501 (Not Implemented)` or a request timeout.
+By default, one retry is attempted.
+
+The maximum number of retries is 10, for a total of 11 request attempts.
+When a request exceeds the maximum number of retries without a response, the
+result is a policy error. Specifying a number larger than this will result
+in a runtime error.
+
+Between each retry, an exponentially increasing delay is observed, allowing
+time for the server to recover. This delay starts at 1 second for the first
+retry and increases each attempt thereafter. The maximum delay for a single
+attempt is 30 seconds.
+
+Retries can be disabled by specifying 0.
+
+### client.retries
+
+Returns the client's configured number of retries as an integer.
+
+### client.with_timeout(integer)
+
+Sets the request timeout to the number of seconds indicated by `integer`.
+
+Each individual request performed by the HTTP client will be limited by the
+given request timeout. This timeout includes connection time, any redirects,
+and reading the response body.
+
+A maximum of 30 seconds is allowed. Specifying a number larger than this
+will result in a runtime error.
+
+By default, requests will time out after 10 seconds.
+
+### client.timeout
+
+Returns the client's configured timeout in whole seconds as an integer.
+
+### client.without_certificate_verification()
+
+Skips verification of TLS certificates during secure HTTP operations,
+allowing for requests to `https://` endpoints which are secured with a TLS
+certificate signed by an untrusted certificate authority. Takes no arguments.
+
+By default, the HTTP client will trigger a runtime error if a TLS certificate
+presented by a server is from an untrusted certificate authority.
+
+Do not change this setting unless you are aware of the risks involved.
+Without verification, TLS accepts any certificate presented by the server
+and any host name in that certificate. In this mode, TLS is susceptible to
+man-in-the-middle attacks. This option should only be used for testing or in
+trusted networks with self-signed certificates.
+
+```sentinel
+http.without_certificate_verification().get(url)
+```
+
+### client.certificate_verification
+
+Returns true if the client requires TLS certificates to be verifiable.
+
+## Type: request
+
+### request.url
+
+Returns the request URL as a string.
+
+### request.headers
+
+Returns the configured request headers as a string-keyed map of strings.
+
+### request.body
+
+Returns the configured body as a string.
+
+### request.with_header(key, value)
+
+Returns a `request` with the header `key` (string) set to `value` (string).
+
+Values are overridden on subsequent calls to the same `key`. To specify
+multiple values, use the existing value when setting the new one.
+
+```sentinel
+original = http.request("http://example.hashicorp.com").with_header("Foo", "bar")
+overridden = original.with_header("Foo", "baz")
+multi_value = original.with_header("Foo", original.headers["Foo"] + ",baz")
+
+main = rule {
+ original.headers["Foo"] is "bar" and
+ overridden.headers["Foo"] is "baz" and
+ multi_value.headers["Foo"] is "bar,baz"
+}
+```
+
+### request.with_basic_auth(username, password)
+
+Returns a `request` with the `Authorization` header set to use HTTP Basic
+Authentication with the provided username and password.
+
+Note that with HTTP Basic Authentication the provided username and password
+are encoded, but not encrypted.
+
+### request.with_body(body)
+
+Returns a `request` with the `body` set. This value will then be sent as a body
+as part of the request. Commonly used along with the [`post()`](#client-post-url_or_request) function.
+
+```sentinel
+req = http.request("http://example.hashicorp.com")
+ .with_header("Content-Type", "application/json")
+ .with_body(json.marshal({ "foo": "bar" }))
+
+resp = http.post(req)
+```
+
+## Type: response
+
+### response.status_code
+
+The response status code as an integer, e.g. `200`.
+
+### response.headers
+
+The response headers as a map.
+
+### response.body
+
+The response body as a string. Callers should unmarshal the content to a useful
+representation as necessary based on the content type. For example, if the
+response is in JSON:
+
+```sentinel
+import "http"
+import "json"
+
+# This example endpoint returns {"some_key": true}
+req = http.request("https://example.hashicorp.com/some-json")
+
+resp = json.unmarshal(http.get(req).body)
+main = rule { keys(resp) contains "some_key" and resp["some_key"] is true }
+```
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/imports/index.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/imports/index.mdx
new file mode 100644
index 0000000000..e45bf81b81
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/imports/index.mdx
@@ -0,0 +1,18 @@
+---
+page_title: Sentinel Language - Standard Imports
+sidebar_current: docs-imports
+description: >-
+ Standard Imports are the built-in imports that are available to all Sentinel
+ policies.
+layout: docs
+---
+
+# Standard Imports
+
+Standard Imports are the built-in [imports](/sentinel/language/imports)
+that are available to all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in import.
+
+Sentinel-embedded applications can choose to allow or deny certain
+standard imports. Please reference the documentation for the Sentinel-enabled
+application you're using to determine if all standard imports are available.
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/imports/json.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/imports/json.mdx
new file mode 100644
index 0000000000..3355458f14
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/imports/json.mdx
@@ -0,0 +1,31 @@
+---
+page_title: 'Import: json'
+sidebar_current: docs-imports-json
+description: The json import enables a Sentinel policy to parse and access a JSON document.
+layout: docs
+---
+
+# Import: json
+
+The json import enables a Sentinel policy to parse and access a JSON
+document.
+
+### json.unmarshal(obj)
+
+Unmarshals the JSON object `obj` into a native Sentinel structure.
+
+All native JSON types can be represented perfectly as Sentinel native types.
+
+```sentinel
+// Typically the input for this would come from an external source.
+config = json.unmarshal("{ \"foo\": 42 }")
+config.foo // 42
+config.bar // undefined (as usual for accessing a non-existent map key)
+```
+
+### json.marshal(obj)
+
+Marshals the Sentinel object `obj` into a JSON object encoded as a string.
+
+Functions and `undefined` cannot be natively encoded as JSON. If values of
+either type are found in the structure, this will return undefined.
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/imports/runtime.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/imports/runtime.mdx
new file mode 100644
index 0000000000..6493bbbb22
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/imports/runtime.mdx
@@ -0,0 +1,16 @@
+---
+page_title: 'Import: runtime'
+sidebar_current: docs-imports-runtime
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: runtime
+
+The runtime import contains various information about the Sentinel runtime. It
+can be used to get information about the runtime as it's embedded in the CLI or
+a specific integration, such as validating a specific runtime version.
+
+### runtime.version
+
+Returns the version of Sentinel within this implementation.
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/imports/sockaddr.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/imports/sockaddr.mdx
new file mode 100644
index 0000000000..a6842ba5f0
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/imports/sockaddr.mdx
@@ -0,0 +1,41 @@
+---
+page_title: 'Import: sockaddr'
+sidebar_current: docs-imports-sockaddr
+description: The sockaddr import makes working with IP addresses easy.
+layout: docs
+---
+
+# Import: sockaddr
+
+The sockaddr import makes working with IP addresses easy.
+
+### sockaddr.is_contained(outer, inner)
+
+The is_contained function returns true if `inner` is an address that
+is contained within `outer`.
+
+```sentinel
+sockaddr.is_contained("192.168.0.0/24", "192.168.0.32") // true
+sockaddr.is_contained("192.168.0.0/24", "192.174.1.1") // false
+```
+
+### sockaddr.is_equal(addr1, addr2)
+
+The is_equal function returns true if addr1 is equal to addr2.
+
+```sentinel
+sockaddr.is_equal("192.168.12.24", "192.168.12.24") // true
+```
+
+### sockaddr.is_ipv4(addr)
+
+The is_ipv4 function returns true if addr is an IPv4 address.
+
+```sentinel
+sockaddr.is_ipv4("192.168.12.24") // true
+sockaddr.is_ipv4("2001:0db8:85a3:0000:0000:8a2e:0370:7334") // false
+```
+
+### sockaddr.is_ipv6(addr)
+
+The is_ipv6 function returns true if addr is an IPv6 address.
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/imports/strings.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/imports/strings.mdx
new file mode 100644
index 0000000000..823c5d8f07
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/imports/strings.mdx
@@ -0,0 +1,91 @@
+---
+page_title: 'Import: strings'
+sidebar_current: docs-imports-strings
+description: The strings import exposes common string operations.
+layout: docs
+---
+
+# Import: strings
+
+The strings import exposes common string operations.
+
+### strings.has_prefix(s, prefix)
+
+Returns true if `s` starts with `prefix`.
+
+```sentinel
+strings.has_prefix("billing-id", "billing-") // true
+strings.has_prefix("bill-id", "billing-") // false
+```
+
+### strings.has_suffix(s, suffix)
+
+Returns true if `s` ends with `suffix`.
+
+```sentinel
+strings.has_suffix("billing-id", "id") // true
+strings.has_suffix("billing-name", "id") // false
+```
+
+### strings.join(a, sep)
+
+Joins a list with the string supplied by `sep`.
+
+Multi-dimensional lists are supported, and non-string primitive
+types will be converted to their string equivalents. Maps and
+other complex non-list types are not supported.
+
+```sentinel
+strings.join(["foo", "bar", "baz"], ".") // "foo.bar.baz"
+strings.join([["foo", "bar"], "baz"], ".") // "foo.bar.baz"
+strings.join(["a", 1, 1.01, true], ",") // "a,1,1.01,true"
+```
+
+### strings.trim_prefix(s, prefix)
+
+Trim the prefix from the string `s`. If the string doesn't have the
+prefix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_prefix("billing-id", "billing-") // "id"
+strings.trim_prefix("bill-id", "billing-") // "bill-id"
+```
+
+### strings.trim_suffix(s, suffix)
+
+Trim the suffix from the string `s`. If the string doesn't have the
+suffix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_suffix("billing-id", "-id") // "billing"
+strings.trim_suffix("bill-id", "-foo") // "bill-id"
+```
+
+### strings.to_lower(s)
+
+Lowercase the string s.
+
+```sentinel
+strings.to_lower("FoO") // "foo"
+```
+
+### strings.to_upper(s)
+
+Uppercase the string s.
+
+```sentinel
+strings.to_upper("FoO") // "FOO"
+```
+
+### strings.split(s, sep)
+
+Split the string 's' via a substring 'sep'. If 'sep' is not contained in
+'s', the return value will be a single-element list containing only 's'.
+As a special-case, if the input is already a list, it will be returned
+as-is. This allows calling the split function when not knowing if the input
+is already a list or not.
+
+```sentinel
+strings.split("foo,bar,baz", ",") // ["foo", "bar", "baz"]
+strings.split(["foo", "bar", "baz"], ",") // ["foo", "bar", "baz"]
+```
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/imports/time.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/imports/time.mdx
new file mode 100644
index 0000000000..3a675e2bbd
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/imports/time.mdx
@@ -0,0 +1,257 @@
+---
+page_title: 'Import: time'
+sidebar_current: docs-imports-time
+description: The time import provides access to the execution time and time functions.
+layout: docs
+---
+
+# Import: time
+
+The time import provides access to the execution time and time functions.
+
+During the execution of a policy the time is fixed to the moment of
+execution. This gives the illusion that a policy instantly executes at a
+single moment of time.
+
+The import uses Coordinated Universal Time (UTC).
+
+As an example of this, if a policy accessed `time.now.second` multiple times,
+for each access the same value would be returned even if they are several seconds apart.
+This is the execution `timespace`, which is mapped to `time.now`.
+
+It is also possible to run functions on a custom time by using the
+`time.load()` function to create a new timespace from the specified value.
+See the documentation for available actions on timespaces.
+
+Times specified as the reference time or via the `time.load()` function can
+be specified in a `timeish` fashion. This is either one of:
+
+- The number of seconds since the Unix epoch (Thursday, 1 January 1970,
+ 00:00:00 UTC),
+- A timestamp matching the format outlined in RFC3339, either in the
+ format `2006-01-02T15:04:05+07:00`, which includes a timezone offset, or
+ `2006-01-02T15:04:05Z`, which indicates UTC.
+
+### time.now
+
+Create a `timespace` locked either to execution time or, if set, the
+reference time. In a given execution, this will always produce the same
+value.
+
+```sentinel
+time.now // {"day": 17, "hour": 23, "minute": 19, "month": 11 ... }
+```
+
+### time.load(timeish)
+
+Create a timespace using the given value. Please see the import
+documentation for valid values for "timeish".
+
+```sentinel
+time.load("2019-11-16T01:20:30Z") // {"day": 16, "hour": 1, "minute": 20, "month": 11 ... }
+```
+
+### time.nanosecond
+
+A nanosecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.nanosecond * 1) // 2019-11-16T01:20:30.000000001Z
+```
+
+### time.microsecond
+
+A microsecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.microsecond * 1) // 2019-11-16T01:20:30.000001Z
+```
+
+### time.millisecond
+
+A millisecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.millisecond * 1) // 2019-11-16T01:20:30.001Z
+```
+
+### time.second
+
+A second duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.second * 1) // 2019-11-16T01:20:31Z
+```
+
+### time.minute
+
+A minute for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.minute * 1) // 2019-11-16T01:21:30Z
+```
+
+### time.hour
+
+An hour duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+## Type: timespace
+
+### timespace.hour
+
+The hour from 0 to 23.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").hour // 1
+```
+
+### timespace.minute
+
+The minute from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").minute // 20
+```
+
+### timespace.second
+
+The second from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").second // 30
+```
+
+### timespace.day
+
+The day of the month, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").day // 16
+```
+
+### timespace.month
+
+The month of the year as an integer, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month // 11
+```
+
+### timespace.month_name
+
+The name of the month of the year, in English. Example: `January`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month_name // November
+```
+
+### timespace.weekday
+
+The weekday as an integer, where 0 is Sunday and 6 is Saturday.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday // 6
+```
+
+### timespace.weekday_name
+
+The name of the day of the week, in English. Example: `Sunday`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday_name // Saturday
+```
+
+### timespace.year
+
+The year as an integer.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").year // 2019
+```
+
+### timespace.unix
+
+The number of seconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix // 1573867230
+```
+
+### timespace.unix_nano
+
+The number of nanoseconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix_nano // 1573867230000000000
+```
+
+### timespace.zone
+
+The timezone offset, in seconds. This can be a negative number to indicate
+time west of UTC.
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone // -28800
+```
+
+### timespace.zone_string
+
+The timezone offset, in the format `+HH:MM` (example: `-08:00` for PST).
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone_string // -08:00
+```
+
+### timespace.before(timeish_or_timespace)
+
+Check if the time in the timespace is before the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").before("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.after(timeish_or_timespace)
+
+Check if the time in the timespace is after the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").after("2019-11-15T01:20:30Z") // true
+```
+
+### timespace.equal(timeish_or_timespace)
+
+Check if two times are equivalent
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").equal("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.add(duration)
+
+Add a duration in nanoseconds to the time in the timespace and return a new timespace. The
+duration can be negative. The duration should be specified as an integer
+multiplied with the correct unit.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+### timespace.sub(timeish_or_timespace)
+
+Subtract a time from the time in the timespace and return a duration in nanoseconds.
+
+```sentinel
+// Gives a duration of 3 hours.
+duration = time.load(
+ "2019-11-16T01:20:30Z",
+).sub("2019-11-16T04:20:30Z")
+
+// Returns 2019-11-16T01:20:30Z
+time.load(
+ "2019-11-16T04:20:30Z",
+).add(duration)
+```
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/imports/types.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/imports/types.mdx
new file mode 100644
index 0000000000..fcfdbd2ae5
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/imports/types.mdx
@@ -0,0 +1,35 @@
+---
+page_title: 'Import: types'
+sidebar_current: docs-imports-types
+description: The types import enables a Sentinel policy to parse an object's type.
+layout: docs
+---
+
+# Import: types
+
+The types import enables a Sentinel policy to parse an object's type.
+
+### types.type_of(obj)
+
+Returns the object's type as a string
+
+```sentinel playground
+import "types"
+
+// Typically the inputs would come from an external source.
+is_boolean = rule { types.type_of(true) is "bool" }
+is_string = rule { types.type_of("Hello!") is "string" }
+is_integer = rule { types.type_of(42) is "int" }
+is_float = rule { types.type_of(42.123) is "float" }
+is_null = rule { types.type_of(null) is "null" }
+is_undefined = rule { types.type_of(undefined) is "undefined" }
+
+main = rule {
+ is_boolean and
+ is_string and
+ is_integer and
+ is_float and
+ is_null and
+ is_undefined
+}
+```
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/imports/units.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/imports/units.mdx
new file mode 100644
index 0000000000..e2ad3442f9
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/imports/units.mdx
@@ -0,0 +1,39 @@
+---
+page_title: 'Import: units'
+sidebar_current: docs-imports-units
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: units
+
+The units import provides access to quick calculations for various byte
+units.
+
+Note that this import uses base-2 terminology:
+
+- One kilobyte is 1024 bytes
+- One megabyte is 1024^2 = 1048576 bytes
+- One gigabyte is 1024^3 = 1073741824 bytes
+- One terabyte is 1024^4 = 1099511627776 bytes
+- One petabyte is 1024^5 = 1125899906842624 bytes
+
+### units.kilobyte
+
+The base-2 representation of a kilobyte (1024 bytes).
+
+### units.megabyte
+
+The base-2 representation of a megabyte (1048576 bytes).
+
+### units.gigabyte
+
+The base-2 representation of a gigabyte (1073741824 bytes).
+
+### units.terabyte
+
+The base-2 representation of a terabyte (1099511627776 bytes).
+
+### units.petabyte
+
+The base-2 representation of a petabyte (1125899906842624 bytes).
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/imports/version.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/imports/version.mdx
new file mode 100644
index 0000000000..59366c1087
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/imports/version.mdx
@@ -0,0 +1,151 @@
+---
+page_title: 'Import: version'
+sidebar_current: docs-imports-version
+description: |-
+ The version import provides functions for parsing versions and version constraints,
+ and verifying versions against a set of constraints.
+layout: docs
+---
+
+# Import: version
+
+The version import provides functions for parsing versions and version constraints,
+and verifying versions against a set of constraints.
+
+Versions are created through the `new` function, which accepts a string type in dotted-tri format (i.e. 1.0.0-alpha.1+001) that can represent a version.
+
+### version.new(v)
+
+Constructs a version from string value.
+
+```sentinel
+version.new("1.0.0-alpha.1+001")
+```
+
+### version.major
+
+An integer representation of the Major number for a given version .
+
+```sentinel
+version.new("1.0.0-alpha.1+001").major // 1
+```
+
+### version.minor
+
+An integer representation of the Minor number for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").minor // 0
+```
+
+### version.patch
+
+An integer representation of the Patch number for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").patch // 0
+```
+
+### version.prerelease
+
+A string representation of the Prerelease for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").prerelease // "alpha.1"
+```
+
+### version.metadata
+
+A string representation of the Metadata for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").metadata // "001"
+```
+
+### version.version
+
+A string representation of the version including pre-release and metadata information.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").version // "1.0.0-alpha.1+001"
+```
+
+### version.greater_than(v)
+
+Tests that the version is greater than a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").greater_than("0.12.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").gt("1.0.1-alpha.1+001") // False
+```
+
+### version.greater_than_or_equals(v)
+
+Tests that the version is greater than or equal to a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").greater_than_or_equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").gte("1.0.1-alpha.1+001") // False
+```
+
+### version.less_than(v)
+
+Tests that the version is less than a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").less_than("1.0.1-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").lt("0.12.0-alpha.1+001") // False
+```
+
+### version.less_than_or_equals(v)
+
+Tests that the version is less than or equal to a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").less_than_or_equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").lte("0.12.0-alpha.1+001") // False
+```
+
+### version.less_than_or_equals(v)
+
+Tests that the version equals a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").eq("0.12.0-alpha.1+001") // False
+```
+
+### version.compare(v)
+
+Compares one version with a given version. Compare returns -1, 0, or 1 if the version is less, equal, or greater than the other version, respectively.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").compare("0.12.0-alpha.1+001") // -1
+version.new("1.0.0-alpha.1+001").compare("1.0.0-alpha.1+001") // 0
+version.new("0.12.0-alpha.1+001").cmp("1.0.0-alpha.1+001") // 1
+```
+
+### version.satisfies(v, x)
+
+Tests that a version adheres to one or more version constraints. Satisfies supports the following restriction operators:
+
+- =
+- !=
+- \>
+- <
+- \>=
+- <=
+- ~>
+
+Satisfies supports the following usage scenarios:
+
+- A constraint with a pre-release can only match a pre-release version that contains the same major, minor and patch identifiers.
+- A constraint without a pre-release can only match a version without a pre-release.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").satisfies("> 1.0.0-alpha.1+001") // false
+version.new("1.0.0-alpha.1+001").satisfies(">= 1.0.0-alpha.1+001") // true
+version.new("1.0.0-alpha.1+001").satisfies(">= 1.0.0-alpha.1+001, < 1.0.0-beta+001") // true
+version.new("1.0.0").sat("= 1.0.0") // true
+version.new("0.12.7").sat("~> 0.12.0") // true
+```
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/index.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/index.mdx
new file mode 100644
index 0000000000..4497b40b83
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/index.mdx
@@ -0,0 +1,31 @@
+---
+layout: 'docs'
+page_title: 'Documentation'
+sidebar_current: 'docs-home'
+description: |-
+ Sentinel is a language and framework for policy built to be embedded in existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+[introduction](/sentinel/intro) to understand what Sentinel is, how it
+compares to other software, and more.
+
+
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/language/arithmetic.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/language/arithmetic.mdx
new file mode 100644
index 0000000000..06b1256a1b
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/language/arithmetic.mdx
@@ -0,0 +1,67 @@
+---
+page_title: Sentinel Language - Arithmetic
+sidebar_current: docs-language-arith
+description: >-
+ Sentinel supports arithmetic operators for integers and floats. Sentinel
+ supports sum, difference, product, quotient, and remainder.
+layout: docs
+---
+
+# Language: Arithmetic
+
+Sentinel supports arithmetic operators for integers and floats. Sentinel
+supports sum, difference, product, quotient, and remainder.
+
+```plaintext
++ sum
+* difference
+* product
+/ quotient
+% remainder
+```
+
+These operators work in a typical infix style:
+
+```sentinel
+4 + 8 // 12
+8 * 2 // 16
+8 / 4 // 2
+8 / 5 // 1
+8 % 5 // 3
+```
+
+## Order of Operations
+
+Arithmetic follows a standard mathematical order of operations.
+Grouping with parentheses can be used to affect ordering.
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+(4 + 5) * 2 // 18
+```
+
+A full table of operator precendence can be found on the
+[boolean expressions page](/sentinel/language/boolexpr). This
+shows how arithmetic operators relate to other operators.
+
+## Integer Division
+
+Integer division with a remainder rounds down to the nearest integer.
+Example: `8 / 3 is 2`.
+
+If the divisor is zero, an error occurs.
+
+## Mixed Numeric Operations
+
+Mixed numeric operations between integer and floating-point values are
+permitted. The result is a floating-point operation with the integer converted
+to a floating-point value for purposes of calculation.
+
+```sentinel
+import "types"
+
+a = 1.1 + 1 // 2.1
+types.type_of(a) // "float"
+```
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/language/boolexpr.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/language/boolexpr.mdx
new file mode 100644
index 0000000000..22e13e05c4
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/language/boolexpr.mdx
@@ -0,0 +1,207 @@
+---
+page_title: Sentinel Language - Boolean Expressions
+sidebar_current: docs-language-boolexpr
+description: >-
+ Boolean expressions are expressions that evaluate to a boolean value from the
+ result of a combination of one or more comparisons and logical operators.
+layout: docs
+---
+
+# Language: Boolean Expressions
+
+Boolean expressions are expressions that evaluate to a boolean value
+from the result of a combination of one or more comparisons and logical
+operators. Boolean expressions form the basis of policy since policy can be broken
+down to a set of logical decisions that turn into true or false.
+
+A single boolean expression is the body of a [rule](/sentinel/language/rules).
+Additionally, boolean expressions are used with [conditionals](/sentinel/language/conditionals),
+and more.
+
+## Order of Operations
+
+The precedence of operators is shown below. Operators with higher
+precedence values (larger numbers) are evaluated first. Operators with
+the same precedence value are evaluated left-to-right.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Examples of this precedence are shown below:
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+```
+
+## Logical Operators
+
+Logical operators are used to change or combine logical expressions.
+There are three binary operators `and`, `or`, and `xor`. There is
+a single unary operator `not` (which can also be specified as `!`).
+
+The binary operators require two boolean operands and the unary
+operator requires a single boolean operand. In both case, the operands
+are values or expressions that are boolean types.
+
+Below is a basic explanation of the logical operators directly from
+the specification:
+
+```sentinel
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+Using parentheses to group boolean expressions, you can combine
+boolean expressions to become more complex or affect ordering:
+
+```sentinel
+(p and q) or r
+p and (q or r)
+```
+
+## Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+For any comparison, the two operands must be equivalent types. The one
+exception are integers and floats. When an integer is compared with
+a float, the integer is promoted to a floating point value.
+
+The available comparison operators are:
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+The behavior of `is` with `==` and `is not` with `!=` is identical.
+
+Example comparisons:
+
+```sentinel
+name is "Mitchell"
+idnumber > 42
+idnumber <= 12
+```
+
+Using the logical operators, these can be combined:
+
+```sentinel
+name is "Mitchell" and idnumber > 42
+```
+
+The language specification provides more detail on exactly
+[how comparison behaves](/sentinel/language/spec#comparison-operators).
+
+## Set Operators
+
+The set operators `contains` and `in` test for inclusion in a collection
+(a list or map).
+
+Set operators may 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.
+
+`contains` tests if the left-hand collection contains the right-hand value.
+`in` tests if the right-hand collection contains the left-hand value. For
+maps, "contains" looks at the keys, not the values.
+
+Examples:
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+```
+
+## Matches Operator
+
+The `matches` operator tests if a string matches a regular expression.
+The `matches` operator can be negated by prefixing the operator with
+`not`, such as `not matches`.
+
+The left-hand value is the string to test. The right-hand value is
+a string value representing a regular expression. The syntax of the regular
+expression is the same general syntax used by Python, Ruby, and other languages.
+The precise syntax accepted is the syntax accepted by RE2.
+
+Regular expressions are not anchored by default; any anchoring must be
+explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+## Any, All Expressions
+
+`any` and `all` expressions allow testing that any or all elements of a
+collection match some boolean expression. The result of an `any` and `all`
+expression is a boolean.
+
+`any` returns the boolean value `true` if any value in the collection expression
+results in the body expression evaluating to `true`. If the body expression
+evalutes to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression
+result in the body expression evaluating to `true`. If any value in the
+collection expression result in the body expression evaluating to `false`,
+the `all` expression returns `false`.
+
+For empty collections, `any` returns `false` and `all` returns `true`.
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" }
+any group.tasks as t { t.driver is "vmware" }
+```
+
+Since `any` and `all` expressions are themselves boolean expressions, they
+can be combined with other operators:
+
+```sentinel
+any ["a", "b"] as char { char is "a" } or
+other_value is "another"
+```
+
+## Emptiness Comparison
+
+The expressions `is empty` and `is not empty` provide a convenience
+method for determining the emptiness of a value. It supports the same types
+as the [built-in length](/sentinel/functions/length), collections and strings.
+
+```sentinel
+[] is empty // true
+[] is not empty // false
+["foo"] is empty // false
+["foo"] is not empty // true
+undefined is empty // undefined
+undefined is not empty // undefined
+```
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/language/collection-operations.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/language/collection-operations.mdx
new file mode 100644
index 0000000000..41da8f3584
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/language/collection-operations.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Sentinel Language - Collection Operations
+sidebar_current: docs-language-collection-operations
+description: |-
+ Operations for interacting with collections (list, map).
+layout: docs
+---
+
+# Language: Collection Operations
+
+Collection operations are expressions that are performed on a list or map to
+return a variation of the initial data.
+
+At the moment, `filter` is the only collection operation available.
+
+## Filter Expression
+
+`filter` is a [quantifier
+expression](/sentinel/language/spec#quantifier-expressions-any-all-filter-map) that
+returns a subset of the provided collection. Only elements whose filter body
+returns true will be returned. If any of the elements filter body returns
+undefined, the final result will be undefined.
+
+Filter uses the same syntax as the `any` and `all` [boolean
+expressions](/sentinel/language/boolexpr#any-all-expressions):
+
+```sentinel
+filter list as value { condition } // Single-iterator, list
+filter list as idx, value { condition } // Double-iterator, list
+
+filter map as key { condition } // Single-iterator, map
+filter map as key, value { condition } // Double-iterator, map
+```
+
+Examples:
+
+```sentinel
+l = [1, 1, 2, 3, 5, 8]
+evens = filter l as v { v % 2 is 0 } // [2, 8]
+
+m = { "a": "foo", "b": "bar" }
+matched_foo = filter m as _, v { v is "foo" } // { "a": "foo" }
+```
+
+## Map Expression
+
+`map` is a [quantifier
+expression](/sentinel/language/spec#quantifier-expressions-any-all-filter-map) that
+returns a list, regardless of the input collection type. Each element within the
+input collection is evaluted according to the map expression body and appended
+to the result.
+
+```sentinel
+l = [1, 2]
+r = map l as v { v % 2 } // [false, true]
+
+m = { "a": "foo", "b": "bar" }
+r = map m as k, v { v } // ["foo", "bar"]
+```
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/language/conditionals.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/language/conditionals.mdx
new file mode 100644
index 0000000000..1736795dbb
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/language/conditionals.mdx
@@ -0,0 +1,161 @@
+---
+page_title: Sentinel Language - Conditionals
+sidebar_current: docs-language-conditional
+description: |-
+ Conditional statements allow your policy to behave differently depending on a condition.
+layout: docs
+---
+
+# Language: Conditionals
+
+Conditional statements allow your policy to behave differently depending
+on a condition.
+
+Conditional statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## If Statements
+
+`if` statements only execute their bodies if a condition is met.
+The syntax of an `if` statement is:
+
+```sentinel
+if condition {
+ // ... this is executed if condition is true
+}
+```
+
+The `condition` must result in a boolean, such as by calling a function
+or evaluating a [boolean expression](/sentinel/language/boolexpr). If
+the `condition` is `true`, the body (within the `{}`) is executed. Otherwise,
+the body is skipped.
+
+Examples:
+
+```sentinel
+// This would execute the body
+value = 12
+if value is 18 {
+ print("condition met")
+}
+
+// Direct boolean values can be used
+value = true
+if value {
+ print("condition met")
+}
+
+// This would not execute the body since the boolean expression will
+// result in undefined.
+value = {}
+if value["key"] > 12 {
+ print("condition met")
+}
+```
+
+### Else, Else If
+
+An `else` clause can be given to an `if` statement to execute a body
+in the case the condition is _not met_. By putting another `if` statement
+directly after the `else`, multiple conditions can be tested for.
+The syntax is:
+
+```sentinel
+if condition {
+ // ...
+} else {
+ // ...
+}
+
+if condition {
+ // ...
+} else if other_condition {
+ // ...
+} else {
+ // ...
+}
+```
+
+### Scoping
+
+The body of an `if` statement does not create a new
+[scope](/sentinel/language/scope). Any variables assigned within the body
+of an if statement will modify the scope that the `if` statement itself
+is in.
+
+Example:
+
+```sentinel
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+```sentinel
+a = 18
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+## Case Statements
+
+`case` statements are a selection control mechanism that execute a clause based
+on matching expressions. It is worth noting that the expression for `case` is
+optional. When no expression is provided, it defaults the expression to `true`.
+Additionally, the order of clauses is important, as they are evaluated from top
+to bottom, executing the first match.
+The syntax of a case statement is:
+
+```sentinel
+case expression {
+ when clause_expression:
+ // executed when clause_expression and expression are equal
+ else:
+ // executed if no clause matches expression
+}
+```
+
+### When Clause
+
+Any clause that has an expression for comparison must use the `when` keyword.
+It accepts a list of expressions, seperated by a `,`.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+}
+```
+
+```sentinel
+case {
+ when x > 40:
+ return true
+}
+```
+
+### Else Clause
+
+The `else` keyword allows for capturing any expressions that have no matching
+`when` clause.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+ else:
+ return false
+}
+```
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/language/functions.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/language/functions.mdx
new file mode 100644
index 0000000000..73702acc3e
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/language/functions.mdx
@@ -0,0 +1,173 @@
+---
+page_title: Sentinel Language - Functions
+sidebar_current: docs-language-functions
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Functions
+
+Functions allow you to create reusable code to perform computations.
+
+Below is a trivial example function that doubles a number and shows
+the main rule using the function:
+
+```sentinel playground
+double = func(x) {
+ return x * 2
+}
+
+main = rule { double(12) is 24 }
+```
+
+Functions may contain any expressions,
+[conditionals](/sentinel/language/conditionals),
+and [loops](/sentinel/language/loops). They must return a value
+at the end of their execution. If no reasonable return value exists,
+the function should return [undefined](/sentinel/language/undefined).
+
+## Creating a Function
+
+A function is created by assigning a variable to a `func`.
+
+A function can have zero or more parameters. These parameters are specified
+within parentheses `()` separated by commas. If a function has no parameters,
+the parentheses must still be present.
+
+A function must terminate with a `return` statement to return a value back to
+the caller. Only a single value can be returned. The type of a return can
+vary between return statements.
+
+Example:
+
+```sentinel
+add1 = func(x) { return x + 1 }
+```
+
+This example creates a function that adds 1 to the parameter `x`. It is
+assigned to the variable `add1`.
+
+## Calling a Function
+
+Functions are called by accessing their name followed by parentheses with
+a list of comma-separated values for the parameters. If the function takes no
+parameters, an empty set of parentheses must still be used to denote a
+function call.
+
+To call the function in the example above:
+
+```sentinel
+x = 1
+y = add1(x)
+```
+
+## Scoping
+
+The body of a `func` creates a new [scope](/sentinel/language/scope).
+
+The parent scope is the scope in which the function is created. This allows
+for the creation of functions within functions that can access their outer
+scopes.
+
+Example:
+
+```sentinel
+f = func() {
+ a = 42
+ print(a) // 42
+ return undefined
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+f = func() {
+ a = 42
+ return undefined
+}
+
+print(a) // 18
+```
+
+And below is an example of creating a function in a function which uses
+outer values:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double()
+}
+
+print(f()) // 84
+```
+
+A more complex example below shows how scoping works when passing
+functions around as arguments or results:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double
+}
+
+double = f()
+print(double()) // 84
+```
+
+## Pass By Value
+
+The parameters to a function are passed by value, but not deep copied.
+This means that elements of collections can still be modified but the
+root value cannot be changed.
+
+Example:
+
+```sentinel
+f = func(x) {
+ x = "value"
+ return x
+}
+
+x = "outside"
+f(x)
+print(x) // "outside"
+```
+
+```sentinel
+f = func(x) {
+ append(x, "value")
+ return x
+}
+
+x = []
+f(x)
+print(x) // ["value"]
+```
+
+## Recursion
+
+A function is allowed to call other functions, including itself.
+This can be used to implement recursion. For example, the fibonacci sequence
+is implemented below:
+
+```sentinel
+fib = func(x) {
+ if x <= 0 {
+ return undefined
+ }
+
+ if x == 1 {
+ return 1
+ } else {
+ return x + fib(x - 1)
+ }
+}
+```
+
+Note that this example also shows using
+[undefined](/sentinel/language/undefined)
+as a return value in cases where a function has undefined behavior.
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/language/imports.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/language/imports.mdx
new file mode 100644
index 0000000000..0ed1541b85
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/language/imports.mdx
@@ -0,0 +1,116 @@
+---
+page_title: Sentinel Language - Imports
+sidebar_current: docs-language-imports
+description: >-
+ Imports enable a Sentinel policy to access reusable libraries and external
+ data and functions.
+layout: docs
+---
+
+# Language: Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external
+data and functions. The exact list of available imports is dependent on the
+host system. Host systems may also make the available imports configurable
+via plugins.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that
+the import `calendar` allows access to engineer calendars. This import
+only exists for the purpose of this example and at the time of writing is
+not a real available import.
+
+```sentinel
+import "calendar"
+
+// Get the calendar for Bob for today
+bob_calendar = calendar.for("bob").today
+
+// Allow this policy to pass if Bob is not on vacation.
+main = rule { not bob_calendar.has_event("vacation") }
+```
+
+This example shows an import being used to deny a behavior if Bob is on
+vacation. Hopefully real policies do not depend on a single person being
+in the office, but the example shows the power of imports.
+
+In addition to consuming imports, you can also
+[extend Sentinel by writing custom imports](/sentinel/extending)
+This allows you to add any arbitrary behavior to your Sentinel-protected
+systems.
+
+## Importing
+
+Whether accessing a built-in library or an external plugin, the syntax
+for an import is the same:
+
+```sentinel
+import "name"
+```
+
+This adds the import with the name `name`. It can be referenced using
+the identifier `name`. For example, if the import above exposed first
+and last name data, you could access it via `name.first` or `name.last`.
+
+You can shadow imports by assigning a variable. In the example below,
+the import `name` becomes unavailable within the function because it is
+shadowed by a variable of the same name.
+
+Shadowing an import is not the same as overwriting a variable. Imports
+are not part of the [scope](/sentinel/language/scope), meaning that
+the shadow only exists for the scope it is created within. For everything
+else, the import works even after it is shadowed. The example below shows that
+as well.
+
+```sentinel
+import "name"
+
+f = func() {
+ name = "jane"
+ return name
+}
+
+print(f()) // "jane"
+print(name.first) // "bob"
+```
+
+## Aliases
+
+An import can be aliased to another name using the syntax below:
+
+```sentinel
+import "name" as other
+```
+
+This would make the import "name" available as `other`.
+
+An import may only be imported once, regardless of aliases. The following
+would be **invalid** and would result in an error:
+
+```sentinel
+import "name" as one
+import "name" as two
+```
+
+## Accessing Import Data
+
+Imports are accessed with dot-separated identifiers, such as `name.first` or
+`name.stage.first`. These are called _selector expressions_. An import may
+return nested data that further can be accessed via selector expressions.
+
+In the first example on this page with the "calendar" import, we see this:
+
+```sentinel
+import "calendar"
+
+a = calendar.for("bob") // selector expression calendar.for
+b = a.today // selector expression on the result
+c = b.vacation // accessing further nested data
+```
+
+The exact structure of an import is dependent on the import. Please reference
+the documentation for an import to learn more.
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/language/index.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/language/index.mdx
new file mode 100644
index 0000000000..7f588c94cd
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/language/index.mdx
@@ -0,0 +1,114 @@
+---
+page_title: Sentinel Language
+sidebar_current: docs-language-basics
+description: |-
+ Sentinel policies are written using the Sentinel language. This language
+ is easy to learn and easy to write. You can learn the Sentinel language
+ and be productive within an hour. Learning Sentinel doesn't require any
+ formal programming experience.
+layout: docs
+---
+
+# Sentinel Language
+
+Sentinel policies are written using the Sentinel language. This language
+is easy to learn and easy to write. You can learn the Sentinel language
+and be productive within an hour. Learning Sentinel doesn't require any
+formal programming experience.
+
+This language guide will contain many details that are not necessary to
+be productive with Sentinel or are targeted to those who are looking for
+exact information about the language (such as what types of line endings are
+allowed). You can safely ignore these sentences.
+
+You may also view the
+[official language specification](/sentinel/language/spec)
+This is a specific and detailed document on the syntax and behavior of
+the language primarily intended for implementation creators and to disambiguate
+the language.
+
+## Simplest Example
+
+The example below is about the simplest practical example of Sentinel.
+It is reasonable to imagine this as a realistic policy. This shows that
+in most cases, Sentinel will be extremely simple:
+
+```sentinel
+main = rule { request.method is "GET" and request.headers contains "X-Key" }
+```
+
+## Files
+
+Sentinel policies are single files that end in the `.sentinel` file extension.
+There is currently no built-in mechanism to Sentinel for merging multiple
+files. This is purposefully done to make Sentinel policies easy to submit
+to systems that support Sentinel policies.
+
+Sentinel policy files must be UTF-8 encoded and can end in both
+Unix (LF) or Windows (CRLF) line breaks. When running the
+[auto-formatter](#),
+line endings will always use Unix line breaks.
+
+## Ordering
+
+Sentinel policies are executed top-down. For example:
+
+```sentinel
+a = 1 // a = 1 here
+b = a + 1 // b = 2 here
+a = 3 // a = 3, b = 2
+```
+
+In this example, the value of `a` and `b` is shown at each line. Since Sentinel
+executes values top-down, the final value of `a` is 3 and `b` is 2. `b` _does
+not_ become 4.
+
+## Main
+
+Sentinel expects there to be a `main` [rule](/sentinel/language/rules).
+The value of this rule is the result of the entire policy.
+
+The result of the policy depends on the evaluated contents of the `main` rule.
+For booleans, a policy passes on a `true` value, and fails on a `false` value.
+Other types generally follow a zero or zero-length pattern for determining
+success.
+
+| Type | Passing Condition | Failing Condition |
+| ------- | ----------------- | -------------------------- |
+| Boolean | `true` | `false` |
+| String | `""` | Any non-zero length string |
+| Integer | `0` | Any non-zero value |
+| Float | `0.0` | Any non-zero value |
+| List | `[]` | Any non-zero length list |
+| Map | `{}` | Any non-zero length map |
+
+A value of main that falls outside of the above types will result in a policy
+error. As a special case, if `main` evaluates to an undefined value, the error
+message will indicate as such, with a reference to where the undefined value was
+encountered.
+
+## More Complex Example
+
+The simple example above is a full working example. In our experience with
+Sentinel, many policies can be representing using this simple form. However,
+to show more features of the language, a more complex example is shown below.
+This example is also a realistic example of what Sentinel may be used for.
+
+```sentinel
+import "units"
+
+memory = func(job) {
+ result = 0
+ for job.groups as g {
+ for g.tasks as t {
+ result += t.resources.memory else 0
+ }
+ }
+
+ return result
+}
+
+main = rule {
+ memory(job) < 1 * units.gigabyte
+}
+```
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/language/lists.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/language/lists.mdx
new file mode 100644
index 0000000000..bc5269d8eb
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/language/lists.mdx
@@ -0,0 +1,135 @@
+---
+page_title: Sentinel Language - Lists
+sidebar_current: docs-language-lists
+description: Lists are a collection of zero or more values.
+layout: docs
+---
+
+# Language: Lists
+
+Lists are a collection of zero or more values.
+
+Lists can be created using by wrapping values in `[]` and separating them
+by commas. An optional trailing comma is allowed. List elements can be
+differing types. Examples:
+
+```sentinel
+[] // An empty list
+["foo"] // Single element list
+["foo", 1, 2, true] // Multi element list with different types
+["foo", [1, 2]] // List containing another list
+```
+
+A list can be [sliced](/sentinel/language/slices) to create sublists.
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for value inclusion in a list.
+
+## Accessing Elements
+
+List elements can be accessed with the syntax `name[index]` where
+`index` is zero-indexed.
+
+A negative index accesses the list in reverse. It is the same as
+reversing a list and then using a positive index. Similar to a positive
+index, it is bounded by the length of the list as a negative value.
+
+Accessing beyond the length of the list results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+a = ["foo", 1, true, [1, 2]]
+
+a[0] // "foo"
+a[2] // true
+a[4] // undefined
+a[-2] // true
+a[-4] // "foo"
+a[-5] // undefined
+a[3][1] // 2
+```
+
+## List Append
+
+Values can be appended to a list using the built-in
+[append](/sentinel/functions/append) function.
+
+This modifies the list in-place and returns
+[undefined](/sentinel/language/undefined). For more information on
+why `append` behaves this way, please read the full documentation for the
+[append](/sentinel/functions/append) function.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append([1,2], "foo") // [1, 2, "foo"]
+append([1,2], [3]) // [1, 2, [3]]
+append(1, 3) // error()
+```
+
+## List Concatenation
+
+Two lists can be concatenated using the `+` operator or the shorthand
+`+=` assignment operator. For the `+` operator, a new list is returned.
+For `+=`, the left-hand list is modified in place.
+
+Examples:
+
+```sentinel
+[1] + [2] // [1, 2]
+[1] + [[1]] // [1, [1]]
+[1] + 1 // error
+
+a = [1]
+a += [2] // a = [1, 2]
+a += 3 // error
+```
+
+## List Length
+
+The length of a list can be retrieved using the
+[length](/sentinel/functions/length) function.
+
+Examples:
+
+```sentinel
+length([]) // 0
+length(["foo"]) // 1
+```
+
+## Removing Items From a List
+
+You can use a combination of [list concatenation](#list-concatenation) and
+[slicing](/sentinel/language/slices) to remove elements from a list.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+a = a[:2] + a[3:] // [1, 2, 4, 5]
+```
+
+The shorthand shown here is effectively the same as `a[0:2] + a[3:length(a)]`,
+which creates a new list out of the concatenation two sub-lists composed of the
+first two elements, and the rest of the list starting at index 3. This
+effectively removes the 3rd element from the list (index 2).
+
+## List Comparison
+
+Lists may be [compared](/sentinel/language/boolexpr#comparison-operators)
+for equality. Lists are equal if they are of equal length and their
+corresponding elements are comparable and equal. Lists with the same elements
+but in a different order are not equal.
+
+```sentinel
+[1, 2] is [1, 2] // true
+[1, 2] is [2, 1] // false
+["a"] is ["a", "b"] // false
+["a", ["b", "c"]] is ["a", ["b", "c"]] // true
+```
+
+List comparison speed is O(N), meaning that the speed of the comparison is
+_linearly_ proportional to the number of elements in the list. The more
+elements, the more iterations that are necessary to verify equality.
+
+-> The N-value quoted above should account for the sum of the elements of _all_
+lists in the subjects of comparison, as list comparison will recurse into these
+lists to check for equality.
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/language/logging-errors.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/language/logging-errors.mdx
new file mode 100644
index 0000000000..705a72cbd4
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/language/logging-errors.mdx
@@ -0,0 +1,52 @@
+---
+page_title: Sentinel Language - Logging and Errors
+sidebar_current: docs-language-log
+description: The built-in functions `print` and `error` can be used for logging and errors.
+layout: docs
+---
+
+# Language: Logging and Errors
+
+The built-in functions `print` and `error` can be used for logging
+and errors. Logging is helpful to understand how a policy is executing
+in development. And errors are useful to halting execution immediately in
+certain scenarios.
+
+## Print
+
+The `print` function is used to output debug information. The exact location
+where this print statements go is dependent on the host application and
+the Sentinel runtime itself.
+
+The `print` function takes zero or more Sentinel values as arguments.
+Each value is formatted for human-friendly output and space-separated.
+Example:
+
+```sentinel
+value = 42
+print("the value is", value) // the value is 42
+
+map = { "foo": false }
+print(map) // { "foo": false }
+
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // false
+```
+
+## Errors
+
+Certain actions in Sentinel, such as accessing an undefined variable,
+attempting to add two incompatible types, etc. result in _runtime errors_.
+These errors halt the execution of a policy immediately. The pass/fail result
+of a policy is considered fail.
+
+Runtime errors can be manually triggered using the `error` function.
+The `error` function behaves exactly like the `print` function except that
+execution is halted immediately.
+
+This should only be used in scenarios where the policy _must fail_ due to
+some unexpected or invalid condition. The line between `error` and
+[undefined](/sentinel/language/undefined) can sometimes be unclear. Undefined
+is recommended when a policy can conceivably recover or safely ignore the
+undefined behavior. An error should be used when the policy should fail and
+notify someone regardless.
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/language/loops.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/language/loops.mdx
new file mode 100644
index 0000000000..622732bd35
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/language/loops.mdx
@@ -0,0 +1,92 @@
+---
+page_title: Sentinel Language - Loops
+sidebar_current: docs-language-loops
+description: >-
+ Loop statements allow you to execute a body of code for each element in a
+ collection or for some fixed number of times.
+layout: docs
+---
+
+# Language: Loops
+
+Loop statements allow you to execute a body of code for each element in
+a collection or for some fixed number of times.
+
+Loop statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## For Statements
+
+`for` statements allow repeated execution of a block for each
+element in a collection.
+
+Example:
+
+```sentinel
+// A basic sum
+count = 0
+for [1, 2, 3] as num {
+ count += num
+}
+```
+
+The syntax is `for COLLECTION as value`. This will iterate over the
+collection, assigning each element to `value`. In the example above,
+each element is assigned to `num`. The body is executed for each element.
+In the example above, the body adds `num` to the `count` variable. This
+creates a basic sum of all values in the collection.
+
+For a map, the assigned element is the key in the map. In the example
+below, `name` would be assigned map keys.
+
+```sentinel
+list = []
+for { "a": 1, "b": 2 } as name {
+ append(list, name)
+}
+
+print(list) // ["a" "b"]
+```
+
+An alternate syntax is `for COLLECTION as key, value`. This will assign
+both the key and value to a variable. For a list, the key is the element
+index. For a map, it is the key and value is assigned the element value.
+Example:
+
+```sentinel
+count = 0
+for { "a": 1, "b": 2 } as name, num {
+ count += num
+}
+
+print(count) // 3
+```
+
+## Scoping
+
+The body of a `for` statement creates a new
+[scope](/sentinel/language/scope). If a variable is assigned within
+the body of a for statement that isn't assigned in a parent scope,
+that variable will only exist for the duration of the body execution.
+
+Example:
+
+```sentinel
+for list as value {
+ a = 42
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+for list as value {
+ a = 42
+}
+
+print(a) // 18
+```
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/language/maps.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/language/maps.mdx
new file mode 100644
index 0000000000..298bf9a567
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/language/maps.mdx
@@ -0,0 +1,121 @@
+---
+page_title: Sentinel Language - Maps
+sidebar_current: docs-language-maps
+description: >-
+ Maps are a collection of zero or more key/value pairs. These are useful for
+ storing values that are looked up by a unique key.
+layout: docs
+---
+
+# Language: Maps
+
+Maps are a collection of zero or more key/value pairs. These are useful
+for storing values that are looked up by a unique key.
+
+Maps can be created using `{}` and specifying key/value pairs. Keys and
+values can be differing types. An optional trailing comma is allowed.
+Examples:
+
+```sentinel
+// Empty map
+{}
+
+// Map with a single value on one line
+{ "key": "value" }
+
+// Map with multiple values with differing types on multiple lines
+{
+ "key": "value",
+ 42: true,
+}
+```
+
+Maps are **unordered**. When
+[looping](/sentinel/language/loops)
+over a map, the key/value pairs can be returned in any order.
+
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for key inclusion in a map.
+
+## Accessing Elements
+
+Map elements can be accessed with the syntax `name[key]`. This looks up
+a value by a key.
+
+Accessing a key that doesn't exist results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+map = { "key": "value", 42: true, }
+
+map["key"] // "value"
+map[42] // true
+map[0] // undefined
+```
+
+## Modifying or Adding Elements
+
+Elements can be added or modified in a map by assigning to `name[key]`.
+If the key doesn't exist, the value is added. If the key already exists,
+the value is overridden.
+
+```sentinel
+map = { "key": "value" }
+
+map[42] = true // Add a new key/value
+map["key"] = 12 // Modify the value of "key"
+```
+
+## Deleting Elements
+
+An element can be deleted from a map using the
+[delete](/sentinel/functions/delete) function.
+
+Examples:
+
+```sentinel
+map = { "key": "value" }
+delete(map, "key") // map is now empty
+delete(map, "other") // no effect for non-existent key
+```
+
+## Keys and Values
+
+The keys and values of a map can be retrieved as
+[lists](/sentinel/language/lists) using the
+[keys](/sentinel/functions/keys) and
+[values](/sentinel/functions/values) functions.
+
+Because maps are unordered, the keys and values are returned in an
+unspecified order. It should not be assumed that keys and values will be
+returned in a consistent order.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+## Map Comparison
+
+Maps may be [compared](/sentinel/language/boolexpr#comparison-operators) for
+equality. Maps are equal if they are of equal length and both their
+corresponding keys and values are comparable and equal.
+
+```sentinel
+{"foo": "bar"} is {"foo": "bar"} // true
+{"foo": "bar"} is {"baz": "bar"} // false
+{"foo": "bar"} is {"foo": "baz"} // false
+{"foo": "bar"} is {"foo": "bar", "baz": "qux"} // false
+{1: "a"} is {1.0: "a"} // true (int/float comparable)
+
+// also true (maps are not ordered):
+{"m": {"a": "b"}, "l": ["a"]} is {"l": ["a"], "m": {"a": " b"}}
+```
+
+-> The current worst-case comparison speed for maps is O(N²), or _quadratic
+time_. This is the square of the cumulative elements or the parent and all child
+maps contained within the map. Consider this when making comparisons on larger,
+more complex maps. This may be optimized in the future.
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/language/parameters.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/language/parameters.mdx
new file mode 100644
index 0000000000..348acf2593
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/language/parameters.mdx
@@ -0,0 +1,133 @@
+---
+page_title: Sentinel Language - Parameters
+sidebar_current: docs-language-parameters
+description: >-
+ Parameteres allow a Sentinel policy to describe variables that are expected
+ to be supplied by the calling application, in addition to any default value.
+layout: docs
+---
+
+# Language: Parameters
+
+Sentinel allows a policy author to supply parameters to help facilitate policy
+reuse and ensure sensitive values do not need to be hard-coded in a policy.
+
+Parameters are supplied by using the `param` keyword, followed by an identifier.
+A default value can also be supplied by using the `default` keyword.
+
+```sentinel
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+```
+
+Once declared, parameters can be used like any other variable, including being
+re-assigned.
+
+```sentinel
+param foo default 1 // 1 (default)
+
+foo = foo + 1 // 2
+```
+
+## Variable Descriptions
+
+You can supply a description to a parameter by adding a comment at the top of
+it. This value can be communicated to a specific implementation of Sentinel to
+provide information about what the parameter is for during configuration.
+
+```sentinel
+// An example parameter. Must be supplied or the policy will fail.
+param foo
+```
+
+## Supplying Parameter Values Using the Sentinel CLI
+
+In a production implementation, supplying parameters to a policy is an
+implementation-specific detail - see the documentation for your particular
+implementation for details.
+
+Using the [Sentinel CLI](/sentinel/commands), you can supply parameters one of
+four ways.
+
+### Supplying Parameter Values Using the Configuration File
+
+You can supply parameters using the
+[`param`](/sentinel/configuration#parameters) section of the
+[configuration file](/sentinel/configuration).
+
+```hcl
+param "foo" {
+ value = "bar"
+}
+```
+
+This method works for both `sentinel apply` and `sentinel test`.
+
+### Supplying Parameter Values Using the Environment
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can supply a value using environment variables - prefix the parameter with
+`SENTINEL_PARAM_`, using the name of the parameter to supply.
+
+```plaintext
+SENTINEL_PARAM_foo=bar sentinel apply policy.sentinel
+```
+
+### Supplying Parameter Values Using the CLI
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can also use the `-param` CLI argument to supply parameter in a `key=value`
+pair.
+
+```plaintext
+sentinel apply -param foo=bar policy.sentinel
+```
+
+### Interactive CLI Prompting
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+If a required value has not been supplied when a policy is run with `sentinel apply`, it will be prompted for, along with its description:
+
+
+
+ $ sentinel apply policy.sentinel
+
+ policy.sentinel:2:7: requires value for parameter foo
+
+ An example parameter. Must be supplied or the policy will fail.
+
+
+ Values can be strings, floats, or JSON array or object values. To
+ force
+
+ strings, use quotes.
+
+
+ Enter a value: bar
+
+
+
+ Pass
+
+
+
+
+### CLI Value Format
+
+-> **NOTE:** This section contains details for the parameter features supported by `sentinel apply`.
+
+The CLI takes either strings, or JSON numbers, arrays, or maps. If you need a
+literal string value, quote the value.
+
+```sentinel
+foo // string
+42 // number (float)
+"42" // string ("42", without quotes)
+[1, 2] // array (list)
+{"a": "b"} // object (map)
+```
+
+-> **NOTE:** Boolean values are not supported by this method.
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/language/rules.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/language/rules.mdx
new file mode 100644
index 0000000000..e31924d414
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/language/rules.mdx
@@ -0,0 +1,204 @@
+---
+page_title: Sentinel Language - Rules
+sidebar_current: docs-language-rules
+description: >-
+ Rules form the basis of a policy by representing behavior that is either
+ passing or failing (true or false).
+layout: docs
+---
+
+# Language: Rules
+
+Rules form the basis of a policy by representing behavior that is either passing
+or failing. A policy can be broken down into a set of rules. Breaking down a
+policy into a set of rules can make it more understandable and aids with
+testing.
+
+An example is shown below:
+
+```sentinel playground
+weather = "sunny"
+day = "wednesday"
+is_sunny = rule { weather is "sunny" }
+is_wednesday = rule { day is "wednesday" }
+
+main = rule {
+ is_sunny and
+ is_wednesday
+}
+```
+
+A rule contains a single expression. This can be a simple [boolean
+expression](/sentinel/language/boolexpr), or an expression representing the
+discovery of a set of violations using more in-depth expressions like [`filter`
+and `map`](/sentinel/language/collection-operations). You can split expressions
+into multiple lines for readability, as you would with any other expression
+within Sentinel.
+
+A rule is also _lazy_ and _memoized_. _Lazy_ means that the rule is only
+evaluated when it is actually required, not at the point that it is
+created. This is covered in more detail in the [lazy](#lazy-and-memoized) section.
+_Memoized_ means that the value is only computed once and then saved. Once
+a rule is evaluated, the result of it is reused anytime the rule is referenced
+again.
+
+## Making rules easier to understand
+
+Rules are an abstraction that make policies much more understandable
+by making it easier for humans to see what the policy is trying to do.
+
+For example, consider the policy before which doesn't abstract into rules:
+
+```sentinel
+main = rule {
+ ((day is "saturday" or day is "sunday") and homework is "") or
+ (day in ["monday", "tuesday", "wednesday", "thursday", "friday"] and
+ not school_today and homework is "")
+}
+```
+
+Assume that `day`, `homework`, and `school_day` are available.
+
+In plain English, this policy is trying to say: you can play with your friends
+only on the weekend as long as there is no homework, or during the week if
+there is no school and no homework.
+
+Despite the relatively simple nature of this policy (a few lines, about 5
+logical expressions), it is difficult to read and understand, especially if
+you've not seen it before.
+
+The same policy using rules as an abstraction:
+
+```sentinel
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+is_valid_weekend = rule { is_weekend and homework is "" }
+is_valid_weekday = rule { not is_weekend and not school_today and homework is "" }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+By reading the names of the rules, its much clearer to see what each individual
+part of the policy is trying to achieve. The readability is improved even
+further when adding comments to the rules to explain them further, which is
+a recommended practice:
+
+```sentinel
+// A weekend is Sat or Sun
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+// A valid weekend is a weekend without homework
+is_valid_weekend = rule { is_weekend and homework is "" }
+
+// A valid weekday is a weekday without school
+is_valid_weekday = rule { not is_weekend and not school_today and homework is "" }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+## "When" Predicates
+
+A rule may have an optional `when` predicate attached to it. When this is
+present, the rule is only evaluated when the predicate results in `true`.
+Otherwise, the rule is not evaluated and the result of the rule is always
+`true`.
+
+"When" predicates should be used to define the context in which the rule
+is semantically meaningful. It is common when translating human language
+policy into Sentinel to have scenarios such as "when the key has a prefix
+of `/account/`, the remainder must be numeric." In that example, when the
+key _does not_ have the specified prefix, the policy doesn't apply.
+
+Assume you have rules `is_prefix` and `is_numeric` to check both the
+conditions in the example above. An example is shown with and without
+the "when" predicate:
+
+```sentinel
+example_no_when = rule { (is_prefix and is_numeric) or not is_prefix }
+
+example_when = rule when is_prefix { is_numeric }
+```
+
+The rules are equivalent in behavior for all values of `is_prefix` and
+`is_numeric`, but the second rule is more succint and easier to understand.
+
+## Testing
+
+To verify a policy works correct, the
+[built-in Sentinel test framework](/sentinel/writing/testing)
+uses rules as the point where you can assert behavior. You say that
+you expect certain rules to be true or false. By doing so, you ensure that
+the policy results in the value you expect using the rule flow that you
+expect.
+
+Therefore, in addition to readability, we recommend splitting policies into
+rules to aid testability.
+
+## Non-Boolean Values
+
+Sentinel supports non-boolean values in rules. This can be useful for passing
+more detail along to a calling integration when more detail is required than a
+boolean value would be able to communicate. This data shows up in the [policy
+trace](/sentinel/writing/tracing) and can be utilized in different ways
+depending on the integration you are using Sentinel with.
+
+Consider a different take on our policy above:
+
+```sentinel
+// A policy to check a set of scheduled days to determine what days you can't
+// come out and play, based on the day not being a weekend day and there being
+// homework to do.
+
+param days
+
+main = rule {
+ filter days as d {
+ d.day not in ["saturday", "sunday"] and
+ d.homework is not ""
+ }
+}
+```
+
+When given a value in the set of `days` that has a `day` that is a weekday, and
+`homework` present, the policy will fail. Additional, when tracing was enabled,
+the days that were detected as violating the policy would be given in the trace
+for the `main` rule.
+
+Generally, for non-boolean values, a policy fails on non-zero data. See the
+details on [the `main` rule](/sentinel/language#main) for more details.
+
+The result of a rule must be either a boolean, string, integer, float, list, or
+map. All other types will result in a runtime error.
+
+## Lazy and Memoized
+
+A rule is _lazy_ and _memoized_.
+
+_Lazy_ means that the rule is only evaluated when it is actually required,
+not at the point that it is created. And _memoized_ means that the value is
+only computed once and then saved. Once a rule is evaluated, the result of it
+is reused anytime the rule is referenced again.
+
+Both of these properties have important implications for Sentinel
+policies:
+
+**Performance:** Rules are a way to improve the performance of a Sentinel
+policy. If you have a boolean expression that is reused a lot, a rule will
+only be evaluated once. In the example shown above, `is_weekend` is used
+multiple times, but will only have to be evaluated once.
+
+**Behavior:** Rules accessing variables see the value of those variables
+at _the time they are evaluated_. This can lead to surprising behavior
+in some cases. For example:
+
+```sentinel playground
+a = 1
+b = rule { a == 1 }
+a = 2
+main = b
+```
+
+In this example, `main` will actually result to `false`. It is evaluated
+when it is needed, which is when the policy executes `main`. At this point,
+`a` is now 2 and `b` has not been evaluated yet. Therefore, `b` becomes `false`.
+All future references to `b` will return `false` since a rule is memoized.
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/language/scope.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/language/scope.mdx
new file mode 100644
index 0000000000..fb81cdef59
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/language/scope.mdx
@@ -0,0 +1,41 @@
+---
+page_title: Sentinel Language - Scope
+sidebar_current: docs-language-scope
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Scope
+
+Scope is the context in which variables are created and accessed. If a
+variable exists in a parent scope, it is accessed and can be modified.
+Otherwise, the variable is created in your current scope.
+
+Scopes are created with _blocks_. A block is a possibly empty sequence
+of statements within matching brace brackets `{}`. You may nest blocks
+to create nested scopes.
+
+In addition to explicitly created blocks, there are implicit blocks:
+
+1. The policy block encapsulates an entire policy.
+2. Each `any`, `all`, and `for` statement is considered to be in
+ its own block. Note that `if` statements _do not_ create their
+ own block.
+
+The example policy below shows various effects of scopes:
+
+```sentinel
+a = 1
+print(a) // 1
+
+f = func() {
+ print(a) // 1
+ a = 12
+ b = 1
+ return undefined
+}
+f()
+
+print(a) // 12
+print(b) // undefined
+```
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/language/slices.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/language/slices.mdx
new file mode 100644
index 0000000000..82dc7f4c18
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/language/slices.mdx
@@ -0,0 +1,45 @@
+---
+page_title: Sentinel Language - Slices
+sidebar_current: docs-language-slices
+description: >-
+ Slices are an efficient way to create a substring or sublist from an existing
+ string or list, respectively.
+layout: docs
+---
+
+# Language: Slices
+
+Slices are an efficient way to create a substring or sublist from an
+existing string or list, respectively.
+
+The syntax for creating a slice is:
+
+```sentinel
+a[low : high]
+```
+
+`low` is the lowest index to slice from. The resulting slice includes
+the value at `low`. `high` is the index to slice to. The resulting slice
+will not include the value at `high`. The length of the resulting list
+or string is always `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+
+a = "hello"
+b = a[1:4] // "ell"
+```
+
+## Convenience Shorthands
+
+Some convenience shorthands are available by omitting the value for either the
+low or high index in the slice expression: omitting `low` implies a low index of
+0, and omitting `high` implies a high index of the length of the list.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+
+b = a[:2] // [1, 2] (same as a[0:2])
+b = a[2:] // [3, 4, 5] (same as a[2:length(a)])
+```
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/language/spec.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/language/spec.mdx
new file mode 100644
index 0000000000..fb436296a1
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/language/spec.mdx
@@ -0,0 +1,1329 @@
+---
+page_title: Sentinel Language Specification
+sidebar_current: docs-language-spec
+description: This is the specification for the Sentinel policy language.
+layout: docs
+---
+
+# Sentinel Language Specification
+
+This is the specification for the Sentinel policy language.
+
+The Sentinel language is designed with policy enforcement in mind. It is dynamically typed and garbage collected and has explicit support for _rule_ construction representing boolean logic.
+
+The language is designed to be easy to learn and use by non-programmers. It is expected to be embedded within applications.
+
+## Table of Contents
+
+
+
+
+- [Source code representation](#source-code-representation)
+- [Declarations and Scope](#declarations-and-scope)
+- [Blocks](#blocks)
+- [Lexical Elements](#lexical-elements)
+ - [Comments](#comments)
+ - [Identifiers](#identifiers)
+ - [Keywords](#keywords)
+ - [Operators and Delimiters](#operators-and-delimiters)
+ - [Integer Literals](#integer-literals)
+ - [Floating-point Literals](#floating-point-literals)
+ - [String Literals](#string-literals)
+ - [Implicit Line Joining](#implicit-line-joining)
+ - [Whitespace](#whitespace)
+ - [Semicolons](#semicolons)
+- [Variables](#variables)
+- [Undefined](#undefined)
+- [Expressions](#expressions)
+ - [Operand](#operand)
+ - [Primary Expressions](#primary-expressions)
+ - [Null](#null)
+ - [Booleans](#booleans)
+ - [Boolean Literals](#boolean-literals)
+ - [Boolean Expressions](#boolean-expressions)
+ - [List Literals](#list-literals)
+ - [Map Literals](#map-literals)
+ - [Function Literals](#function-literals)
+ - [Rule Expressions](#rule-expressions)
+ - [Index Expressions](#index-expressions)
+ - [Selectors](#selectors)
+ - [Slice Expressions](#slice-expressions)
+ - [Calls](#calls)
+ - [Operators](#operators)
+ - [Operator Precedence](#operator-precedence)
+ - [Arithmetic Operators](#arithmetic-operators)
+ - [Integer operators](#integer-operators)
+ - [Integer overflow](#integer-overflow)
+ - [Floating-point operators](#floating-point-operators)
+ - [String Concatenation](#string-concatenation)
+ - [List Concatenation](#list-concatenation)
+ - [Comparison Operators](#comparison-operators)
+ - [Logical Operators](#logical-operators)
+ - [Set Operators](#set-operators)
+ - [Matches Operator](#matches-operator)
+ - [Else Operator](#else-operator)
+ - [Quantifier Expressions (any, all, filter, map)](#quantifier-expressions-any-all-filter-map)
+- [Statements](#statements)
+ - [Expression Statements](#expression-statements)
+ - [Assignments](#assignments)
+ - [If Statements](#if-statements)
+ - [Case Statements](#case-statements)
+ - [For Statements](#for-statements)
+ - [Break Statements](#break-statements)
+ - [Continue Statements](#continue-statements)
+ - [Return Statements](#return-statements)
+- [Imports](#imports)
+- [Parameters](#parameters)
+- [Built-in Functions](#built-in-functions)
+ - [Length](#length)
+ - [Collections](#collections)
+ - [List Append](#list-append)
+ - [Map Delete](#map-delete)
+ - [Keys and Values](#keys-and-values)
+ - [Range](#range)
+ - [Type Conversion](#type-conversion)
+ - [Printing](#printing)
+ - [Errors](#errors)
+- [Program Execution](#program-execution)
+
+
+
+## Source code representation
+
+Source code is Unicode text encoded in UTF-8. A "character" referenced in this document refers to a Unicode code point. Each code point is distinct: upper and lower case letters are different characters.
+
+The underscore character \_ (U+005F) is considered a "letter".
+
+```ebnf
+letter = unicode_letter | "_" .
+decimal_digit = "0" … "9" .
+octal_digit = "0" … "7" .
+hex_digit = "0" … "9" | "A" … "F" | "a" … "f" .
+```
+
+## Declarations and Scope
+
+A declaration binds an identifier to a value. An identifier is declared at the point it is first assigned. An assignment is considered a first assignment if the identifier hasn't already been previously declared in the current scope or any parent scopes.
+
+The scope of an identifier is the extent of source text in which the identifier denotes the specified value. Sentinel is lexically scoped using blocks.
+
+An identifier declared in a block may be redeclared in an inner block. While the identifier of the inner declaration is in scope, it denotes the value assigned in the inner declaration.
+
+## Blocks
+
+A block is a possibly empty sequence of statements within matching brace brackets.
+
+```ebnf
+Block = "{" StatementList "}" .
+StatementList = { Statement ";" } .
+```
+
+Blocks nest and affect scoping.
+
+In addition to explicit blocks in the source code, there are implicit blocks:
+
+1. The universe block encompasses all source text.
+2. Each program has a program block containing all source text for that program.
+3. Each "any", "all", and "for" statements is considered to be in its own implicit block. "if" does not create an implicit block.
+
+## Lexical Elements
+
+### Comments
+
+Comments are sections of source text used for documentation.
+
+```plaintext
+# Single line comment
+// Single line comment
+/* multi
+line
+ comment */
+```
+
+Three forms of comments are supported: two single-line forms and one multi-line form. A single-line comment begins with the token `//` or `#`. Everything between the starting token and the end of the line is ignored.
+
+A multi-line comment begins with the token `/*` and ends with the token `*/`. Everything between `/*` and `*/` is ignored.
+
+A comment may not start inside a string literal or inside another comment.
+
+A multi-line comment containing no newlines acts like a space.
+
+### Identifiers
+
+Identifiers name program entities such as rules, variables, and functions. An identifier is a sequence of one or more letters and digits. The first character in an identifier must be a letter.
+
+```ebnf
+identifier = letter { letter | unicode_digit } .
+```
+
+```sentinel
+a
+_a
+_A
+αβ
+```
+
+#### Pre-Declared Identifiers
+
+The following identifiers are implicitly declared in the [universe block](#blocks):
+
+```plaintext
+Constants:
+ false null true undefined
+
+Functions:
+ append bool delete error float int keys length print range string values
+```
+
+### Keywords
+
+The following keywords are reserved and may not be used as identifiers:
+
+```plaintext
+all
+any
+as
+break
+case
+continue
+default
+else
+empty
+filter
+for
+func
+if
+import
+map
+param
+return
+rule
+when
+```
+
+### Operators and Delimiters
+
+The following character sequences represent operators, delimiters, and other special tokens:
+
+```plaintext
++
+-
+*
+/
+%
++=
+-=
+*=
+/=
+%=
+==
+<
+>
+=
+!
+!=
+<=
+>=
+( )
+[ ]
+{ }
+,
+.
+:
+;
+and
+contains
+else
+in
+is
+matches
+not
+or
+xor
+```
+
+As a special case, `else` is both a keyword and operator, depending on the context. See [Else Operator](#else-operator) for more details.
+
+### Integer Literals
+
+An integer literal is a sequence of digits representing an integer constant. An optional prefix sets a non-decimal base: `0` for octal, `0x` or `0X` for hexadecimal. In hexadecimal literals, letters `a-f` and `A-F` represents values 10 through 15.
+
+Integers are signed 64-bit values (-9223372036854775808 to 9223372036854775807).
+
+```ebnf
+int_lit = decimal_lit | octal_lit | hex_lit .
+decimal_lit = ( "1" … "9" ) { decimal_digit } .
+octal_lit = "0" { octal_digit } .
+hex_lit = "0" ( "x" | "X" ) hex_digit { hex_digit } .
+```
+
+```
+42
+0600
+0xBadFace
+170141183460469231731687303715884105727
+```
+
+### Floating-point Literals
+
+A floating-point literal is a decimal representation of a floating-point constant. It has an integer part, a decimal point, a fractional part, and an exponent part. The integer and fractional part comprise decimal digits; the exponent part is an e or E followed by an optionally signed decimal exponent. One of the integer part or the fractional part may be elided; one of the decimal point or the exponent may be elided.
+
+Floating-point numbers are IEEE-754 64-bit floating numbers.
+
+```ebnf
+float_lit = decimals "." [ decimals ] [ exponent ] |
+ decimals exponent |
+ "." decimals [ exponent ] .
+decimals = decimal_digit { decimal_digit } .
+exponent = ( "e" | "E" ) [ "+" | "-" ] decimals .
+```
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+### String Literals
+
+String literals are character sequences between double quotes, as in "bar". Within the quotes, any character may appear except newline and unescaped double quote. The text between the quotes forms the value of the literal.
+
+A multi-character sequence beginning with a backslash encode values in various formats.
+
+Backslash escapes allow arbitrary values to be encoded as ASCII text. There are four ways to represent the integer value as a numeric constant: `\x` followed by exactly two hexadecimal digits; `\u` followed by exactly four hexadecimal digits; `\U` followed by exactly eight hexadecimal digits, and a plain backslash `\` followed by exactly three octal digits. In each case the value of the literal is the value represented by the digits in the corresponding base.
+
+After a backslash, certain single-character escapes represent special values:
+
+```plaintext
+\a U+0007 alert or bell
+\b U+0008 backspace
+\f U+000C form feed
+\n U+000A line feed or newline
+\r U+000D carriage return
+\t U+0009 horizontal tab
+\v U+000b vertical tab
+\\ U+005c backslash
+\" U+0022 double quote
+```
+
+The three-digit octal (\nnn) and two-digit hexadecimal (\xnn) escapes represent individual bytes of the resulting string; all other escapes represent the (possibly multi-byte) UTF-8 encoding of individual characters. Thus inside a string literal \377 and \xFF represent a single byte of value 0xFF=255, while ÿ, \u00FF, \U000000FF and \xc3\xbf represent the two bytes 0xc3 0xbf of the UTF-8 encoding of character U+00FF.
+
+A string value is a (possibly empty) sequence of bytes. Strings are immutable: once created, it is impossible to change the contents of a string.
+
+The length of a string s (its size in bytes) can be discovered using the built-in function `length`. An individual character (of type string) can be accessed by integer indices 0 through `length(s)-1`.
+
+```ebnf
+string_lit = `"` { unicode_value | byte_value } `"` .
+unicode_value = unicode_char | little_u_value | big_u_value | escaped_char .
+byte_value = octal_byte_value | hex_byte_value .
+octal_byte_value = `\` octal_digit octal_digit octal_digit .
+hex_byte_value = `\` "x" hex_digit hex_digit .
+little_u_value = `\` "u" hex_digit hex_digit hex_digit hex_digit .
+big_u_value = `\` "U" hex_digit hex_digit hex_digit hex_digit
+ hex_digit hex_digit hex_digit hex_digit .
+escaped_char = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | `"` ) .
+```
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+### Implicit Line Joining
+
+Expressions can be split over more than one line. For example:
+
+```sentinel
+a or
+b or # This is a comment
+c
+```
+
+Implicitly continued lines can have trailing comments. Blank continued lines are allowed.
+
+### Whitespace
+
+Whitespace is needed to separate tokens, but no distinction is made between the number and combination of whitespace characters. Whitespace characters can be space (U+0020), horizontal tabs (U+0009), carriage returns (U+000D), and newlines (U+000A).
+
+```sentinel
+a or b
+
+a or b
+
+a or
+ b
+
+rule { a or b }
+
+rule {
+ a or b }
+
+rule {
+ a or
+ b
+}
+```
+
+### Semicolons
+
+The formal grammar uses semicolons ";" as terminators in a number of productions.
+It is idiomatic Sentinel source to omit semicolons in most cases.
+
+A semicolon is automatically inserted into the token stream immediately after
+a line's final token if that token is:
+
+- An identifier
+- An integer, float, or string literal
+- The keyword `break`, `continue`, or `return`
+- The delimiter `)`, `]`, or `}`
+
+## Variables
+
+A variable is a storage location for holding a value.
+
+A variable has a dynamic type, which is the concrete type of the value assigned to the variable at run time. The dynamic type may vary during execution and change as a result of further assignments.
+
+A variable's value is retrieved by referring to the variable in an expression; it is the most recent value assigned to the variable. If a variable has not yet been declared (initially assigned), it is an error.
+
+```sentinel
+x = 7 // x is type int
+x = "foo" // x is now type string
+
+x = y // error if y is not previously assigned
+```
+
+## Undefined
+
+The value denoted by the keyword `undefined` represents undefined behavior or values. It can be created directly using the keyword `undefined`. It is also returned as a result of expressions in specified cases.
+
+`undefined` is a valid operand for any operations. Only `undefined or true` will result in true. All other operations result in `undefined`. An exception is if `undefined` is not reached as a result of short-circuit operations.
+
+```sentinel
+undefined OR true = true
+undefined OR false = undefined
+undefined OR undefined = undefined
+undefined AND true = undefined
+undefined AND false = undefined
+undefined AND undefined = undefined
+undefined XOR true = undefined
+undefined XOR false = undefined
+undefined XOR undefined = undefined
+
+// Short-circuit examples
+false OR true OR undefined = true
+false OR undefined OR true = true
+true AND false AND undefined = false
+true AND undefined AND false = undefined
+
+// Non-logical operators
+undefined + 5 = undefined
+-undefined = undefined
+!undefined = undefined
+```
+
+If the result of the main rule is `undefined`, it is treated as `false` but is indicative of erroneous logic.
+
+## Expressions
+
+### Operand
+
+Operands denote the elementary values in an expression. An operand may be a literal, an identifier denoting a variable, rule, or function, or a parenthesized expression.
+
+```ebnf
+Operand = Literal | OperandName | MethodExpr | "(" Expression ")" .
+Literal = BasicLit | CompositeLit | FunctionLit | RuleLit .
+BasicLit = int_lit | float_lit | string_lit .
+OperandName = identifier | QualifiedIdent.
+```
+
+### Primary Expressions
+
+Primary expressions are the operands for unary and binary expressions.
+
+```ebnf
+PrimaryExpr =
+ Operand |
+ PrimaryExpr Selector |
+ PrimaryExpr Index |
+ PrimaryExpr Slice |
+ PrimaryExpr Arguments |
+ PrimaryExpr ( "is empty" | "is not empty" ).
+
+Selector = "." identifier .
+Index = "[" Expression "]" .
+Slice = "[" [ Expression ] ":" [ Expression ] "]" |
+ "[" [ Expression ] ":" Expression ":" Expression "]" .
+Arguments = "(" [ ( ExpressionList | Type [ "," ExpressionList ] ) [ "..." ] [ "," ] ] ")" .
+```
+
+```sentinel
+x
+2
+(s + ".txt")
+f(3.1415, true)
+m["foo"]
+s[i : j + 1]
+obj.color
+f.p[i].x()
+x is empty
+x is not empty
+```
+
+### Null
+
+The reserved word `null` denote the singleton value `null`. Null represents the explicit absense of a value. Behavior of `null` within expressions is specified explicitly for each expression.
+
+### Booleans
+
+### Boolean Literals
+
+The reserved words `true` and `false` denote objects that represent the boolean values `true` and `false`, respectively. These are boolean literals.
+
+```ebnf
+BoolLit = "true" | "false" .
+```
+
+#### Boolean Expressions
+
+Boolean expressions are expressions that must result in a boolean value. Any other value type becomes the `undefined` value.
+
+```ebnf
+BoolExpr = Expression .
+```
+
+### List Literals
+
+A list literal denotes a list, which is an integer indexed collection of values.
+
+```ebnf
+ListLit = "[" [ ElementList [ "," ] ] "]" .
+ElementList = Element { "," Element } .
+Element = Expression | LiteralValue .
+```
+
+A list may contain zero or more values. The number of values in a list is its length.
+
+A list has a set of indices. An empty list has an empty set of indices. A non-empty list has the index set `{0...n - 1}` where `n` is the length of the list. Attempting to access a list using an index that is not a member of its set of indices results in the `undefined` value.
+
+### Map Literals
+
+A map literal denotes a map, which is an unordered group of elements indexed by a set of unique keys.
+
+```ebnf
+MapLit = "{" [ KeyedElementList [ "," ] ] "}" .
+KeyedElementList = KeyedElement { "," KeyedElement } .
+KeyedElement = Element ":" Element .
+```
+
+Keys can only be a boolean, numeric, or string type.
+
+The value of a non-existent key is the `undefined` value.
+
+### Function Literals
+
+A function literal represents a function.
+
+```ebnf
+FunctionLit = "func" Function .
+Function = Parameters FunctionBody .
+FunctionBody = Block .
+Parameters = "(" [ IdentifierList [ "," ] ] ")" .
+IdentifierList = identifier { "," identifier } .
+```
+
+```sentinel
+func(a, b) { return b }
+```
+
+Function literals are only allowed in the file scope. They may refer to variables defined in surrounding blocks.
+
+A function must terminate with a return statement. If a function has no meaningful return value, it should return `undefined`.
+
+### Rule Expressions
+
+A rule is an expression that is evaluated lazily and the result is memoized.
+
+If the optional "when" predicate is present, the rule is evaluated only when
+the "when" boolean expression results in true. If the predicate is false,
+the rule is not evaluated and returns true. The predicate is evaluated when
+the rule would be evaluated; it is also lazy and memoized in the same way.
+
+```ebnf
+RuleExpr = "rule" [ "when" BoolExpr ] "{" Expr "}" .
+```
+
+```sentinel
+rule { x is y }
+
+rule {
+ x == "value" or
+ y == "other"
+}
+
+rule when x is y { y > 42 }
+
+rule { map ["a", "b", "c"] as _, id {
+ { "id": id }
+}}
+```
+
+### Index Expressions
+
+A primary expression of the form `a[x]` denotes the element of a list or map indexed by x. The value x is called the index or key.
+
+For `a` of type map:
+
+- If `a` contains a key `x`, `a[x]` is the map value with key `x`.
+- If `a` does not contain key `x`, `a[x]` is the `undefined` value.
+
+For `a` of type list:
+
+- `x` must be an integer
+- `x` must be in the range `[-1 * length(a), length(a)-1]`
+- If `x` is contained in the set of indices of `a`, `a[x]` is the list element at index `x`. If `x` is negative, `a[x]` is equivalent to `a[length(a)+x]`.
+- If `x` is not contained in the set of indices of `a`, `a[x]` is the `undefined` value.
+
+For `a` of value `null`:
+
+- `a[x]` is the `undefined` value.
+
+Otherwise `a[x]` is an error.
+
+### Selectors
+
+For a primary expression x, the selector expression `x.f` denotes the field `f` of the value `x`. The identifier `f` is called the selector. The type of the selector expression is the type of the selector.
+
+As a special case, selectors can be [reserved words](#keywords) and keyword [operators](#operators-and-delimiters), but cannot be any other non-identifier element.
+
+Selectors are used to access data from [imports](#imports). The first primary expression `x` in `x.f` denotes the import name. The field `f` is the selector to access data from the import.
+
+Selectors may also be used to access map data with static keys. They are syntactic sugar over index expressions. `math.pi` is equivalent to `math["pi"]` and exhibit the same limitations and behavior as an index expression. Selectors cannot, however, be used to assign values to map data.
+
+Selectors on undefined result in undefined.
+
+```sentinel
+math.pi
+time.pst.hour
+```
+
+### Slice Expressions
+
+Slice expressions construct a substring or list from a string or list.
+
+The primary expression
+
+```sentinel
+a[low : high]
+```
+
+constructs a substring or list. The indices `low` and `high` select which elements of operand `a` appear in the result. The result has indices starting at 0 and length equal to `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+```
+
+For convenience, any of the indices may be omitted. A missing `low` index defaults to zero; a missing `high` index defaults to the length of the sliced operand:
+
+```sentinel
+a[2:] // same as a[2 : length(a)]
+a[:3] // same as a[0 : 3]
+a[:] // same as a[0 : length(a)]
+```
+
+The indices are in range if `0 <= low <= high <= length(a)`, otherwise they are out of range. If the indices are out of range at run time, the result is the `undefined` value.
+
+If `a` is the value `null`, the result is the `undefined` value.
+
+If `a` is any other value type, it is an error.
+
+### Calls
+
+Given an expression `f` where `f` is a function value:
+
+```sentinel
+f(a1, a2, … an)
+```
+
+calls `f` with arguments `a1, a2, ... an`. The type of the expression is the result of `f`. The arguments are evaluated left to right. Arguments are passed by value.
+
+### Operators
+
+```ebnf
+Expression = UnaryExpr | Expression binary_op Expression .
+UnaryExpr = PrimaryExpr | unary_op UnaryExpr .
+
+binary_op = logical_op | set_op | rel_op | add_op | mul_op | else_op .
+logical_op = "and" | "or" | "xor" .
+set_op = ["not"] ( "contains" | "in" ).
+rel_op = "==" | "!=" | "<" | "<=" | ">" | ">=" |
+ "is" | "is not" | "matches" | "not matches" .
+add_op = "+" | "-" .
+mul_op = "*" | "/" | "%" .
+else_op = "else" .
+unary_op = "+" | "-" | "!" | "not" .
+```
+
+#### Operator Precedence
+
+Unary operators have the highest precedence.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Binary operators of the same precedence associate from left to right. For instance, x / y _ z is the same as (x / y) _ z.
+
+### Arithmetic Operators
+
+Arithmetic operators apply to numeric values and yield a result of the same type when both operands are the same.
+
+Arithmetic operations between integer and floating-point types result in a floating-point value with the integer operand treated as a floating-point value for purposes of calculation.
+
+Arithmetic operations between numeric values (integer, floating-point) and non-numeric values (strings, lists) are not permitted.
+
+All five supported arithmetic operators (+, -, \*, /, %) apply to both integer and floating-point types; + also applies to strings.
+
+```plaintext
++ sum integers, floats, strings, lists
+* difference integers, floats
+* product integers, floats
+/ quotient integers, floats
+% remainder integers, floats
+```
+
+#### Integer operators
+
+For two integer values x and y, the integer quotient `q = x / y` and remainder `r = x % y` satisfy the following relationships:
+
+```sentinel
+x = q*y + r and |r| < |y|
+```
+
+with x / y truncated towards zero.
+
+```plaintext
+ x y x / y x % y
+ 5 3 1 2
+-5 3 -1 -2
+ 5 -3 -1 2
+-5 -3 1 -2
+```
+
+As an exception to this rule, if the dividend x is the most negative value for the int type of x (-9223372036854775808), the quotient `q = x / -1` is equal to x (and r = 0).
+
+If the divisor is a constant, it must not be zero. If the divisor is zero at run time, an error occurs.
+
+#### Integer overflow
+
+For signed integers, the operations `+`, `-`, and `*` may legally overflow and the resulting value exists and is deterministically defined by the signed integer representation, the operation, and its operands. No exception is raised as a result of overflow.
+
+#### Floating-point operators
+
+For floating-point numbers, +x is the same as x, while -x is the negation of x. The result of a floating-point division by zero is not specified beyond the IEEE-754 standard.
+
+#### String Concatenation
+
+Strings can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = "hi"
+y = "hello"
+x = x + ", " + y // "hi, hello"
+x += " and good bye" // "hi, hello and good bye"
+```
+
+String addition creates a new string by concatenating the operands.
+
+#### List Concatenation
+
+Lists can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = [1, 2]
+x = x + [2, 3] // [1, 2, 2, 3]
+x += [4] // [1, 2, 2, 3, 4]
+```
+
+List addition creates a new list by concatenating the operands.
+
+### Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+In any comparison, the two operands must be equivalent types. The only exception are integers and floats, which are considered numeric and may be compared. Any comparison between other non-matching types results in the `undefined` value.
+
+The equality operators `==`, `!=`, `is`, and `is not` apply to operands that are comparable. The ordering operators `<`, `<=`, `>`, and `>=` apply to operands that are ordered. The behavior of `is` with `==` and `is not` with `!=` is identical. These terms and the result of the comparisons are defined as follows:
+
+- Boolean values are comparable. Two boolean values are equal if they are either both true or both false.
+- Integer values are comparable and ordered, in the usual way.
+- Floating point values are comparable and ordered, as defined by the IEEE-754 standard.
+- An integer compared with floating point value treats the integer as the converted floating point value.
+- String values are comparable and ordered, lexically byte-wise.
+- Lists are comparable. Lists are equal if they are of equal length and their
+ corresponding elements are comparable and equal.
+- Maps are comparable. Maps are equal if they are of equal length and both their
+ corresponding keys and values are comparable and equal.
+
+If either operand is the `undefined` value, the result of the expression is the `undefined` value.
+
+### Emptiness Comparisons
+
+Checking the emptiness of an object can be achieved by using one of the emptiness expressions, `is empty` or `is not empty`. Although similar to [Comparison Operators](#comparison-operators) in that they yield a boolean value and read as though a comparison is taking place, both `is empty` and `is not empty` are evaluated as a multi-word expression.
+
+An emptiness comparison can only be performed against collections, strings or `undefined`, with the same rules as the [built-in length](/sentinel/functions/length) function.
+
+```sentinel
+"" is empty // true
+"foo" is empty // false
+[] is empty // true
+[1] is empty // false
+{} is empty // true
+{"a": "b"} is empty // false
+
+"" is not empty // false
+"foo" is not empty // true
+[] is not empty // false
+[1] is not empty // true
+{} is not empty // false
+{"a": "b"} is not empty // true
+
+undefined is empty // undefined
+undefined is not empty // undefined
+```
+
+### Logical Operators
+
+Logical operators apply to boolean values and yield a boolean result.
+
+Logical operators are evaluated left-to-right and perform short-circuit logic. The right-hand side is not guaranteed to be evaluated if a short-circuit can be performed.
+
+```plaintext
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+### Set Operators
+
+The set operators `contains` and `in` test for set inclusion for lists
+and maps, and substring inclusion for strings.
+
+Set operators may be negated by prefixing the operator with `not`: `not contains` and `not in`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+`contains` tests if the left-hand collection contains the right-hand value. For lists, this tests equality of any value. For maps, this tests equality of any key. For strings, this tests for substring existence.
+
+`in` tests if the right-hand collection contains the left-hand value. The behavior is equivalent to `contains`.
+
+The collection must be a list, map, or string. If it is the `undefined` value, the result is the `undefined` value. For any other value, the result is an error.
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+
+"test" contains "est" // true
+"test" contains "best" // false
+"test" in "testing" // true
+"best" in "testing" // false
+```
+
+### Matches Operator
+
+The matches operator tests if a string matches a regular expression.
+
+The matches operators may be negated by prefixing the operator with `not`: `not matches`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+The left-hand value is the string to test. The right-hand value is a string value representing a regular expression.
+
+The syntax of the regular expression is the same general syntax used by Python, Ruby, and other languages. More precisely, it is the syntax accepted by RE2. The regular expression is not anchored by default; any anchoring must be explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+If either operand is `undefined`, the result is the `undefined` value. For any other non-string value, the result is an error.
+
+### Else Operator
+
+Else operators can be used to provide a default value for an expression that may evaluate to the `undefined` value. Else operators return their left-hand value unless it is `undefined`, otherwise they return their right-hand value.
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else null
+```
+
+### Quantifier Expressions (any, all, filter, map)
+
+Quantifiers are expressions that apply a predicate to a collection (list or map). The purpose of the quantifier is to produce a result based on its particular type.
+
+`any` and `all` expressions are existential and universal quantifiers, respectively. `any` expressions are equivalent to a chain of `or` and `all` expressions are equivalent to a chain of `and`. Both expressions implement short-circuiting equivalent to logical operators.
+
+The `map` expression is an apply-to-all quantifier and returns a new list for all values in the input collection. The output list will be the same length as the input collection.
+
+The `filter` expression is a subset quantifier and returns the subset of all values in the input collection for which the supplied predicate applies. This will be zero or more elements, up to the length of the input.
+
+The body of a quantifier expression is a boolean expression.
+
+`any` returns the boolean value `true` if any value in the collection expression results in the body expression evaluating to `true`. If the body expression evaluates to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression result in the body expression evaluating to `true`. If any value in the collection expression result in the body expression evaluating to `false`, the all expression returns `false`.
+
+For empty collections, `any` returns false and `all` returns `true`.
+
+`map` always returns a list, regardless of if the input collection is a list itself; maps (as in the data type) also return lists when processed through `map`. Each element is the result of the supplied expression body.
+
+`filter` returns a list or map, the same type as its input collection. Only the elements for which the expression body evaluated to `true` will be returned. If an iteration of the expression body results in `undefined`, the entire result set is `undefined`.
+
+When the collection is a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When the collection is a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+QuantExpr = QuantOp Expression "as" [ identifier "," ] identifier "{" BoolExpr "}" .
+QuantOp = "all" | "any" | "filter" | "map" .
+```
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" } // true if every iterations is true
+any group.tasks as t { t.driver is "vmware" } // true if any iteration is true
+map group.tasks as t { { "driver": t.driver } } // [{"driver": "vmware"}, ...]
+filter group.tasks as t { t.driver is "vmware" } // subset of tasks that is true
+```
+
+## Statements
+
+### Expression Statements
+
+Function call expressions may exist in the statement context.
+
+```ebnf
+ExpressionStmt = Expression .
+```
+
+### Assignments
+
+Assignments set the value of an expression to the value of another expression.
+
+```ebnf
+Assignment = AssignExpr assign_op Expression .
+AssignExpr = identifier | IndexExpr .
+assign_op = [ add_op | mul_op ] "=" .
+```
+
+The assignment `x op= y` is equivalent to `x = x op (y)` for supported values of `op`.
+
+```sentinel
+a = 1
+b[12] = 42
+c["key"] = "value"
+
+a *= 12
+b[12] += 8
+```
+
+Assignments to lists and maps can be carried out using [index expressions](#index-expressions), with the operands of the index expression being evaluated alongside the right hand side expression in a right-to-left (RHS, LHS) order.
+
+In addition to the rules detailed in the section describing their use, the following rules apply when assigning to maps or lists using index expressions:
+
+- The [variable](#variables) must exist and be a list or map. Attempts to use an index assignment on an unknown variable, or a variable that not a list or map, results in a runtime error.
+- Assigning to a list or map index that already exists overwrites that index's data with evaluated right hand side's value.
+- Assigning to an unknown key in a map creates a key for that map with the evaluated right hand side's value assigned to that key.
+- Attempting to assign a value to a list index that is out of range results in a runtime error.
+
+### If Statements
+
+If statements specify the conditional execution of two branches according to the value of a boolean expression. If the expression evaluates to true, the "if" branch is executed, otherwise, if present, the "else" branch is executed.
+
+```ebnf
+IfStmt = "if" BoolExpr Block [ "else" ( IfStmt | Block ) ] .
+```
+
+```sentinel
+if x < y {
+ return x
+} else if x > z {
+ return z
+} else {
+ return y
+}
+```
+
+### Case Statements
+
+Case statements specify a selection control mechanism to conditionally execute a branch based on expression equality.
+
+Each clause that wishes to provide an expression must use the "when" keyword. Multiple expressions can be provided, separated with a comma (",").
+
+There can be one, optional, "else" clause within a `case` statement. The "else" clause is executed if all other clauses failed to run.
+
+The clauses are evaluated in the order they are listed, with the first successful evaluation being executed.
+
+A `case` statement can optionally provide an expression. If no expression is provided, it is the equivalent of writing `case true {`.
+
+```ebnf
+CaseStmt = "case" [ Expression ] "{" { CaseWhenClause } "}" .
+CaseWhenClause = CaseWhenCase ":" StatementList .
+CaseWhenCase = "when" Expression { "," Expression } | "else" .
+```
+
+```sentinel
+case x {
+when y, z:
+ return true
+else:
+ return false
+}
+
+case {
+when x > 42:
+ return true
+else:
+ return false
+}
+```
+
+### For Statements
+
+A `for` statement specifies repeated execution of a block.
+
+The expression must evaluate to a list or a map.
+
+When iterating over a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When iterating over a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+ForStmt = "for" Expressions "as" [ identifier "," ] identifier Block .
+```
+
+```sentinel
+for [1, 2, 3] as v { count += v } // basic sum
+
+for [1, 2, 3] as idx, v {
+ if idx > 1 { count += v }
+}
+
+data = { "a": 12, "b": 32 }
+for data as k { count += data[k] } // sum map values
+for data as k, v { count += v }
+```
+
+### Break Statements
+
+A `break` statement terminates execution of the innermost `for` statement
+within the same function.
+
+```ebnf
+BreakStmt = "break" .
+```
+
+```sentinel
+// Will only print "1"
+for [1, 2, 3] as v {
+ print(v)
+ break
+}
+```
+
+### Continue Statements
+
+A `continue` statement begins the next iteration of the innermost `for` loop.
+The `for` loop must be within the same function.
+
+```ebnf
+ContinueStmt = "continue" .
+```
+
+```sentinel
+// Will print 1, 3
+for [1, 2, 3] as v {
+ if v == 2 {
+ continue
+ }
+
+ print(v)
+ break
+}
+```
+
+### Return Statements
+
+A return statement in a function F terminates the execution of F and provides the result value.
+
+```ebnf
+ReturnStmt = "return" Expression .
+```
+
+A function must terminate with a return statement.
+
+The return statement can be invoked at any time during a function to terminate execution.
+
+## Imports
+
+An import adds an assignment to the global scope to external definitions.
+
+The import declarations must only appear at the top of the source file, before any other statements. Comments may appear before imports.
+
+```ebnf
+ImportDecl = "import" Name ["as" identifier] .
+Name = string_lit .
+```
+
+An import name and identifier must be distinct. Two names may not repeated even if they're declared with different identifiers.
+
+If an identifier is not specified, the identifier is the name of the import itself.
+
+An import is not a valid value. The identifier representing the import may not be used as an expression, such as in assignment statements or call expressions.
+
+Values in imports are not assignable directly via their selectors. Function calls may be used to alter the state of the external data represented by the import. Whether or not this is supported is specific to the import implementation. The exception to this assignment restriction is the memoized data returned by a call to an import (see below).
+
+Data returned by imports can be memoized, meaning that data is returned by the import in the form of a map which is then used as much as possible without having to return to the import for more data. For example, `t = time.now` returns a map so that `t.hour` will be able to be looked up without having to go back to the import for that data.
+
+Data returned by imports may be callable, meaning that methods may be called on the memoized data returned by an import. For example, given `t = time.now`, `t.after(some_previous_time)` is valid, with the receiver data being set to the local memoized data stored in `t`. It is a valid case to accept modifications to the receiver data (example: assignment to the memoized data via index expressions), as long as all data in in the receiver continues to be string-keyed. It is an invalid case to accept receiver data with non-string-typed keys. Methods may also alter the contents of receiver data so that it is different after the call is finished.
+
+As with methods, imports may also have callable keys where the data may not be memoized. Example: in the event of `v = foo.bar`, if `v.baz` is not included in the memoized data, a call will be made to the import to fetch it. The same rules and restrictions to receiver data apply to callable keys as do to method calls.
+
+The support for callable methods and non-memoized keys on data returned by imports is specific to the import implementation.
+
+The handling of import loading is specific to the runtime implementation.
+
+Implicit imports may be added by the runtime, for example for standard library definitions. An explicit import of the same name should override a matching implicit import. For example, if "time" is an implicit import and the program specifies `import "time" as cal`, then only `cal` is defined.
+
+```sentinel
+import "time"
+import "math" as m
+```
+
+## Parameters
+
+```ebnf
+ParamDecl = "param" identifier [ "default" Expression ]
+```
+
+A parameter is a way for a policy to describe variables that are expected to be supplied by the calling application, in addition to any default value.
+
+Parameter declarations must appear at the top of the source file, following imports.
+
+Like imports, comments may appear before parameters; this is mainly pertinent when the policy is not importing anything, which would make parameters the first declarations that appear in a policy.
+
+A parameter declaration describes a valid variable that is added to the scope of a policy at runtime. This variable has the same properties and rules as other variables assigned during the course of policy evaluation as defined by the specification. This includes having a dynamic type and being able to be re-assigned during execution.
+
+Parameters may only be strings, integers, floating point numbers, booleans, lists, or maps. More complex types such as rules or functions are not allowed.
+
+A parameter can specify a default value by adding one in the declaration via use of the "default" keyword, and supplying a literal for the default value. The literal for a default is a very limited expression, comprising of:
+
+- For strings, a simple string literal;
+- For integers and floating-point numbers, either a literal denoting the value, or a unary expression with the literal, and the operators - or +;
+- For booleans, the pre-declared identifiers true or false;
+- For lists and maps, their respective literals composed of values and keys (for maps) of the above values only.
+
+Any other type of expression or identifier is not allowed.
+
+A parameter that does not have a default value defined is required by the policy. Evaluating a policy with a required parameter that has not been supplied at execution results in a runtime error.
+
+Parameters must not conflict with imports or any other identifiers currently defined in the global scope, including any reserved or pre-declared identifiers. Attempting to assign a parameter to an existing value results in a runtime error.
+
+```sentinel
+import "time"
+
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+param time default "11:00" // error, conflicts with "time" import
+param undefined // error, reserved identifier
+```
+
+## Built-in Functions
+
+Built-in functions are predeclared. They are called like any other function.
+
+### Length
+
+The built-in function `length` returns the length of a collection of string.
+
+The length of a string is the number of bytes in the string. It is not necessarilly the number of characters.
+
+The length of a collection is the number of elements in that collection.
+
+The length of `undefined` is `undefined`.
+
+### Collections
+
+#### List Append
+
+The built-in function `append` appends a value to the end of a list in-place.
+
+Appending to a non-list value results in an immediate `fail`.
+
+The return value of `append` is always the `undefined` value.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+#### Map Delete
+
+The built-in function `delete` deletes elements from a map by key. The map is modified in-place.
+
+Deleting a key that does not exist does nothing.
+
+Calling delete for a non-map value results in an immediate `fail`.
+
+The return value of `delete` is always the `undefined` value.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
+
+#### Keys and Values
+
+The built-in function `keys` and `values` return the keys and values of a map, respectively. The return value is a list. Both functions return values in an unspecified order.
+
+The `keys` or `values` of `undefined` is `undefined`.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+### Range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it defaults to `1`.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4]
+range(1, 5, 2) // [1,3]
+range(0, -3, -1) // [0,-1,-2]
+```
+
+### Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a value of that type according to the rules below.
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
+
+### Printing
+
+The built-in function `print` can be used to output formatted debug information. The runtime may omit print function calls depending on its execution mode. The runtime may choose where the output of a print function goes.
+
+`print` is a variadic function, accepting one or more values to be printed; values are separated by a single whitespace character (`" "`).
+
+The return value of `print` is always `true` to allow it to be used in boolean expressions.
+
+```sentinel
+print("hello") // "hello"
+print("hello", "world") // "hello world"
+print("The", "number", "is", 42) // "The number is 42"
+print([1, 2, 3]) // "[1, 2, 3]"
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // "false"
+```
+
+### Errors
+
+The built-in function `error` is equivalent to `print` but immediately causes execution to halt and the main rule to evaluate to `false`. The program execution is considered to have failed.
+
+## Program Execution
+
+Program execution begins by evaluating the source top to bottom. If evaluation is successful, the `main` rule is evaluated and its result is returned. Execution can be interrupted at any time by an error, which can be called explicitly or implicitly through illegal or undefined behavior.
+
+---
+
+> The content of this page is licensed under the [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
+>
+> Based on [The Go Programming Language Specification](https://golang.org/ref/spec) licensed under [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/language/undefined.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/language/undefined.mdx
new file mode 100644
index 0000000000..dc51531783
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/language/undefined.mdx
@@ -0,0 +1,97 @@
+---
+page_title: Sentinel Language - Undefined
+sidebar_current: docs-language-undefined
+description: >-
+ The value `undefined` is a special value representing undefined behavior or an
+ unknown value. Any operation on an `undefined` value results in `undefined`.
+layout: docs
+---
+
+# Language: Undefined
+
+The value `undefined` is a special value representing undefined behavior
+or an unknown value.
+
+Undefined is not an error because there are situations where the undefined
+value can be safely ignored and the language also provides construts for
+recovering from an undefined value.
+
+The undefined value can be created manually with `undefined`. In most cases,
+undefined is created by accessing a non-existent list element or value.
+The undefined value is created in a number of other circumstances. The documentation
+will note when an undefined value may be created.
+
+Almost any operation with `undefined` results in `undefined`. For example,
+performing math on undefined, attempting to call a function that is undefined,
+etc.
+
+## Main and Undefined
+
+If the value `undefined` is returned from the top-level `main` rule, then
+the policy is considered `false`. However, the runtime provides mechanisms
+for the host application to determine the policy failure was caused by
+an undefined value and report that to the user.
+
+The `main` rule returning the undefined value should be considered a logical
+error in most cases and should probably be corrected by the policy writer.
+
+## Debugging Undefined
+
+When an `undefined` value is first created (either explicitly or implicitly),
+the runtime will record the position where the undefined was created. This
+location can be used to find logic that could be erroneous.
+
+## Boolean Logic and Undefined
+
+Boolean logic is one way to safely recover from `undefined`. If boolean
+logic can safely determine a result despite an undefined value, that result
+will be returned.
+
+For example: `undefined or true` will result in `true`, because no matter
+what actual value `undefined` could've been if the policy behaved properly,
+the boolean expression would still be true.
+
+Another scenario where `undefined` can be safely ignored is short-circuit
+logic with `and`. `false and undefined` will result in `false`.
+
+The full table of logical operations on `undefined` is below:
+
+```sentinel
+undefined or true = true
+undefined or false = undefined
+undefined or undefined = undefined
+undefined and true = undefined
+undefined and false = undefined
+undefined and undefined = undefined
+undefined xor true = undefined
+undefined xor false = undefined
+undefined xor undefined = undefined
+
+// Short-circuit examples
+false or true or undefined = true
+false or undefined or true = true
+true and false and undefined = false
+true and undefined and false = undefined
+```
+
+## Else Expressions
+
+The `else` expression allows explicit recovery from undefined and is useful
+for setting default values.
+
+`else` is an operator where the left and right hand side are values. If the
+left-hand value is `undefined`, the right-hand value is returned. Otherwise,
+the left-hand value is returned.
+
+Examples:
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else "default"
+
+// In more complex scenarios
+
+foo() else 42 == 12
+a = config.value else "default"
+```
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/language/values.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/language/values.mdx
new file mode 100644
index 0000000000..f932480340
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/language/values.mdx
@@ -0,0 +1,210 @@
+---
+page_title: Sentinel Language - Values
+sidebar_current: docs-language-values
+description: Values are the data that Sentinel policies operate on.
+layout: docs
+---
+
+# Language: Values
+
+Values are the _data_ that Sentinel policies operate on. You can create this
+data yourself, for example by just typing the number `42`, or you may access
+this data from an external source to make policy decisions.
+
+Values have a _type_, which determines what kind of operations can be performed
+on the data. Example types are booleans (true and false), numbers,
+and strings (text).
+
+This page documents all the available value types. You can also
+[convert between types](#type-conversion).
+
+## Boolean
+
+A boolean is a value that is either true or false.
+
+A boolean value is created literally with `true` or `false`.
+
+As a policy language, booleans are central to the behavior of Sentinel.
+Booleans are used as conditions in [if statements](/sentinel/language/conditionals),
+are the result of [rules](/sentinel/language/rules), and more. The ultimate
+result of a Sentinel policy is true or false.
+
+## Integer
+
+An integer is a whole number.
+
+An integer is a 64-bit value. This means it can represent numbers from
+-9,223,372,036,854,775,808 to 9,223,372,036,854,775,808.
+
+Integers are created by typing them out literally with no
+separators (such as a comma). An optional prefix can set a non-decimal base:
+`0` for octal, and `0x` for hexadecimal. In hexadecimal literals, letters
+`a-f` and `A-F` represent values 10 to 15.
+
+Example integers are shown below:
+
+```sentinel
+42
+0600
+0xBadFace
+170141183460469
+```
+
+Integers are used for math and numerical comparison.
+
+## Float
+
+A float is a number with a decimal point. It has an integer part, a decimal
+point, a fractional part, and optionally an exponent part. Example
+floats are shown below:
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+Floats are IEEE-754 64-bit floating point numbers.
+
+## String
+
+Strings are text values.
+
+Strings are created by wrapping text in double quotes, such as `"hello"`.
+Within the quotes, any character may appear except newline and an unescaped
+double quote. The text between the quotes is the value.
+
+Because Sentinel policies must be UTF-8 encoded text, strings themselves are
+UTF-8 encoded text. This means you can put any value UTF-8 character into
+a string, such as `"日本語"`.
+
+You can have a string with a literal double-quote by _escaping_ it with
+a backslash. For example: `"they said \"hello\""` turns into the value
+`they said "hello"`.
+
+Backslash escapes can be used for much more than only escaping a double quote.
+Newlines are `\n`, a literal backslash is `\\`, and many more. The full list
+of available backslash escapes can be found
+[in the language specification](/sentinel/language/spec#string-literals).
+
+Example strings:
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+Strings do not support indexing, however it is possible to achieve a similar
+result through the combination of the [strings](/sentinel/imports/strings)
+standard import and list indexing.
+
+```sentinel playground
+import "strings"
+
+asciistr = "Hello, world!"
+utf8str = "日本語"
+
+utf8split = rule {
+ strings.split(utf8str, "")[2] == "語"
+}
+
+asciisplit = rule {
+ strings.split(asciistr, "")[5] == ","
+}
+
+main = rule { utf8split and asciisplit }
+```
+
+Strings support [slicing](/sentinel/language/slices) to efficiently create
+substrings.
+
+## List
+
+See [Lists](/sentinel/language/lists).
+
+## Map
+
+See [Maps](/sentinel/language/maps).
+
+## Rule
+
+See [Rules](/sentinel/language/rules).
+
+## Function
+
+See [Functions](/sentinel/language/functions).
+
+## Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a
+value of that type. Some examples are shown below followed by a list of exact
+rules for type conversion.
+
+```sentinel
+int(42) // 42
+int("42") // 42
+int(42.8) // 42
+int(true) // 1
+
+float(1.2) // 1.2
+float(1) // 1.0
+float("4.2") // 4.2
+float(true) // 1.0
+
+string("foo") // "foo"
+string(88) // "88"
+string(0xF) // "15"
+string(true) // "true"
+
+bool("true") // true
+bool(1) // true
+bool(-1) // true
+bool(0.1) // true
+bool("false") // false
+bool(0) // false
+```
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/language/variables.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/language/variables.mdx
new file mode 100644
index 0000000000..7516890212
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/language/variables.mdx
@@ -0,0 +1,99 @@
+---
+page_title: Sentinel Language - Variables
+sidebar_current: docs-language-vars
+description: Variables store values that can be used later.
+layout: docs
+---
+
+# Language: Variables
+
+Variables store values that can be used later.
+
+Most notably, a variable assignment of `main` is required for all
+Sentinel policies. This is the main [rule](/sentinel/language/rules) that
+is executed to determine the result of a policy. The `main` rule may
+use other variables that are assigned in the policy.
+
+Example:
+
+```sentinel
+a = 1
+b = a + 1
+```
+
+In this example, two variables are assigned: `a` and `b`. `a` is given
+the value of "1" and `b` uses the `a` to calculate its own value.
+
+## Syntax
+
+The syntax for assigning a variable is:
+
+```sentinel
+IDENTIFIER = VALUE
+```
+
+On the left of the equal sign is an _identifier_. This is a name for your
+variable and will be how you reference it later. An identifier is any
+combination of letters and digits, but must start with a letter. An
+underscore `_` is also a valid letter.
+
+On the right is any valid Sentinel value or expression. A value is a
+literal value such as a number or string. An expression is some computed
+value such as doing math, calling a function, etc.
+
+## Assignment and Reassignment
+
+A variable is _assigned_ when it is given a value. You can also _reassign_
+variables at any time by setting it to a new value. The new value takes
+effect for any subsequent use of that variable. A variable can be reassigned
+to a different type.
+
+For example:
+
+```sentinel
+a = 1 // a = 1
+b = a // b = 1
+a = "value" // a = "value", b = 1
+c = a // c = "value", b = 1
+```
+
+In the above example you can see that the variables are set and reassigned.
+Notice that the value of a variable is the _current_ value, and that reassigning
+a variable only affects _future_ uses of that variable. You can see this with
+`c` and `b` being different values.
+
+## Unassigned Variables
+
+Using a variable that is _unassigned_ is an error.
+
+In the example below, the first line would result in the policy erroring.
+Sentinel is executed top-down and the value of `c` is not available yet
+on the first line.
+
+```sentinel
+a = c // Error!
+c = 1
+```
+
+## Type Conversion
+
+While a topic more related to [values][lang-values], variables can be assigned
+(or-reassigned) a different value type via type conversion.
+
+[lang-values]: /sentinel/language/values
+
+```sentinel
+s = "1.1"
+a = int(s) // a = 1
+a = float(s) // a = 1.1
+
+a = 1
+s = string(a) // s = "1"
+```
+
+For more details, see the type conversion sections in the
+[values][lang-values-type-conversion] page, or the [Sentinel Language
+Specification][lang-spec-type-conversion].
+
+[lang-values-type-conversion]: /sentinel/language/values#type-conversion
+[lang-spec-type-conversion]: /sentinel/language/spec#type-conversion
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/nomad.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/nomad.mdx
new file mode 100644
index 0000000000..a105b0337d
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/nomad.mdx
@@ -0,0 +1,51 @@
+---
+page_title: Nomad and Sentinel
+sidebar_current: docs-app-nomad
+description: >-
+ Nomad Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies can currently execute on job
+ submission (creation, update).
+layout: docs
+---
+
+# Nomad
+
+Nomad is a simple, flexible scheduler and workload orchestrator.
+[Nomad Enterprise](https://www.hashicorp.com/products/nomad/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies can currently execute on job submission (creation, update).
+
+Sentinel policies have full access to the job structure. This allows the
+Sentinel policy to control behavior based on any attribute within a job,
+such as the driver, resource requests, network configuration, volume
+configuration, and more. The information that Sentinel policies have access
+to will expand over time.
+
+Nomad fully supports all [enforcement levels](/sentinel/concepts/enforcement-levels).
+For soft mandatory policies, the `sentinel-override` capability must be
+available on the user's ACL policy to allow override. Overrides are always
+logged.
+
+The Nomad integration with Sentinel is documented in depth in the
+[Nomad Enterprise documentation](https://www.nomadproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Only allow Docker-based jobs.**
+
+```sentinel
+# Test policy only allows Docker based tasks
+main = rule { all_drivers_docker }
+
+# all_drivers_docker checks that all the drivers in use are Docker
+
+all_drivers_docker = rule {
+ all job.task_groups as tg {
+ all tg.tasks as task {
+ task.driver is "docker"
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/terraform.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/terraform.mdx
new file mode 100644
index 0000000000..5922fd0cfb
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/terraform.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Terraform and Sentinel
+sidebar_current: docs-app-terraform
+description: >-
+ Sentinel can be used with Terraform and Terraform Enterprise to enforce policy
+ on Terraform configurations, states, and plans.
+layout: docs
+---
+
+# Terraform
+
+[Terraform Enterprise](https://www.hashicorp.com/products/terraform/) uses Sentinel to enforce
+policy on [Terraform](https://www.terraform.io) configurations, states, and plans.
+
+The Sentinel integration with Terraform runs within
+[Terraform Enterprise](https://www.hashicorp.com/products/terraform/)
+after a `terraform plan` and before a `terraform apply`. The policies
+have access to the created plan, the state at the time of the plan,
+and the configuration at the time of the plan.
+
+The Terraform integration with Sentinel is documented in depth in the [Terraform Enterprise documentation](https://www.terraform.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show basic examples.
+
+### Examples
+
+**Example: All AWS instances must have a tag**
+
+```sentinel
+import "tfplan"
+
+main = rule {
+ all tfplan.resources.aws*instance as *, instances {
+ all instances as \_, r {
+ (length(r.applied.tags) else 0) > 0
+ }
+ }
+}
+```
+
+**Example: Only allow GCP instance sizes smaller than `n1-standard-16`**
+
+```sentinel
+import "tfplan"
+
+allowed_machine_types = [
+ "n1-standard-1",
+ "n1-standard-2",
+ "n1-standard-4",
+ "n1-standard-8",
+]
+
+main = rule {
+ all tfplan.resources.google_compute_instance as _, instances {
+ all instances as _, r {
+ r.applied.machine_type in allowed_machine_types
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/vault.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/vault.mdx
new file mode 100644
index 0000000000..6db2f8af22
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/vault.mdx
@@ -0,0 +1,64 @@
+---
+page_title: Vault and Sentinel
+sidebar_current: docs-app-vault
+description: >-
+ Vault Enterprise uses Sentinel to augment the built-in policy system to
+ provide Role Governing Policies (RGPs) and Endpoint Governing Policies (EGPs)
+ to enable complex, flexible policies across identities and endpoints.
+layout: docs
+---
+
+# Vault
+
+[Vault Enterprise](https://www.hashicorp.com/products/vault/) uses Sentinel
+to augment the built-in policy system to provide Role Governing Policies (RGPs)
+and Endpoint Governing Policies (EGPs) to enable complex, flexible policies
+across identities and endpoints.
+
+**Role Governing Policies (RGPs)** are Sentinel policies that are tied to
+particular tokens, Identity entities, or Identity groups. They have access to
+a rich set of controls across various aspects of Vault. These are evaluated
+whenever a token they're attached to is used.
+
+**Endpoint Governing Policies (EGPs)** are Sentinel policies that are tied to
+particular paths instead of tokens. They have access to as much request
+information as possible, but they can take effect even on unauthenticated
+paths, such as login paths.
+
+The Vault integration with Sentinel is documented in depth in the
+[Vault Enterprise documentation](https://www.vaultproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Endpoint policy that requires MFA authentication from a corporate network.**
+
+```sentinel
+import "sockaddr"
+
+# We expect logins to come only from our private IP range
+cidrcheck = rule {
+ sockaddr.is_contained(request.connection.remote_addr, "10.20.0.0/16")
+}
+
+# Require Ping MFA validation to succeed
+ping_valid = rule {
+ mfa.methods.ping.valid
+}
+
+main = rule when request.path is "auth/ldap/login" {
+ ping_valid and cidrcheck
+}
+```
+
+**Example: Endpoint policy that disallows tokens created before a certain time.**
+
+```sentinel
+import "time"
+import "strings"
+
+main = rule when not strings.has_prefix(request.path, "auth/ldap/login") {
+ time.load(token.creation_time).unix > time.load("2017-09-17T13:25:29Z").unix
+}
+```
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/writing/basic.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/writing/basic.mdx
new file mode 100644
index 0000000000..b62533466e
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/writing/basic.mdx
@@ -0,0 +1,113 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-basic
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Policy Basics
+
+Sentinel policies are easy to write while still supporting advanced constructs
+for creating complex policies. This page will explain the basics of writing
+Sentinel policies to get started. You don't need any prior Sentinel knowledge,
+but we do recommend reading the
+[getting started guide](/sentinel/intro/getting-started/first-policy) and
+[language guide](/sentinel/language) after this.
+
+Sentinel policies are text files written using the [Sentinel language](/sentinel/language).
+The policies are evaluated top-to-bottom. The value of `main` after execution
+determines whether a policy passes or fails.
+
+## The Simplest Policy
+
+Sentinel only requires that a policy have a `main` variable that evaluates to
+a boolean value.
+
+A valid example is shown below:
+
+```sentinel playground
+main = 10 > 5
+```
+
+This type of minimal policy is not purely academic. In practice, simple
+policies can often be reduced to a single line logical statement resulting
+in `true` or `false`. However, the expression is usually wrapped in a
+[rule](/sentinel/language/rules) for testing reasons.
+
+You can verify Sentinel will execute this minimal policy using the CLI:
+
+```
+$ sentinel apply minimal.sentinel
+Pass
+```
+
+## Logical Expressions
+
+Policy is at its core a set of logic: you can or can not perform some action
+under a certain set of circumstances. Those circumstances are logical
+expressions. Therefore, Sentinel policies primarily translate into logical
+expressions.
+
+Detailed documentation on boolean expressions is
+[available in the language guide](/sentinel/language/boolexpr).
+
+A simple numerical comparison was seen in the first example on this page.
+Sentinel also provides inclusion operators such as `contains`, `any`, `all`, and
+more. Sentinel allows some operators to have aliases to promote readability
+while remaining programmer-familiar, such as `==` which can equivalently be `is`.
+
+The example below verifies that all numbers in a list are even:
+
+```sentinel playground
+numbers = [6, 22, 8, 4, 12]
+main = all numbers as n { n % 2 is 0 }
+```
+
+## Variables
+
+A policy will very often use variables. Applications such as
+[Nomad](https://www.nomadproject.io) inject variables into the global
+scope of a policy for making policy decisions. For example, Nomad injects
+the job that is being run into the policy scope. Knowing how to use
+variables is critical to effectively using Sentinel.
+
+Detailed documentation on how to define and access variables is
+[available in the language guide](/sentinel/language/variables).
+
+Variables can be defined and used explicitly. For example:
+
+```sentinel playground
+value = 10
+main = value > 5
+```
+
+But they may also be introduced implictly by the host system. Nomad
+injects `job` into policies to describe the job that is being run. The
+policy below is a valid policy that requires a job have two task groups.
+Notice that `job` is not defined anywhere. It is implicitly inserted by
+the host application (in this case Nomad). Refer to the application you're
+writing policy for to determine if it implicitly inserts values.
+
+```json sentinel
+{
+ "policy": "main = length(job.task_groups) is 2",
+ "globals": {
+ "job": {
+ "task_groups": ["foo", "bar"]
+ }
+ }
+}
+```
+
+## And more!
+
+The Sentinel language supports many more features such as functions,
+loops, and more. You can learn about all of this in the
+[complete language guide](/sentinel/language).
+
+The other pages in the [writing policy](/sentinel/writing)
+will cover other information you need to know about writing Sentinel
+policies that isn't simply a language reference.
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/writing/debugging.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/writing/debugging.mdx
new file mode 100644
index 0000000000..7dc284d222
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/writing/debugging.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-debugging
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Debugging
+
+As policies become more complex, it becomes harder to understand exactly why
+a policy may be behaving differently than you expect.
+
+Prior to actively debugging, there are multiple best practices we recommend
+following. We recommend breaking your policy down into a set of
+[rules](/sentinel/writing/rules). This will make it easier to understand
+[traces](/sentinel/writing/tracing) and make it easier to
+[test](/sentinel/writing/testing) your policies.
+This should help to debug policies up to a significant complexity.
+
+## Print Logging
+
+The built-in [print function](/sentinel/language/logging-errors#print)
+can be used to log data. The print output is only shown during failures
+or when [tracing is explicitly enabled](/sentinel/writing/tracing).
+
+The `print` function outputs each argument, which can be of any type, converted
+to a human-friendly string format. Arguments are separated with a single space.
+
+Example:
+
+```sentinel playground
+value = 42
+print("the number is", value) // the number is 42
+
+object = { "foo": false }
+print(object) // { "foo": false }
+
+main = rule { true }
+```
+
+The `print` function returns the value `true` so that it can also be
+used within rule expressions. Note that the `print` function should be
+used at the beginning of statements otherwise they may never be
+reached. This is due to internal performance techniques within Sentinel
+like [short-circuiting](https://en.wikipedia.org/wiki/Short-circuit_evaluation).
+
+Example:
+
+```sentinel playground
+// rule_a and rule_b will evaluate to false
+rule_a = rule { print("rule_a is printed") and false } // "rule_a is printed"
+rule_b = rule { false and print("rule_b is printed") } // Nothing is printed
+
+// rule_c and rule_d will evaluate to true
+rule_c = rule { print("rule_c is printed") or true } // "rule_c is printed"
+rule_d = rule { true or print("rule_d is printed") } // Nothing is printed
+
+main = rule { rule_a or rule_b or rule_c and rule_d }
+```
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/writing/imports.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/writing/imports.mdx
new file mode 100644
index 0000000000..c2d99581eb
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/writing/imports.mdx
@@ -0,0 +1,123 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom import](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only local
+context for making policy decisions.
+
+Sentinel also comes with a set of [standard imports](/sentinel/imports).
+Standard imports are available to every Sentinel policy to help policy writers
+with common tasks such as working with the time, network addresses, and more.
+
+-> **This page is about writing policies that _use_ imports.** If you're
+interested in _creating_ a new import, please see the section on [extending
+Sentinel](/sentinel/extending) for information on how to write modules and
+import plugins.
+
+## Using Imports
+
+To use an import, you use the `import` keyword at the top of your policy. This
+specifies the name of the import you want to use. The application you're writing
+the policy for must already be
+[configured](/sentinel/extending/plugins#installing-plugins) to provide that
+import.
+
+Details on imports can be found in the
+[import section in the language reference](/sentinel/language/imports).
+
+In the example below, we use the [time](/sentinel/imports/time) import:
+
+```sentinel playground
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+There are two options to develop a policy locally when using imports:
+configure Sentinel to launch the import on `apply`, or mock the import
+using `mock`. The former requires access to the import plugin while the
+latter is faster (doesn't have to launch a process) and doesn't require
+the plugin.
+
+## Mocking Imports
+
+The first option to developing policies locally is to mock the import values.
+When mocking an import, you don't need the import plugin available. This can be
+useful since some imports may not be available as a plugin and may only be
+available to the application the policy runs in.
+
+Mocks are specified via the [configuration
+file](/sentinel/configuration). Mocks can also be used for
+[testing](/sentinel/writing/testing).
+
+You can supply mock configuration one of two ways, depending on your use case:
+
+- **[Using static data](/sentinel/configuration#mocking-static-data):**
+ Use this method when you can accurately represent your mock data in JSON and
+ do not need to mock complex Sentinel features such as functions.
+- **[Using Sentinel code](/sentinel/configuration#mocking-with-sentinel-code):**
+ Use this method when using a static JSON object is insufficient, such as when
+ you need to mock functions or other complex Sentinel features.
+
+Our example does not require complex data to be mocked, so a static JSON object
+is sufficient:
+
+```json
+{
+ "mock": {
+ "time": {
+ "now": {
+ "hour": 12,
+ "weekday_name": "Tuesday"
+ }
+ }
+ }
+}
+```
+
+This can be used via the CLI:
+
+```
+$ sentinel apply -config=config.json policy.sentinel
+Pass
+```
+
+## Launching Imports
+
+If you have access to the plugin binary, you can launch the import. The
+benefit of this is that it is really using the import to test your
+policy. If the import changes, your policies may start failing. If you
+only use mock data and the import changes, your policies will still
+appear to work.
+
+Imports are configured in the configuration file:
+
+```json
+{
+ "imports": {
+ "time": {
+ "path": "/path/to/sentinel-time-import"
+ }
+ }
+}
+```
+
+This would require the `sentinel-time-import` binary. For this example
+this doesn't currently exist. We plan on writing one to provide for this
+section of the documentation.
+
+## Custom Imports
+
+You can also [create your own imports](/sentinel/extending).
+
+If your policy decisions could benefit from accessing external information,
+then you can use custom imports as a way to do this.
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/writing/index.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/writing/index.mdx
new file mode 100644
index 0000000000..064069d389
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/writing/index.mdx
@@ -0,0 +1,35 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing
+description: >-
+ Sentinel provides a language and workflow for building policy across any
+ system that embeds Sentinel. By learning Sentinel once, you are able to
+ effectively control access to many systems using Sentinel's powerful features.
+ Basic concepts that are important to understand for Vault usage.
+layout: docs
+---
+
+# Writing Sentinel Policy
+
+This section covers how to write Sentinel policies.
+
+It is recommended that you complete the
+[getting started guide](/sentinel/intro/getting-started/first-policy) prior to
+reading this section of the documentation. The getting started guide will
+explain all the basics of writing and testing policies. This section of the
+documentation will serve as more of a reference guide to all available
+features of Sentinel.
+
+Sentinel provides a language and workflow for building policy across any system
+that embeds Sentinel. By learning Sentinel once, you are able to effectively
+control access to many systems using Sentinel's powerful features.
+
+Sentinel also provides a [local CLI](/sentinel/commands) for developing and testing
+Sentinel policies. This CLI can be integrated into a daily developer's workflow
+along with CI to treat [policy as code](/sentinel/concepts/policy-as-code).
+
+Sentinel uses its own [language](/sentinel/language) for writing
+policies. You can view a [language reference](/sentinel/language)
+as well as the [specification](/sentinel/language/spec) for details. You
+don't have to read those documents immediately, since the language should
+be easy enough to pick up throughout this section.
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/writing/rules.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/writing/rules.mdx
new file mode 100644
index 0000000000..ab4fc69d08
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/writing/rules.mdx
@@ -0,0 +1,65 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-rules
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Rules
+
+Rules form the basis of a policy by representing behavior that is either passing
+or failing. Rules are a first class language construct in Sentinel. A policy can
+and should be broken down into rules to aid with readability, testability, and
+performance.
+
+Rules provide:
+
+- **Readability:** A policy that is broken down into rules can be read
+ more easily. The logic becomes clearer to see for policy writers when
+ they come back to it.
+
+- **Reportability:** When [tracing](/sentinel/writing/tracing) is enabled,
+ rules form the foundation of the data returned. All evaluated rules are added
+ to the trace with the data the rule evaluated to, and their position within
+ policy or module source files.
+
+- **Testability:** [Policy testing](/sentinel/writing/testing) is
+ built on asserting the values of rules. By breaking logic down into
+ rules, you're able to more effectively test your policies.
+
+- **Performance:** Rules are only evaluated on demand, and are also only
+ evaluated once. This means that a rule referenced multiple times only has a
+ one-time performance cost. For complex logic, this could result in improved
+ performance.
+
+An example usage of rules is shown below:
+
+```json sentinel
+{
+ "policy": "is_sunny = rule { weather is \"sunny\" }\nis_wednesday = rule { day is \"wednesday\" }\nmain = rule { is_sunny and is_wednesday }",
+ "globals": {
+ "weather": "sunny",
+ "day": "wednesday"
+ }
+}
+```
+
+Rules can also contain non-boolean data. This can be useful for passing more
+detail to the caller than an opaque boolean value would be able to communicate.
+
+```sentinel playground
+days = [{"weather": "sunny"}]
+main = rule {
+ filter days as d {
+ d.weather is not "sunny"
+ }
+}
+```
+
+Details about rules and their behavior can be found in
+[the language reference](/sentinel/language/rules). Rules have important
+behavior so we recommend reading the language reference on rules.
+Rules will be used throughout the remainder of the documentation.
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/writing/testing.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/writing/testing.mdx
new file mode 100644
index 0000000000..61ab0ea46c
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/writing/testing.mdx
@@ -0,0 +1,491 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-testing
+description: >-
+ Sentinel has a built-in test framework to validate a policy behaves as
+ expected.
+layout: docs
+---
+
+# Testing
+
+Sentinel has a built-in test framework to validate a policy behaves as expected.
+
+With an ever-increasing amount of automation surrounding technology,
+the guardrails provided by policies are a critical piece towards ensuring
+expected behavior. As a reliance on correct policy increases, it is important
+to test and verify policies.
+
+Testing is a necessary step to fully realize
+[policy as code](/sentinel/concepts/policy-as-code). Just as good software
+is well tested, a good set of policies should be equally well tested.
+
+## Test Folder Structure
+
+Policies are tested by asserting that [rules](/sentinel/writing/rules)
+are the expected values given a pre-configured input. Tests are run
+by executing the [test command](/sentinel/commands/test).
+
+Sentinel is opinionated about the folder structure required for tests.
+This opinionated structure allows testing to be as simple as running
+`sentinel test` with no arguments. Additionally, it becomes simple to test
+in a CI or add new policies.
+
+The structure Sentinel expects is `test//*.[hcl|json]` where ``
+is the name of your policy file without the file extension. Within that folder
+is a list of HCL or JSON files. Each file represents a single test case.
+Therefore, each policy can have multiple tests associated with it.
+
+-> Note that the primary configuration file format for Sentinel is HCL. While
+you can write configuration files in a HCL-equivalent JSON, we only discuss the
+use of HCL on this page.
+
+## Test Case Format
+
+Each HCL file within the test folder for a policy is a single test case.
+
+The file is the same configuration format as the [CLI configuration
+file](/sentinel/configuration). The format lets you define mock data, imports to
+use, and more. This mock data is the key piece in being able to test policies:
+you craft a specific scenario and assert your policy behaves as you expect.
+
+Test cases also use the `test` block within the configuration file to assert the
+value of [rules](/sentinel/writing/rules). If the `test` key is omitted, the
+policy is expected to pass. If the test key is specified, only the rules
+specified in the map will be asserted. This means if you omit `main`, then the
+final policy result is not asserted.
+
+Example with assertions:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+
+test {
+ rules = {
+ main = false
+ is_open_hours = false
+ is_weekday = true
+ }
+}
+```
+
+The configuration above specifies some parameter data, and asserts the result of
+some rules. This is the same configuration used in the example section below.
+
+## Example
+
+Lets use the following file as an example. Save this file to a directory
+and name it `policy.sentinel`. It can be named anything with the `sentinel`
+extension, but by naming it `policy.sentinel` your output should match
+the example output on this page.
+
+```sentinel
+// The day of the week.
+param day
+
+// The hour of the day.
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+### A Passing Test
+
+Next, let's define a single test case. Relative to where you saved
+the policy, create a file at the path `test/policy/good.hcl`.
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 14
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/good.hcl
+```
+
+This passed because the policy passed. We didn't assert any specific
+rules. By not specifying any assertions, `test` expects the policy itself
+to fully pass.
+
+### A Failing Test
+
+Define another test case to fail. We want to verify our policy fails when expected, too.
+
+Save the following as `test/policy/7-am.hcl`:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+FAIL - policy.sentinel
+ FAIL - test/policy/7-am.hcl
+ expected "main" to be true, got: false
+
+ trace:
+ policy.sentinel:9:1 - Rule "main"
+ bool: false
+
+ policy.sentinel:8:1 - Rule "is_open_hours"
+ bool: false
+
+ PASS - test/policy/good.hcl
+```
+
+As you can see, the test fails because "main" is false. This is good
+because the policy _should_ have failed since we specified an invalid
+hour. But, we expect main to be false and don't want our test to fail!
+Update `7-am.hcl` to add test assertions:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+
+test {
+ rules = {
+ main = false
+ is_open_hours = false
+ }
+}
+```
+
+And when we run the tests:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/7-am.hcl
+ PASS - test/policy/good.hcl
+```
+
+The test passes. We asserted that we expect the `main` rule to be false,
+the `is_open_hours` rule to be false, and the `is_weekday` rule to be
+true. By asserting some rules are true, we can verify that our policy
+is failing for reasons we expect.
+
+## Mocking
+
+The above example demonstrates how to test by supplying different
+[parameters](/sentinel/language/parameters). Parameters in a policy can be
+specifically useful when you want to control user-defined input values to a
+policy.
+
+However, generally, when testing, you will need mimic the conditions you will
+see in production. Production implementations of Sentinel will supply data using
+one of two methods:
+
+- **Global data:** Data is injected directly into the policy's scope and is
+ accessible using normal identifiers, similar to variables.
+- **Imports:** Data is stored behind an [import](/sentinel/language/imports)
+ and loaded on demand as needed by the policy author.
+
+Proper testing of a policy requires that these values be able to be _mocked_ -
+or, in other words, simulated in a way that allows the accurate testing of the
+scenarios that a policy could reasonably pass or fail under.
+
+Mocking both globals and imports can be done by setting various parts of the
+configuration file.
+
+### Mocking Globals
+
+Demonstrating the mocking of globals can be seen by making a few modifications to
+our example policy, removing the `param` declarations:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Then, change the `param` section in the configuration file to
+[`global`](/sentinel/configuration#global).
+
+```json
+global "day" {
+ value = "monday"
+}
+
+global "hour" {
+ value = 14
+}
+```
+
+This test should still pass, as if nothing had happened, although what we've
+done is shifted our parameters to globals, simulating an environment where `day`
+and `hour` are already defined for us.
+
+### Mocking Imports
+
+To mock imports, we need to use the
+[`mock`](/sentinel/configuration#mock-imports) section of the
+configuration file.
+
+Let's say the above example is behind an import named `time`.
+
+-> **NOTE:** [`time`](/sentinel/imports/time) is a valid standard import.
+This example may not be accurate to the
+import's syntax.
+
+The code now looks like this:
+
+```sentinel
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To mock this import, we can [mock it as static
+data](/sentinel/configuration#mocking-static-data). The configuration
+file now looks like, without assertions:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ weekday_name = "Monday"
+ hour = 14
+ }
+ }
+}
+```
+
+The policy will now pass, with the `time` import mocked.
+
+Data can also be [mocked as Sentinel
+code](/sentinel/configuration#mocking-with-sentinel-code). In this case,
+the above configuration file would look like:
+
+```hcl
+mock "time" {
+ module {
+ source = "mock-time.sentinel"
+ }
+}
+```
+
+And a file named `mock-time.sentinel` would now hold your mock values:
+
+```sentinel
+day = "monday"
+hour = 14
+```
+
+Mocking as Sentinel code allows more complex details to be mocked as well, such
+as functions. Say we wanted to mock the `time.load()` function. To mock this,
+just add it to the `mock-time.sentinel` file:
+
+```sentinel
+load = func(_) {
+ return {
+ "weekday_name": "Monday",
+ "hour": 14,
+ }
+}
+```
+
+Your code can now be written as:
+
+```sentinel
+import "time"
+
+t = time.load("a_mock_timestamp")
+
+is_weekday = rule { t.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { t.hour > 8 and t.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To see more details, see the [Mock
+Imports](/sentinel/configuration#mock-imports) section in the
+configuration file.
+
+## Asserting Non-Boolean Rules
+
+Non-boolean rules can also be asserted by `sentinel test`.
+
+To assert non-boolean values, simply enter the expected value into the rule
+contents:
+
+```hcl
+param "maintenance_days" {
+ value = [
+ {
+ day = "wednesday"
+ hour = 9
+ },
+ {
+ day = "friday"
+ hour = 1
+ },
+ {
+ day = "sunday"
+ hour = 1
+ },
+ ]
+}
+
+test {
+ rules = {
+ main = [
+ {
+ day = "wednesday"
+ hour = 9
+ },
+ ]
+ }
+}
+```
+
+This would assert a policy checking for violations of a maintenance policy,
+saying that maintenance hours should happen before 6AM on any given day:
+
+```sentinel
+param maintenance_days
+
+main = rule {
+ filter maintenance_days as d {
+ d.hour >= 6
+ }
+}
+```
+
+## Test JSON Output
+
+Running `sentinel test` with the `-json` flag will give you the test results in
+a JSON output format, suitable for parsing by reporting software.
+
+-> **Note:** The JSON output format is currently under development and the
+format may change at a later time. Additionally, support for other well-known
+formats, such as JUnit, may become available in the future.
+
+The current format is an object with the only top-level key being `policies`,
+with each test grouped up by policy being run.
+
+The policy result fields are:
+
+- `path`: The path of the policy and the index of the test result in the
+ `policies` field in the root object.
+- `status`: A string representation of the policy's test status as a whole. Can
+ be one of `PASS`, `FAIL`, `ERROR`, or `?`. The final status, `?`, represents a
+ policy that has no tests to process, and acts like a passing test.
+- `errors`: An array of any error messages encountered during processing the
+ policy for testing. Usually reserved for policy file or parser-related errors.
+ For case-specific errors, see the error field for the particular case.
+- `cases`: A map of case results, indexed by case path.
+
+The case result fields are:
+
+- `path`: The path of the test case and the result's index in the `cases` field
+ in the policy object.
+- `status`: A string representation of the case's test status. Can be one of
+ `PASS`, `FAIL` or `ERROR`.
+- `errors`: An array of any error messages encountered during running this test
+ case.
+- `trace`: The trace for this policy in JSON format. See the
+ [tracing](/sentinel/writing/tracing) page for more details.
+- `rule_detail`: When the `status` of this test case is `FAIL`, contains an
+ object of assertion failure detail, indexed by rule. Only failures are counted
+ here; any rules not found here can be assumed to have passed or not asserted.
+- `config_warnings`: An array of strings denoting any configuration warnings
+ found while processing the configuration for this test case.
+- `config_legacy`: Denotes whether or not the configuration is a legacy JSON
+ configuration and needs to be modernized. This field may be removed in future
+ releases.
+
+A passing example is shown below:
+
+```json
+{
+ "policies": {
+ "policy.sentinel": {
+ "path": "policy.sentinel",
+ "status": "PASS",
+ "errors": null,
+ "cases": {
+ "test/policy/pass.hcl": {
+ "path": "test/policy/pass.hcl",
+ "status": "PASS",
+ "errors": null,
+ "trace": {
+ "description": "A very basic policy to determine if a run is within working hours.",
+ "error": null,
+ "print": "",
+ "result": true,
+ "rules": {
+ "is_open_hours": {
+ "desc": "Passes if run during business hours.",
+ "ident": "is_open_hours",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 290,
+ "line": 13,
+ "column": 1
+ },
+ "value": true
+ },
+ "is_weekday": {
+ "desc": "Passes if the day does not fall on the weekend.",
+ "ident": "is_weekday",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 193,
+ "line": 10,
+ "column": 1
+ },
+ "value": true
+ },
+ "main": {
+ "desc": "",
+ "ident": "main",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 338,
+ "line": 14,
+ "column": 1
+ },
+ "value": true
+ }
+ }
+ },
+ "rule_detail": {},
+ "config_warnings": null,
+ "config_legacy": false
+ }
+ }
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.18.x/content/sentinel/docs/writing/tracing.mdx b/content/sentinel/v0.18.x/content/sentinel/docs/writing/tracing.mdx
new file mode 100644
index 0000000000..f2069bd682
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/docs/writing/tracing.mdx
@@ -0,0 +1,401 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-tracing
+description: >-
+ Sentinel provides execution traces to better understand the execution of a
+ policy.
+layout: docs
+---
+
+# Traces
+
+Sentinel provides execution traces to better understand the execution of a policy.
+
+When using the Sentinel [CLI](/sentinel/commands), traces are shown
+on policy failure during `apply` or `test`, or when the `-trace` flag is
+explicitly specified. For Sentinel-enabled applications, traces are optional
+and may require special configuration to enable.
+
+The availability of the trace may vary from application to application. While
+some applications may only log traces on failure, others, such as [Terraform
+Cloud](https://www.terraform.io/cloud), log the trace on every execution. For
+more details, consult your implementation's documentation.
+
+-> **Note:** We're actively improving Sentinel tracing in each release
+to provide better data and improve readability. The exact format of a
+trace may not match the Sentinel version embedded in the application you're
+using, but the data should be similar. The canonical format for a trace should
+get more stable as we approach a 1.0 release.
+
+## Analyzing a Trace
+
+To show traces, let's use the example policy below with the CLI:
+
+```sentinel
+param day
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+
+main = rule { is_open_hours and is_weekday }
+```
+
+Let's cause the policy to fail so the trace is more interesting.
+If you're on a terminal that supports it, the trace output will be colored
+so you can more clearly see passes, failures, and rules.
+
+
+
+ $ sentinel apply main.sentinel{'\n'}
+
+ main.sentinel:1:7: requires value for parameter day
+
+ {'\n'}
+ {'\n'}
+ {'\n'}
+ {' '}Values can be strings, floats, or JSON array or object values. To force
+ {'\n'}
+ {' '}strings, use quotes.{'\n'}
+ {'\n'}
+ {' '}Enter a value: sunday{'\n'}
+ {'\n'}
+
+ main.sentinel:2:7: requires value for parameter hour
+
+ {'\n'}
+ {'\n'}
+ {'\n'}
+ {' '}Values can be strings, floats, or JSON array or object values. To force
+ {'\n'}
+ {' '}strings, use quotes.{'\n'}
+ {'\n'}
+ {' '}Enter a value: 14{'\n'}
+ {'\n'}
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+
+ Fail - main.sentinel
+
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:7:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}
+ false
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:5:1 - Rule "is_open_hours"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}true{'\n'}
+ {'\n'}
+
+ main.sentinel:4:1 - Rule "is_weekday"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}false{'\n'}
+
+
+
+We can see all of our rules neatly and clearly displayed along with the result
+of the policy. If you are getting the trace due to a failed policy and not
+because you explicitly used `-trace`, an informational message is displayed.
+
+As we can see from our basic example, the `main` rule has failed and its result
+is highlighted in red. All peripheral rules that were also evaluated as part of
+the policy are also displayed below the main rule. Source positions are also
+displayed so that you can find the references to the rules in your code.
+
+Via the trace, we can clearly see that while `is_open_hours` returned a true
+result, `is_weekday` did not, and per the logic in our code, the policy is
+rejected.
+
+## Non-Boolean Rules and the Trace
+
+The trace is particularly important when working with rules that return
+non-boolean data, such as rules where you want to see violations instead of a
+simple opaque boolean value. Let's take our example from the [rules](./rules)
+section, and write a small static policy for it:
+
+```sentinel playground
+days = [
+ {"weekday": "Sunday", "weather": "clouds"},
+ {"weekday": "Monday", "weather": "rain"},
+ {"weekday": "Tuesday", "weather": "sunny"},
+ {"weekday": "Wednesday", "weather": "sunny"},
+ {"weekday": "Thursday", "weather": "clouds"},
+ {"weekday": "Friday", "weather": "rain"},
+ {"weekday": "Saturday", "weather": "sunny"},
+]
+
+main = rule {
+ filter days as d {
+ d.weather is not "sunny"
+ }
+}
+```
+
+Here is the output of our policy:
+
+
+
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+ {'\n'}
+
+ Fail - weather.sentinel
+
+ {'\n'}
+ {'\n'}
+
+ weather.sentinel:11:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+
+ {' [\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Sunday"'}
+ {'\n'}
+ {' "weather": "clouds"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Monday"'}
+ {'\n'}
+ {' "weather": "rain"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Thursday"'}
+ {'\n'}
+ {' "weather": "clouds"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Friday"'}
+ {'\n'}
+ {' "weather": "rain"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' ]'}
+
+
+
+
+We can now see all of the weekdays with non-sunny weather.
+
+Note that to prevent formatting issues and excessive output, the human-readable
+trace is limited in the volume of output it will display for collections.
+
+## JSON Output
+
+The trace can also be displayed in JSON by adding `-json` to `sentinel apply`
+and `sentinel test`, and will display the trace in its entirety, unlike the
+standard CLI output.
+
+Here is the result of running `sentinel apply -json` against our weather policy:
+
+```json
+{
+ "can_override": false,
+ "error": null,
+ "policies": [
+ {
+ "allowed_failure": false,
+ "error": null,
+ "policy": "weather.sentinel",
+ "result": false,
+ "trace": {
+ "description": "",
+ "error": null,
+ "print": "",
+ "result": false,
+ "rules": {
+ "main": {
+ "desc": "",
+ "ident": "main",
+ "position": {
+ "filename": "weather.sentinel",
+ "offset": 328,
+ "line": 11,
+ "column": 1
+ },
+ "value": [
+ {
+ "weather": "clouds",
+ "weekday": "Sunday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Monday"
+ },
+ {
+ "weather": "clouds",
+ "weekday": "Thursday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Friday"
+ }
+ ]
+ }
+ }
+ }
+ }
+ ],
+ "result": false
+}
+```
+
+Note that the trace is always included with this output, regardless of whether
+or not the policy passes or fails; using `-trace` is unnecessary.
+
+Additionally, with `sentinel apply`, you can display the value of a single rule
+in JSON, instead of the entire trace. This is done with the `-json-rule` flag.
+
+-> **Note:** `sentinel apply -json-rule` needs to be run either against a single
+on-disk policy, or against a single policy within a policy set. It is ignored
+in full policy set executions and functions exactly like `-json` in these
+scenarios.
+
+Here is the result of executing `sentinel apply -json-rule main weather.sentinel`:
+
+```json
+[
+ {
+ "weather": "clouds",
+ "weekday": "Sunday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Monday"
+ },
+ {
+ "weather": "clouds",
+ "weekday": "Thursday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Friday"
+ }
+]
+```
+
+## Other Trace Details
+
+There are some other details that are good to know when working with the trace:
+
+- Only rules that get executed are added to the trace. Considering our first
+ example where the `main` rule is the expression `is_open_hours and is_weekday`,
+ if our expression was using an `or` operator instead of `and`, in addition to
+ the policy passing, `is_open_hours` would be present in the trace, but
+ `is_weekday` would not be, as the policy would have never evaluated that rule.
+- Descriptions for both rules and policies will show up in the trace as well if
+ present. Here is the output to our first policy with the appropriate
+ annotations:
+
+
+
+ ...{'\n\n'}
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+
+ Fail - main.sentinel
+
+ {'\n'}
+ {'\n'}
+ Description:{'\n'}
+ {' A very basic policy to determine if a run is within working\n'}
+ {' hours.\n'}
+ {'\n'}
+
+ main.sentinel:7:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}
+ false
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:5:1 - Rule "is_open_hours"
+
+ {'\n'}
+ {' '}
+ Description:
+ {'\n'}
+ {' Passes if run during business hours.\n'}
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}true{'\n'}
+ {'\n'}
+
+ main.sentinel:4:1 - Rule "is_weekday"
+
+ {'\n'}
+ {' '}
+ Description:
+ {'\n'}
+ {' Passes if the day does not fall on the weekend.\n'}
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}false{'\n'}
+
+
diff --git a/content/sentinel/v0.18.x/content/sentinel/intro/getting-started/first-policy.mdx b/content/sentinel/v0.18.x/content/sentinel/intro/getting-started/first-policy.mdx
new file mode 100644
index 0000000000..38d7b22c9a
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/intro/getting-started/first-policy.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Your First Sentinel Policy
+sidebar_current: intro-getting-started-first
+description: Let's begin by writing a working Sentinel policy.
+layout: intro
+---
+
+# Your First Sentinel Policy
+
+Sentinel is a system to enforce complex policies on an integrated application.
+
+Writing Sentinel policy requires minimal programming experience. The Sentinel
+language is designed to be approachable and learned quickly and easily. Whether
+you're a professional programmer or someone who uses SQL and Excel, you can
+learn to write Sentinel policies.
+
+Let's begin by writing a simple, working Sentinel policy:
+
+```sentinel playground
+hour = 4
+main = rule { hour >= 0 and hour < 12 }
+```
+
+This is a valid Sentinel policy. It will pass since we hardcoded the
+`hour` to be 4. In a real system, `hour` may be something that is provided
+to us and actually set to the current hour. We'll learn more about that later.
+
+For now, try running this policy locally. Save the above example to a file
+named `policy.sentinel` and execute it. Then, modify the policy to make it
+fail. Play around more if you'd like.
+
+```
+$ sentinel apply policy.sentinel
+Pass
+```
+
+## Main
+
+Every Sentinel policy must have a `main` rule. This is the rule that
+is evaluated to determine the result of a policy.
+
+A rule describes an expression that generally means one of two things:
+
+- Does a policy _pass a condition_ that would authorize an operation? In our
+ above example, describe a policy that checks the supplied hour (4) is within an
+ authorized time window (between 0 - midnight, and 12 noon).
+- Conversely, can a policy find any _violations_ that would block authorization
+ of the operation? Building on the above, consider a policy that takes a
+ schedule, and finds all time blocks that fall outside of the example time window
+ supplied in the above policy.
+
+It is easy to imagine that such a rule might be used in a system such
+as [Nomad](https://www.nomadproject.io) to restrict the times when a
+deploy can occur. The power of arbitrary logical statements within Sentinel
+allows Sentinel policies to restrict almost any behavior.
+
+Next, we'll
+[introduce and explain rules](/sentinel/intro/getting-started/rules)
+so we can use them in our policies.
diff --git a/content/sentinel/v0.18.x/content/sentinel/intro/getting-started/imports.mdx b/content/sentinel/v0.18.x/content/sentinel/intro/getting-started/imports.mdx
new file mode 100644
index 0000000000..dbbb146ecf
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/intro/getting-started/imports.mdx
@@ -0,0 +1,58 @@
+---
+page_title: Imports
+sidebar_current: intro-getting-started-imports
+description: Imports enable Sentinel to access external data and functions.
+layout: intro
+---
+
+# Imports
+
+Imports enable Sentinel to access external data and functions.
+
+Sentinel ships with a set of [standard imports](/sentinel/imports).
+Standard imports are available by default to every Sentinel policy. Note that
+the application embedding Sentinel may allow or deny the available
+set of imports.
+
+To use an import, use the `import` statement. Then, access data and
+functions on the import using the import name followed by a period and
+the field you want to access. The example below checks whether the request
+path starts with a prefix. Assume that `request_path` is available.
+
+```sentinel
+import "strings"
+
+main = rule { strings.has_prefix(request_path, "/admin") }
+```
+
+## External Data
+
+The true power in imports is their ability to reference external data.
+Imports can be added to a Sentinel-enabled application through
+[plugins](/sentinel/extending). This enables any Sentinel-enabled
+application to make policy decision based on any source of data.
+
+This ability allows the policy system to enforce almost any necessary
+organizational policy, since the ability of a policy isn't restricted
+purely to the embedding application's data model.
+
+For example, policies in [Nomad](https://www.nomadproject.io) can
+access data in [Consul](https://www.consul.io) to determine attributes
+of a policy. In the example below, we use a hypothetical Consul import:
+
+```sentinel
+import "consul"
+
+main = rule {
+ job.tasks[0].resources.memory <= int(consul.get("policy/nomad/max-memory"))
+}
+```
+
+In this example, a Nomad job's memory usage is limited to the value of
+a Consul key/value item. By simply changing a Consul KV entry, policy
+can be changed. Imports can do anything, you can [write your own import plugins](/sentinel/extending)
+to extend Sentinel.
+
+Next, we'll learn how to
+[test policies](/sentinel/intro/getting-started/testing)
+to ensure our policy logic is correct.
diff --git a/content/sentinel/v0.18.x/content/sentinel/intro/getting-started/index.mdx b/content/sentinel/v0.18.x/content/sentinel/intro/getting-started/index.mdx
new file mode 100644
index 0000000000..5c9b6b43c5
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/intro/getting-started/index.mdx
@@ -0,0 +1,16 @@
+---
+page_title: Install Sentinel CLI - Getting Started
+sidebar_current: intro-getting-started-overview
+description: The first step to using Sentinel is to get the CLI installed.
+layout: intro
+---
+
+# Getting Started With Sentinel
+
+This section covers getting started with Sentinel. Here, we will take you
+through the first steps that you will need to do to get started with installing
+the Sentinel CLI, writing your first policy, and getting familiar with rules,
+boolean logic, imports, and testing.
+
+You can select a topic on the menu to get started. The first step is to [install
+the Sentinel CLI](/sentinel/intro/getting-started/install).
diff --git a/content/sentinel/v0.18.x/content/sentinel/intro/getting-started/install.mdx b/content/sentinel/v0.18.x/content/sentinel/intro/getting-started/install.mdx
new file mode 100644
index 0000000000..e478fd532f
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/intro/getting-started/install.mdx
@@ -0,0 +1,56 @@
+---
+page_title: Install Sentinel CLI - Getting Started
+sidebar_current: intro-getting-started-install
+description: The first step to using Sentinel is to get the CLI installed.
+layout: intro
+---
+
+# Install Sentinel CLI
+
+Sentinel is a policy framework that is embedded in the enterprise versions of
+HashiCorp tools. The policies you write are deployed to these applications and
+enforced there.
+
+The Sentinel CLI is a command-line interface for local development and testing.
+For the getting started guide, we'll use the CLI to learn how to write policies
+for Sentinel-enabled applications. The Sentinel CLI is distributed as a [binary
+package](/sentinel/downloads) for all supported platforms and architectures.
+
+## Installation Instructions
+
+To install the Sentinel CLI, find the [appropriate package](/sentinel/downloads) for your
+system and download it. The CLI is packaged as a zip archive.
+
+After downloading Sentinel, unzip the package. The CLI runs as a single binary
+named `sentinel`. Any other files in the package can be safely removed and
+Sentinel will still function.
+
+The final step is to make sure that the `sentinel` binary is available on the `PATH`.
+See [this page](https://stackoverflow.com/questions/14637979/how-to-permanently-set-path-on-linux)
+for instructions on setting the PATH on Linux and Mac.
+[This page](https://stackoverflow.com/questions/1618280/where-can-i-set-path-to-make-exe-on-windows)
+contains instructions for setting the PATH on Windows.
+
+## Verifying the Installation
+
+After installing the Sentinel CLI, verify the installation worked by opening a
+new terminal session and checking that the `sentinel` binary is available. By
+executing `sentinel`, you should see help output similar to the following:
+
+```
+$ sentinel
+Usage: sentinel [--version] [--help] []
+
+Available commands are:
+ apply Execute a policy and output the result
+ fmt Format Sentinel policy to a canonical format
+ test Test policies
+ version Prints the Sentinel version
+```
+
+If you get an error that the binary could not be found, then your `PATH`
+environment variable was not setup properly. Please go back and ensure that your
+`PATH` variable contains the directory where Sentinel was installed.
+
+Otherwise, the CLI is installed and ready to go. Let's celebrate by [writing our
+first policy!](/sentinel/intro/getting-started/first-policy)
diff --git a/content/sentinel/v0.18.x/content/sentinel/intro/getting-started/logic.mdx b/content/sentinel/v0.18.x/content/sentinel/intro/getting-started/logic.mdx
new file mode 100644
index 0000000000..d3716a64ea
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/intro/getting-started/logic.mdx
@@ -0,0 +1,54 @@
+---
+page_title: Logic
+sidebar_current: intro-getting-started-logic
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Logic
+
+The core of a policy is a set of logical expressions to determine whether
+a behavior is allowed or not.
+Sentinel makes it easy to write readable logical expressions to express
+the intended policy.
+
+The logical expression syntax will be familiar to anyone who has programmed
+before. Sentinel also supports a couple more unique constructs to assist
+with common policy requirements. Detailed documentation on how to write
+logical expressions and the supported operators is available in
+[the boolean expression reference](/sentinel/language/boolexpr).
+
+Example logic:
+
+```sentinel
+a is b // Equality, you can also use ==
+a is not b // Inequality, you can also use !=
+
+a > b // Relational operators, comparison
+a < b
+a >= b
+a <= b
+
+a contains b // Inclusion check, substrings, etc.
+a not contains b // Negated version of above
+```
+
+The full list of available operators can be found in
+[the boolean expression reference](/sentinel/language/boolexpr).
+
+Logic can be combined with `and` or `or`. When combined with `and`, both
+sides must result in `true`. When combined with `or`, only one side needs
+to be true to result in `true`.
+
+With these building blocks, basic rules can be built up:
+
+```sentinel
+main = rule {
+ hour >= 8 and
+ hour < 17
+}
+```
+
+Next, we'll introduce
+[imports](/sentinel/intro/getting-started/imports)
+so that we can write rules that interact with external data.
diff --git a/content/sentinel/v0.18.x/content/sentinel/intro/getting-started/next-steps.mdx b/content/sentinel/v0.18.x/content/sentinel/intro/getting-started/next-steps.mdx
new file mode 100644
index 0000000000..23b470be59
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/intro/getting-started/next-steps.mdx
@@ -0,0 +1,21 @@
+---
+page_title: Next Steps
+sidebar_current: intro-getting-started-nextsteps
+description: That concludes the getting started guide for Sentinel.
+layout: intro
+---
+
+# Next Steps
+
+That concludes the getting started guide for Sentinel. Hopefully you're excited
+about the possibilities of Sentinel and ready to put this knowledge to use to
+improve the safety of your environments.
+
+We've covered the basics of the core features of Sentinel in this guide.
+We recommend the following as next steps:
+
+- [Documentation](/sentinel) - The documentation is an in-depth
+ reference guide of all the features of Sentinel.
+
+- [Language Reference](/sentinel/language) - The language reference
+ is a complete reference to the features of the Sentine policy language.
diff --git a/content/sentinel/v0.18.x/content/sentinel/intro/getting-started/rules.mdx b/content/sentinel/v0.18.x/content/sentinel/intro/getting-started/rules.mdx
new file mode 100644
index 0000000000..f12a61fe75
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/intro/getting-started/rules.mdx
@@ -0,0 +1,86 @@
+---
+page_title: Rules
+sidebar_current: intro-getting-started-rules
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Rules
+
+Rules in Sentinel are a first-class concept. Within a policy, rules serve a few
+purposes:
+
+- They make complex logic more understandable by allowing said logic to be
+ broken down.
+- They allow assertion of this logic through
+ [testing](/sentinel/intro/getting-started/testing) of the rule's contents.
+- They facilitate reporting of data as rules get published as part of the [policy
+ trace](/sentinel/writing/tracing).
+
+A rule functions in ways similar to both a variable and a function: they hold a
+value, but are lazily evaluated; a rule's value is not assigned until the first
+time it's referenced in a policy. Additionally, while the value of evaluated
+rules will be available within a policy's trace after it's evaluated, values of
+variables - and the return value of functions - are not. Finally, a rule value
+is memoized - further references to the rule will not change its result.
+
+Rules can hold more than just boolean data. For more advanced rule patterns, see
+[the language reference](/sentinel/language/rules).
+
+## Writing Rules
+
+Let's look at the Sentinel policy we wrote in the previous section:
+
+```sentinel playground
+hour = 4
+main = rule { hour >= 0 and hour < 12 }
+```
+
+The logic in this policy is straightforward and easy to understand. However,
+it is common for policy logic to become much more complicated. Rules are the
+key abstraction for making complex logic understandable. We can turn the
+policy above into a set of rules:
+
+```sentinel playground
+hour = 4
+
+past_midnight = rule { hour >= 0 }
+before_noon = rule { hour < 12 }
+
+main = rule {
+ past_midnight and
+ before_noon
+}
+```
+
+This policy is easier to read. Our original policy was already quite
+simple, but it is easy to imagine a more complex policy becoming much
+more readable by extracting logical expressions into a set of rules.
+
+Rules don't just exist to make a policy more readable. They're also a
+testing and debugging point. For testing, you can assert that certain rules
+are certain values given a specific input to verify that your policy works
+as you expect. Testing and debugging are covered in later sections.
+
+## Conditional Rules
+
+In many cases, a rule is only valid when something else is also true.
+
+Consider the human language statement "before you eat, you must wash your hands."
+The rule "you must wash your hands" is only valid "before you eat". In any
+other scenario, the rule is meaningless.
+This is also true in a technical context. Imagine the rule "if the driver
+is Docker, you must not expose any ports." For this example, assume
+rules `is_docker` and `has_no_exposed_ports` exist. You can write this rule
+like this:
+
+```sentinel
+main = rule when is_docker { has_no_exposed_ports }
+```
+
+When `is_docker` is true, the rule is evaluated as normal. However,
+when `is_docker` is false, the rule always returns `true`, because the
+logic of the rule is meaningless.
+
+Let's learn how to create more complex rules with
+[logical expressions](/sentinel/intro/getting-started/logic).
diff --git a/content/sentinel/v0.18.x/content/sentinel/intro/getting-started/testing.mdx b/content/sentinel/v0.18.x/content/sentinel/intro/getting-started/testing.mdx
new file mode 100644
index 0000000000..7149a7966f
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/intro/getting-started/testing.mdx
@@ -0,0 +1,72 @@
+---
+page_title: Testing
+sidebar_current: intro-getting-started-testing
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Testing
+
+It is important to ensure the policies you write are correct. Sentinel
+includes a built-in test framework that can be run locally and in CI
+environments to test that policies behave as expected.
+
+A common pitfall with many simple ACL systems is that they provide no easy
+way to verify their correctness. You basically have to set the ACL and try
+the behaviors against a real system to verify it is working as expected.
+This requires a lot of setup and is unique to each system.
+
+[Sentinel's built-in test framework](/sentinel/writing/testing) has zero
+dependencies. Contained within the [Sentinel CLI](/sentinel/commands), it can
+mock the data that real systems are exposing to the policy. It is designed to be
+CI-friendly and enables continuous testing of your policies. This is necessary
+for [policy as code](/sentinel/concepts/policy-as-code).
+
+Detailed documentation on testing policies is available in [the Sentinel Testing
+reference](/sentinel/writing/testing).
+
+## Writing Tests
+
+For this example, save the following policy as `policy.sentinel`:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Next, let's write a passing test case. This will test that the policy tests
+when we expect it to pass. Save the following in `test/policy/good.hcl`:
+
+```hcl
+global "day" {
+ value = "monday"
+}
+
+global "hour" {
+ value = 14
+}
+```
+
+And run `sentinel test`:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/good.json
+```
+
+The `sentinel test` command will automatically find all Sentinel policies
+and their associated test cases and run them all. The test framework will
+run all HCL files as individual test cases, allowing you to test a variety
+of scenarios for your policies.
+
+Try adding another test case to force the policy to fail. This test case can
+be saved at `test/policy/fail.hcl`. For test cases with intentional
+failures, you'll need to use the [test assertions described in the testing
+reference](/sentinel/writing/testing#a-failing-test).
+
+Now that you have a good grasp of what Sentinel is and how to use it, please feel
+free to look through the
+[next steps](/sentinel/intro/getting-started/next-steps)
+you can take to further your Sentinel knowledge.
diff --git a/content/sentinel/v0.18.x/content/sentinel/intro/index.mdx b/content/sentinel/v0.18.x/content/sentinel/intro/index.mdx
new file mode 100644
index 0000000000..785a442fb8
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/intro/index.mdx
@@ -0,0 +1,32 @@
+---
+layout: intro
+page_title: Documentation
+sidebar_current: docs-home
+description: >-
+ Sentinel is a language and framework for policy built to be embedded in
+ existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+[introduction](/sentinel/intro) to understand what Sentinel is, how it
+compares to other software, and more.
+
+
diff --git a/content/sentinel/v0.18.x/content/sentinel/intro/what.mdx b/content/sentinel/v0.18.x/content/sentinel/intro/what.mdx
new file mode 100644
index 0000000000..36853d366c
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/intro/what.mdx
@@ -0,0 +1,64 @@
+---
+page_title: Introduction
+sidebar_current: intro-what
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: intro
+---
+
+# Introduction to Sentinel
+
+Welcome to the intro guide to Sentinel! This guide is the best
+place to start with Sentinel.
+
+If you are already familiar with the basics of Sentinel, the
+[documentation](/sentinel) provides a better reference
+guide for all available features as well as internals.
+
+## What is Sentinel?
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+Sentinel provides a language for writing policy and a workflow for developing
+and testing policies independent of the software they'll be written to. We
+also provide a framework for developers to build plugins that allow a
+Sentinel-enabled system to access external information to make policy
+decisions.
+
+Most systems today have some degree of access control. You are able to define
+identities and what they have access to. These ACL systems solve an immediate
+and necessary problem of locking down a system in very broad strokes. Sentinel
+is a reusable system for more advanced software policy decisions. Sentinel
+enables:
+
+- **Fine-Grained Policy**: Most ACL systems only enable coarse-grained
+ behaviors: "read", "write", etc. Sentinel enables fine-grained behavior such
+ as disallowing a certain API call when specific parameters are present.
+
+- **Logic-Based Policy**: You can write policy using full
+ conditional logic. For example, you may only allow a certain application behavior
+ on Monday to Thursday unless there is a manager override.
+
+- **Accessing External Information**: Sentinel can source external information
+ to be used in policy decisions. For example, a policy that restricts the size
+ of a payload may read data from [Consul](https://www.consul.io) to determine
+ the payload size limit.
+
+- **Enforcement levels**: Sentinel allows policies to be defined along
+ with an "enforcement level" that dictates the pass/fail behavior of a policy.
+ Advisory policies warn if they fail, soft mandatory policies can have their
+ failures overridden, and hard mandatory policies must pass under all
+ circumstances. Having this as a built-in concept enables you to model
+ policy more accurately and completely for your organization.
+
+## Next Steps
+
+See the page on [Why Sentinel?](/sentinel/intro/why) to learn more
+about the origins of the product. Then, continue onwards with
+the [getting started guide](/sentinel/intro/getting-started/install) to write
+your first policies and see how Sentinel works in practice.
diff --git a/content/sentinel/v0.18.x/content/sentinel/intro/why.mdx b/content/sentinel/v0.18.x/content/sentinel/intro/why.mdx
new file mode 100644
index 0000000000..98b58aab92
--- /dev/null
+++ b/content/sentinel/v0.18.x/content/sentinel/intro/why.mdx
@@ -0,0 +1,49 @@
+---
+page_title: Why Sentinel?
+sidebar_current: intro-why
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: intro
+---
+
+# Why Sentinel?
+
+The growth of infrastructure and applications has been enabled in part
+by an increasing trend towards automation everywhere.
+Configuration as code (such as Chef or Puppet with [Packer](https://www.packer.io))
+has enabled automated machine configuration. Infrastructure
+as code (such as [Terraform](https://www.terraform.io)) has enabled
+automatic infrastructure creation. And schedulers (such as
+[Nomad](https://www.nomadproject.io) and Kubernetes) have enabled
+automatic application deployment.
+Sentinel enables guardrails to be put in place
+on automation while allowing the codification and automatic enforcement
+of business requirements in critical areas of your infrastructure.
+
+Meanwhile, businesses have business requirements and sometimes legal
+requirements which must be expressed in policies. Traditionally, these
+policies are enforced by humans. But in a highly automated world, the
+automation is only as fast as its slowest component. In many cases, this
+is the human verification step.
+
+As an example: before infrastructure as code and autoscaling, if an order
+came through for 5,000 new machines, a human would likely respond to the
+ticket verifying that the user really intended to order 5,000 new machines.
+Today, automation can almost always freely order 5,000 new compute instances
+without any hesitation, which can result in unintended expense or system
+instability.
+
+Sentinel introduces [policy as code](/sentinel/concepts/policy-as-code)
+and a powerful framework built-in to HashiCorp tooling to allow automation
+guardrails, business requirements, legal compliance, and more to be
+actively enforced by the running systems in realtime.
+
+With Sentinel, you can require an override to create certain numbers of
+infrastructure resources. You can disallow unsafe deployment configurations
+with Nomad. You can enforce certain key/value formats in Consul. You
+can restrict secret access by time in Vault. And more.
+
+Sentinel is available today in HashiCorp enterprise products. You can
+try Sentinel immediately with the [getting started guide](/sentinel/intro/getting-started/install) or learn more about the integrations in the
+[documentation](/sentinel).
diff --git a/content/sentinel/v0.18.x/data/docs-nav-data.json b/content/sentinel/v0.18.x/data/docs-nav-data.json
new file mode 100644
index 0000000000..5c6d583186
--- /dev/null
+++ b/content/sentinel/v0.18.x/data/docs-nav-data.json
@@ -0,0 +1,316 @@
+[
+ {
+ "title": "Release Notes",
+ "path": "changelog"
+ },
+ {
+ "title": "Basic Concepts",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "concepts"
+ },
+ {
+ "title": "Policy as Code",
+ "path": "concepts/policy-as-code"
+ },
+ {
+ "title": "Policy Language",
+ "path": "concepts/language"
+ },
+ {
+ "title": "Imports",
+ "path": "concepts/imports"
+ },
+ {
+ "title": "Enforcement Levels",
+ "path": "concepts/enforcement-levels"
+ }
+ ]
+ },
+ {
+ "title": "Configuration File Syntax",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "configuration"
+ },
+ {
+ "title": "Remote Sources",
+ "path": "configuration/remote-sources"
+ }
+ ]
+ },
+ {
+ "title": "Commands (CLI)",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "commands"
+ },
+ {
+ "title": "apply",
+ "path": "commands/apply"
+ },
+ {
+ "title": "fmt",
+ "path": "commands/fmt"
+ },
+ {
+ "title": "test",
+ "path": "commands/test"
+ }
+ ]
+ },
+ {
+ "title": "Writing Policy",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "writing"
+ },
+ {
+ "title": "Basics",
+ "path": "writing/basic"
+ },
+ {
+ "title": "Rules",
+ "path": "writing/rules"
+ },
+ {
+ "title": "Traces",
+ "path": "writing/tracing"
+ },
+ {
+ "title": "Testing",
+ "path": "writing/testing"
+ },
+ {
+ "title": "Imports",
+ "path": "writing/imports"
+ },
+ {
+ "title": "Debugging",
+ "path": "writing/debugging"
+ }
+ ]
+ },
+ {
+ "title": "Extending Sentinel",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "extending"
+ },
+ {
+ "title": "Modules",
+ "path": "extending/modules"
+ },
+ {
+ "title": "Plugins",
+ "path": "extending/plugins"
+ },
+ {
+ "title": "Internals",
+ "path": "extending/internals"
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Language",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "language"
+ },
+ {
+ "title": "Variables",
+ "path": "language/variables"
+ },
+ {
+ "title": "Values",
+ "path": "language/values"
+ },
+ {
+ "title": "Lists",
+ "path": "language/lists"
+ },
+ {
+ "title": "Maps",
+ "path": "language/maps"
+ },
+ {
+ "title": "Rules",
+ "path": "language/rules"
+ },
+ {
+ "title": "Imports",
+ "path": "language/imports"
+ },
+ {
+ "title": "Parameters",
+ "path": "language/parameters"
+ },
+ {
+ "title": "Boolean Expressions",
+ "path": "language/boolexpr"
+ },
+ {
+ "title": "Arithmetic",
+ "path": "language/arithmetic"
+ },
+ {
+ "title": "Slices",
+ "path": "language/slices"
+ },
+ {
+ "title": "Conditionals",
+ "path": "language/conditionals"
+ },
+ {
+ "title": "Loops",
+ "path": "language/loops"
+ },
+ {
+ "title": "Collection Operations",
+ "path": "language/collection-operations"
+ },
+ {
+ "title": "Functions",
+ "path": "language/functions"
+ },
+ {
+ "title": "Scope",
+ "path": "language/scope"
+ },
+ {
+ "title": "Undefined",
+ "path": "language/undefined"
+ },
+ {
+ "title": "Logging and Errors",
+ "path": "language/logging-errors"
+ },
+ {
+ "title": "Specification",
+ "path": "language/spec"
+ }
+ ]
+ },
+ {
+ "title": "Builtin Functions",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "functions"
+ },
+ {
+ "title": "append",
+ "path": "functions/append"
+ },
+ {
+ "title": "delete",
+ "path": "functions/delete"
+ },
+ {
+ "title": "error",
+ "path": "functions/error"
+ },
+ {
+ "title": "keys",
+ "path": "functions/keys"
+ },
+ {
+ "title": "length",
+ "path": "functions/length"
+ },
+ {
+ "title": "print",
+ "path": "functions/print"
+ },
+ {
+ "title": "range",
+ "path": "functions/range"
+ },
+ {
+ "title": "values",
+ "path": "functions/values"
+ }
+ ]
+ },
+ {
+ "title": "Standard Imports",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "imports"
+ },
+ {
+ "title": "base64",
+ "path": "imports/base64"
+ },
+ {
+ "title": "decimal",
+ "path": "imports/decimal"
+ },
+ {
+ "title": "http",
+ "path": "imports/http"
+ },
+ {
+ "title": "json",
+ "path": "imports/json"
+ },
+ {
+ "title": "runtime",
+ "path": "imports/runtime"
+ },
+ {
+ "title": "sockaddr",
+ "path": "imports/sockaddr"
+ },
+ {
+ "title": "strings",
+ "path": "imports/strings"
+ },
+ {
+ "title": "time",
+ "path": "imports/time"
+ },
+ {
+ "title": "types",
+ "path": "imports/types"
+ },
+ {
+ "title": "units",
+ "path": "imports/units"
+ },
+ {
+ "title": "version",
+ "path": "imports/version"
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Consul",
+ "path": "consul"
+ },
+ {
+ "title": "Nomad",
+ "path": "nomad"
+ },
+ {
+ "title": "Terraform",
+ "path": "terraform"
+ },
+ {
+ "title": "Vault",
+ "path": "vault"
+ }
+]
diff --git a/content/sentinel/v0.18.x/data/intro-nav-data.json b/content/sentinel/v0.18.x/data/intro-nav-data.json
new file mode 100644
index 0000000000..c95181af70
--- /dev/null
+++ b/content/sentinel/v0.18.x/data/intro-nav-data.json
@@ -0,0 +1,47 @@
+[
+ {
+ "title": "What is Sentinel?",
+ "path": "what"
+ },
+ {
+ "title": "Why Sentinel?",
+ "path": "why"
+ },
+ {
+ "title": "Getting Started",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "getting-started"
+ },
+ {
+ "title": "Installing the CLI",
+ "path": "getting-started/install"
+ },
+ {
+ "title": "Your First Policy",
+ "path": "getting-started/first-policy"
+ },
+ {
+ "title": "Rules",
+ "path": "getting-started/rules"
+ },
+ {
+ "title": "Logic",
+ "path": "getting-started/logic"
+ },
+ {
+ "title": "Imports",
+ "path": "getting-started/imports"
+ },
+ {
+ "title": "Testing",
+ "path": "getting-started/testing"
+ },
+ {
+ "title": "Next Steps",
+ "path": "getting-started/next-steps"
+ }
+ ]
+ }
+]
diff --git a/content/sentinel/v0.18.x/img/sentinel-import-topology.svg b/content/sentinel/v0.18.x/img/sentinel-import-topology.svg
new file mode 100644
index 0000000000..36f7c79bc9
--- /dev/null
+++ b/content/sentinel/v0.18.x/img/sentinel-import-topology.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/content/sentinel/v0.18.x/redirects.jsonc b/content/sentinel/v0.18.x/redirects.jsonc
new file mode 100644
index 0000000000..d9e73f4a49
--- /dev/null
+++ b/content/sentinel/v0.18.x/redirects.jsonc
@@ -0,0 +1,15 @@
+[
+ {
+ "source": "/",
+ "destination": "/sentinel",
+ "permanent": true,
+ },
+ {
+ "source": "/sentinel/commands/config",
+ "destination": "/sentinel/configuration",
+ "permanent": true,
+ },
+ // disallow ".html" or "/index.html" in favor of cleaner, simpler paths
+ { "source": "/:path*/index", "destination": "/:path*", "permanent": true },
+ { "source": "/:path*.html", "destination": "/:path*", "permanent": true },
+]
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/changelog.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/changelog.mdx
new file mode 100644
index 0000000000..7b8887d9cf
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/changelog.mdx
@@ -0,0 +1,737 @@
+---
+page_title: Sentinel Runtime Release Notes
+sidebar_current: docs-changelog
+description: >-
+ This are the public release notes for the Sentinel runtime. See this document
+ for the latest updates.
+layout: docs
+---
+
+# Sentinel Runtime Release Notes
+
+These are the release notes for the Sentinel runtime.
+
+Each version of the runtime is released with a corresponding version of
+[Sentinel CLI](/sentinel/commands). To download the CLI, see the [downloads
+page](/sentinel/downloads).
+
+Sentinel integrations and embedded runtimes may not always have the latest
+version installed, depending on the product's individual release cycle. For more
+information, contact the support team for your specific integration.
+
+
+## 0.19.5 (February 9, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `SelectorExpr` will correctly rewrite `X`.
+
+## 0.19.4 (February 8, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `Compile` function will correctly set the `Original`
+ field when rewriting a `CallExpr` as an `ImportExpr`.
+
+## 0.19.3 (February 3, 2023)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.19.2 (January 17, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `Compile` function will no longer modify `ast.CallExpr`
+ arguments in place.
+
+## 0.19.1 (December 14, 2022)
+
+BUG FIXES:
+
+- `lang/ast`: The `Rewrite` function will no longer modify the provided Node
+ in place.
+
+ENHANCEMENTS:
+
+- `lang/semantic`: The Break semantic check uses the AST walker.
+
+## 0.19.0 (December 6, 2022)
+
+BREAKING CHANGES:
+
+- `config`: Attempts to override a standard import with a custom import will
+ now be met with an error.
+- `imports`: All plugin imports now return `sdk.Plugin` instead of `sdk.Import`,
+ as per the SDK update to v0.4.0.
+
+ENHANCEMENTS:
+
+- `runtime/importer`: An `Import` and `ImportInstance` interface have been
+ added to improve the import capabilities.
+- `runtime/importer`: The `Importer` interface now returns a `Import` interface
+ instead of a `sdk.Import`.
+- `lang/semantic`: The Import Assignment semantic check uses the AST walker.
+- `lang/ast`: Added an AST Walker so the AST rewriter does not need to be used for basic AST tasks.
+
+FEATURES:
+
+- `config`: Configuration syntax now allows for configuration of imports within
+ the standard library.
+- `config`: A new configuration syntax for defining modules and plugins is now
+ available.
+- Static data can now be added via `import` configuration.
+- `cmd/apply`: The `apply` command now supports multiple primary configuration
+ files by default, loading all configuration files within the working
+ directory.
+- `cmd/apply`: The `apply` command now accepts applying configuration overrides
+ to primary configuration.
+
+## 0.18.13 (October 31, 2022)
+
+BUG FIXES:
+
+- `cmd/apply`: `sentinel apply` no longer aborts on advisory policy failure.
+
+## 0.18.12 (September 15, 2022)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where nested `CallExpr` were not rewritten.
+
+## 0.18.11 (June 8, 2022)
+
+NOTES:
+
+- `config`: The `enforcement_level` key for policies is now optional, defaulting to `"advisory"` when not supplied.
+
+## 0.18.10 (May 20, 2022)
+
+NOTES:
+
+- `runtime/initwd`: Improvements to the installer, including timeouts and filesize limits, as well as disabling support
+ for symlinks within `git` or `hg` repositories.
+
+## 0.18.9 (March 23, 2022)
+
+NOTES:
+
+- This release fixes an issue with the Docker container build pipeline. There are no changes to the
+Sentinel runtime or CLI.
+
+## 0.18.8 (March 22, 2022)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue in the installer for Windows paths.
+
+## 0.18.7 (February 24, 2022)
+
+NOTES:
+
+- **Darwin arm64**. This release will be our first to include Darwin arm64 architecture.
+
+BUG FIXES:
+
+- `lang/ast`: Fixed issues where `IndexSpec` and `RuleLit` nodes where returning incorrect end positions.
+- `lang/ast`: Fixed an issue where `ParamSpec` nodes where returning incorrect end positions.
+
+## 0.18.6 (February 2, 2022)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.18.5 (January 14, 2022)
+
+IMPROVEMENTS:
+
+- `runtime/initwd`: Changed the sum algorithm used during remote installation from `md5` to `sha256` to reduce chance of collision.
+
+## 0.18.4 (July 20, 2021)
+
+FEATURES:
+
+- `imports/static`: Improved handling of struct tags when performing encoding, allowing `-` to mark as ignored.
+
+## 0.18.3 (June 1, 2021)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue for remote source installation where duplicate sub-directories resulted in incorrect installation.
+- `imports/static`: Fixed an issue where global configuration that contained an empty map could not be accessed without raising selector issues.
+
+## 0.18.2 (May 18, 2021)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where import expressions inside lists and maps were not being evaluated correctly.
+
+## 0.18.1 (May 11, 2021)
+
+BUG FIXES:
+
+- `imports/json`: Fixed an issue where empty maps where failing when passed at any level to `marshal`.
+
+## 0.18.0 (March 25, 2021)
+
+FEATURES:
+
+- `imports/http`: Added support for POST actions within the `http` import
+ through addition of the `post` function. Additionally, support for adding
+ a body to the `request` object has been added via `with_body`.
+
+## 0.17.4 (February 2, 2021)
+
+BUG FIXES:
+
+- `runtime/format`: Fixed a nesting issue with the rule printer where nesting
+ state leaks were leading to all collection values eventually being truncated,
+ regardless of their nesting level.
+
+## 0.17.3 (January 15, 2021)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue where local file installation failed on Windows
+ when using `sentinel test`.
+
+## 0.17.2 (January 13, 2021)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where duplicate log messages were being printed with
+ `sentinel test`.
+
+## 0.17.1 (January 7, 2021)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where hard-coded path separator caused `sentinel
+ test` issues in Windows.
+
+## 0.17.0 (January 5, 2021)
+
+LANGUAGE CHANGES:
+
+- **Map Expression** `map`. A new quantifier expression, `map` has been added.
+ `map` takes a collection and applies an operation to each element in the
+ collection, returning a list of results with the resulting values.
+- **Emptiness checks** `is empty` and `is not empty`. Two new expressions,
+ `is empty` and `is not empty` have been added. As they are aliases to the
+ `length` built-in, only `string`, `map`, `list` or `undefined` is
+ supported.
+- **Comparable maps** Maps (the data type) are now comparable. Maps are equal if
+ they are of equal length and both their corresponding keys and values are
+ comparable and equal.
+- **Rich Return Types** Rules can now process and return non-boolean data.
+ Scalar, list, and map types are supported. When non-boolean values are used,
+ the presence of a non-zero or non-zero-length `main` rule determines policy
+ failure. More details can be found on
+ https://docs.hashicorp.com/sentinel/language#main.
+
+FEATURES:
+
+- **CLI**: `sentinel apply` and `sentinel test` now have options for JSON
+ output. For more details, see the CLI documentation.
+- `imports/base64`: Added a new import, `base64`, to assist with encoding and
+ decoding base64 strings.
+
+## 0.16.1 (October 21, 2020)
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where `contains` to ensure any instance of `undefined` would correctly result in `undefined`.
+- `runtime/eval`: Fixed an issue in `any` and `all` expressions to ensure correct boolean logic when dealing with a collection containing `undefined`.
+- `imports`: Fixed an issue where import processes were not killed, which caused non-graceful closures for the clients.
+- `lang/scanner`: Fixed an issue where inline `#` style comments were not being consumed.
+
+## 0.16.0 (October 14, 2020)
+
+BREAKING CHANGES:
+
+- `cmd/doc`: Removal of the deprecated `sentinel doc` command.
+- `sentinel`: Replace `whitelist` and `blacklist` with `allowlist` and `denylist`, as per inclusive language changes.
+
+FEATURES:
+
+- `imports/version`: Added a new import, `version`, which supplies helpers for dealing with semantic versioning.
+- `cmd/apply`: Added an `HCL` based configuration file, which will eventually deprecate the legacy `JSON` configuration.
+- `cmd/apply`: Added support for download remote policies and modules.
+- `cmd/test`: Added support for downloading remote test modules.
+- `cmd/apply`: Added support for evaluating a policy based on it's key within the configuration.
+- `cmd/apply`: Added support for running all policies in a configuration by default.
+
+## 0.15.6 (July 7, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.15.5 (May 20, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel integrations. There are no user-facing changes.
+
+## 0.15.4 (May 13, 2020)
+
+BUG FIXES:
+
+- `imports`: Fixed an issue with the standard imports that was affecting the handling of null data within lists.
+
+## 0.15.3 (April 16, 2020)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where `IndexExpr` were not rewritten, as well as ensuring `SelectorExpr` uses any nested rewrites.
+- `lang/printer`: Fixed issue printing deeply nested structures and/or loops.
+
+IMPROVEMENTS:
+
+- `imports/decimal`: Added alias methods to improve consistency of method names within the `decimal` import. The alias methods are `is_nan` for `isnan`, as well as both `is_inf` and `is_infinite` for `isinfinite`.
+- `command/apply`: `sentinel apply` will now output the policy description when a trace is forced via `-trace`.
+- `sentinel`: Improved `String` output formatting of Policy docstring for `EvalPolicyResult`.
+
+## 0.15.2 (April 2, 2020)
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue with the `printer` that was causing a panic
+ when a `#` line comment was used with no text following it.
+- `imports/types`: Fixed an issue with the `types` import where calls to
+ `type_of` for undefined types were incorrectly returning as map types. These
+ will now be correctly be identified as undefined as expected.
+
+## 0.15.1 (March 6, 2020)
+
+BUG FIXES:
+
+- `sentinel`: Fixed how modules are managed internally in the runtime to correct
+ race conditions in concurrent scenarios and to allow for per-evaluation module
+ restrictions. This functionality is only of interest to embedded applications
+ with long-lived runtimes and is currently not implemented in any HashiCorp
+ integration.
+
+## 0.15.0 (March 5, 2020)
+
+NOTES:
+
+- **Windows Digital Signature**. Our windows releases for this version and up will be signed
+ and verified according to Microsoft's requirements.
+
+FEATURES:
+
+- `cmd/config`: Modules can now be loaded off of local disk using the Sentinel
+ CLI. For information on how to do so, see the Sentinel documentation.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: Changed modules so that they now have singleton scope when
+ loaded. Importing a module under two different aliases, or within a nested
+ module, will now share scopes - modification of state in one will alter the
+ other.
+- `lang/object`: Introduced an interface to allow the duplication of various
+ types, like collections.
+- `runtime/eval`: Changed how collection data is returned from modules. This
+ data is now duplicated to prevent accidental or deliberate circumvention of
+ the prohibition on assignment to import data, and brings behavior to parity
+ with binary imports.
+
+## 0.14.4 (February 6, 2020)
+
+IMPROVEMENTS:
+
+- `imports/time`: Changed the `add` timespace function in the `time` import to
+ allow it to take float types as durations. These values will be truncated to
+ the appropriate integer-value duration.
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where out-of-place right-hand braces were being
+ parsed as empty statements, instead of raising syntax errors.
+
+## 0.14.3 (January 22, 2020)
+
+IMPROVEMENTS:
+
+- `cmd/test`: Fail when an assertion is not found in a trace.
+- `cmd/test`: Added a message to notify when no rules were evaluated in a test.
+
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue where import aliases (the `as baz` in `import
+ "foo/bar" as baz`) were being removed when formatting with `sentinel fmt`.
+
+- `imports/http`: Fixed an issue with the http import where the client library's
+ debug logs were being printed to standard error.
+
+## 0.14.2 (January 15, 2020)
+
+NOTES:
+
+With this release, we are discontinuing support for MacOS 32-bit. 64-bit builds
+are now the only builds available for MacOS.
+
+IMPROVEMENTS:
+
+- `lang/printer`: A newline is now added to files formatted via `sentinel fmt`.
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue in printing where multi-line expressions would
+ indent infinitely. They will now only indent once at the most. This is mostly
+ seen when using `sentinel fmt`.
+- `runtime/encoding`: Fixed an issue where if a plugin returned a deeply
+ embedded undefined value, the value was instead decoded into a map.
+
+## 0.14.1 (January 6, 2020)
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where reserved words could not be used as
+ selectors when the selector expression was the last lexeme on a line.
+
+## 0.14.0 (December 18, 2019)
+
+LANGUAGE CHANGES:
+
+- **Filter Expression** `filter`. A new quantifier expression, `filter` has been added.
+ `filter` returns a subset of the provided collection based on a boolean condition that
+ is asserted for each element.
+
+NOTES:
+
+- **Apple Notarization**. Our darwin releases for this version and up will be signed
+ and notarized according to Apple's requirements.
+
+## 0.13.1 (November 25, 2019)
+
+BUG FIXES:
+
+- `runtime/eval`: Parameters are no longer allowed in mock files. Adding one to
+ a mock will result in a runtime error.
+- `runtime/eval`: Fixed an issue where import calls from within mocks were
+ failing under certain circumstances.
+- `imports/decimal`: `int` should now correctly provide the truncated integer
+ representation of a decimal number, not the rounded one.
+
+## 0.13.0 (November 15, 2019)
+
+LANGUAGE CHANGES:
+
+- **Policy Parameters**. The new [policy
+ parameters](https://docs.hashicorp.com/sentinel/language/parameters) feature
+ allows authors to use a `param` declaration at the top of a policy to supply
+ values that are expected to come from outside of a policy. See the linked
+ documentation for more details.
+
+FEATURES:
+
+- `imports/http`: The [`http`
+ import](https://docs.hashicorp.com/sentinel/imports/http) is a new addition
+ to the standard library enabling the use of HTTP-accessible data from outside
+ the runtime in policy rules.
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where loading other imports before a mocked
+ import would cause those imports to no longer be visible from within the
+ policy.
+
+## 0.12.0 (October 7, 2019)
+
+LANGUAGE CHANGES:
+
+- **New `case` statement**. This statement is a selection control mechanism to
+ conditionally execute a branch based on expression equality, allowing
+ simplification of complex conditional chains that may otherwise need to be
+ written with `else if`. See the [Case
+ Statements](https://docs.hashicorp.com/sentinel/language/spec#case-statements)
+ section of the Sentinel language specification for more details.
+
+IMPROVEMENTS:
+
+- `lang/semantic`: Added a semantic check to ensure usage of append is not using
+ a return value.
+
+BUG FIXES:
+
+- `runtime/eval`: Corrected an issue where calling a method on an import object
+ value that was the result of a method call on another import object value
+ would have erroneously tried to call an import of the name of the "parent"
+ import object value. Example: in `a = subject.new(); b = a.call(); c =
+ b.call()`, `b.call()` would attempt to call a method named `call` on the root
+ namespace for an import named `a`. This has now been corrected so that
+ `b.call()` will now correctly call the `call` method for the respective object
+ namespace residing in the import named `subject`.
+- `imports`: Some standard imports may have been returning null for some unknown
+ keys in objects when they should have been returning undefined. This was due
+ to an SDK issue which was corrected in SDK version 0.3.2, which has now been
+ corrected.
+- `cmd/test`: `sentinel test` will now correctly fail a policy if it encounters
+ an error.
+- `cmd/test`: `sentinel test` will now correctly display errors and other output
+ that were missing due to a formatting issue.
+- `runtime/eval`: The `append` builtin now correctly returns undefined for all
+ calls, as called for by the Specification. Note that in most cases, the
+ semantic check outlined above will trigger an error if the return value is
+ used.
+
+## 0.11.0 (September 5, 2019)
+
+LANGUAGE CHANGES:
+
+- **New builtin function:** `range()`. This function existed in the spec in
+ earlier versions but was removed as it lacked an implementation. This has now
+ been implemented and re-added to the spec. See the
+ [Range](https://docs.hashicorp.com/sentinel/language/spec#range) section of
+ the Sentinel Specification for more details.
+- Lists are now comparable. Lists are equal if their corresponding elements are
+ comparable and equal.
+- Method calls on values returned by imports are now supported. See the
+ [Imports](https://docs.hashicorp.com/sentinel/language/spec#imports) section
+ of the Sentinel Specification for more details.
+
+FEATURES:
+
+- `imports/decimal`: This is a new import designed to do exact precision
+ mathematical calculations.
+- `runtime/eval`: Compound call expressions that refer to imports (example:
+ foo.bar().baz() when `foo` is a loaded import) will now function as expected.
+ Previously, this was only supported up to the first call (example:
+ `foo.bar()`).
+- `imports/time`: Timespaces returned by calls such as `time.now` are now
+ callable. Example: `t = time.now; t.after(some_previous_time)` will now
+ function.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: The implementation of comparison of non-comparable types has
+ now changed. Rather than triggering a runtime error, non-comparable types will
+ now return false when attempting to compare.
+
+## 0.10.4 (August 15, 2019)
+
+BUG FIXES:
+
+- `runtime/encoding`: Fixed an issue with conversion of null values that could
+ lead to crashes in imports.
+
+## 0.10.3 (July 15, 2019)
+
+BUG FIXES:
+
+- `command/config`: Parsing a configuration file where malformed data is
+ encountered after apparently properly-formed data will now report an error. An
+ example would be a situation where misplaced braces would cause a JSON object
+ to only parse part of the configuration file.
+
+## 0.10.2 (June 25, 2019)
+
+BUG FIXES:
+
+- `lang/parser`: Corrected an issue where parsing certain compound binary
+ expressions where the negation predicate (`not`) was in use would cause the
+ negation to have no effect. Example: `foo else "bar" not in "baz"` would have
+ been parsed and evaluated as `foo else "bar" in "baz"`, effectively producing
+ the opposite result.
+
+## 0.10.1 (May 9, 2019)
+
+BUG FIXES:
+
+- `command/test`: `sentinel test` now displays policies without
+tests as an unknown result with [no test files], instead of the somewhat
+erroneous behavior of displaying it as a PASS. A full test run with a mixture
+of passing tests and no tests still results in an overall successful result.
+
+## 0.10.0 (April 18, 2019)
+
+LANGUAGE CHANGES:
+
+- Mixed-number arithmetic operations are now allowed. Addition (`+`),
+ subtraction (`-`), multiplication (`*`), division (`/`), and remainder
+ operations (`%`) are no longer restricted to number values of the same
+ type, and can mix integer and floating point. The result of these operations
+ is always a floating-point number.
+- Remainder (modulo, `%`) operations are now allowed on floating-point numbers.
+
+
+BUG FIXES:
+
+- `lang/parser`: Fixed a bug that affected the use of `not` with `contains`,
+ `matches`, or `in` in a compound expression.
+- `runtime/eval`: Fixed error messages on evaluation errors with `contains` and
+ `in` to indicate that strings are an allowed type along with lists and maps.
+
+
+## 0.9.2 (March 15, 2019)
+
+This is a dependency update related to the changes mentioned in 0.9.1. No other
+changes have been made.
+
+## 0.9.1 (March 14, 2019)
+
+This is a patch release that is required to integrate with the latest versions
+of the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). No other
+changes have been made.
+
+## 0.9.0 (January 28, 2019)
+
+FEATURES:
+
+- `imports/strings`: Added a `join` function to the `strings` import. This can
+ be used to join a list into a string with a specific separator.
+ Multi-dimensional lists and all Sentinel primitives are supported.
+
+## 0.8.1 (January 17, 2019)
+
+BUG FIXES:
+
+- `lang/eval`: Fixed a bug that prevents the effective use of `return`
+ statements in `for` loops.
+
+## 0.8.0 (January 14, 2019)
+
+FEATURES:
+
+- Mocks can now be represented by Sentinel code. This allows for the mocking of
+ functions and other complex data structures that cannot be represented in
+ JSON. For more information on using this feature via mocks, click
+ [here](https://docs.hashicorp.com/sentinel/commands/config#mocking-with-sentinel-code).
+
+
+IMPROVEMENTS:
+
+- Import validation has now been moved to the semantic checking phase. This
+ should result in better reporting of validation errors. In addition, import
+ validation will now enforce the use of an `as` identifier when an import path
+ is not a valid identifier on its own (example: `import "foo/bar"`).
+
+## 0.7.0 (December 12, 2018)
+
+BUG FIXES:
+
+- There have been changes to the runtime in how scope is handled over multiple
+ policy executions. Scope is now correctly unique per single policy execution,
+ and values set or builtins that are overridden in one policy will no longer
+ affect those values within another.
+
+## 0.6.0 (November 30, 2018)
+
+FEATURES:
+
+- `imports/runtime`: This new import allows for one to check various aspects of
+ the Sentinel runtime as it may be embedded in the simulator or a specific
+ implementation. For now, it allows the version to be checked.
+
+## 0.5.1 (November 28, 2018)
+
+IMPROVEMENTS:
+
+- `imports/time`: Added the `zone` and `zone_string` attributes to assist with
+ validation of a timespace's zone.
+- `command/fmt`: Added a new -check flag. This option does not commit changes,
+ but instead checks to see what files need formatting and outputs them on
+ stdout.
+
+BUG FIXES:
+
+- `command/test`: Ensure that passing test results are correctly output one per
+ line. Tests are also now run in a deterministic fashion based on
+ lexicographical (alphabetical) order.
+- `imports/time`: `month_name` and `weekday_name` will now show up correctly in
+ a returned timespace result.
+
+
+## 0.5.0 (November 5, 2018)
+
+IMPROVEMENTS:
+
+- `spec`: Selectors can now contain any reserved word (example: `rule`) or
+ keyword operator (example: `any`, `all`, `is`, `not`). This only works for the
+ _selector_ part of the expression (after the first period) - the first primary
+ expression (before the first period) still needs to be an identifier that does
+ not conflict with reserved words.
+
+BUG FIXES:
+
+- The simulator should now display import function call names correctly in
+ import errors.
+
+## 0.4.0 (October 1, 2018)
+
+FEATURES:
+
+- `builtin`: Added the `bool` built-in type conversion function. Booleans will
+ also now accepted as conversion into other values as well, with the full list
+ of behaviors available in the spec.
+
+## 0.3.2 (September 27, 2018)
+
+FEATURES:
+
+- `command/apply`: `sentinel apply` now prints out messages output by the
+ `print()` function when a trace is output on policy failure, or when a trace
+ is forced with `-trace`.
+- `imports/time`: Added the `month_name` and `weekday_name` keys to the
+ timespace, which return full-English names for the month and day of the week.
+
+
+BUG FIXES:
+
+- `command/fmt`: `sentinel fmt -` Will no longer print out the filter status
+ message on the output stream when `-write=false` Is not explicitly stated.
+ This brings the behavior of the command in line with the help text.
+- `runtime`: Index operations on the right-hand-side that have negative indexes
+ that go out of range (example: `length(list) * -1 - 1`) now correctly return
+ `undefined`. left-hand-side index assignments with a out-of-range negative
+ index still return runtime errors.
+
+## 0.3.1 (August 3, 2018)
+
+BUG FIXES:
+
+- `runtime`: Basic index assignment has been implemented as per the spec.
+- `runtime`: Index expressions for lists with negative indexes will no longer panic if the
+ list index is less than `length(list) * -1`.
+
+## 0.3.0 (July 20, 2018)
+
+FEATURES:
+
+- **New standard import: `types`.** This can be used to dynamicaly detect the
+ type of some value.
+
+## 0.2.0 (April 11, 2018)
+
+FEATURES:
+
+- **New standard import: `json`.** Marshal and unmarshal JSON documents and
+ access their contents as native Sentinel values.
+- **`break` and `continue`.** These are now both specified and implemented.
+ `break` allows loop exiting and `continue` allows immediate execution of the
+ next iteration.
+
+IMPROVEMENTS:
+
+- runtime: `print()` map values are now ordered alphabetically by keys.
+
+BUG FIXES:
+
+- command/test: If no `test` block exists, test behaves like it is asserting
+ `main: true`.
+- runtime: default maximum stack depth to 500
+- runtime: `print()` map values now appear like more typical maps.
+- runtime: division by zero is an error, not a crash
+- runtime: plugins that send map values with `null` values now decode properly
+ into native Sentinel values.
+
+## 0.1.0 (September 19, 2017)
+
+Initial release.
+
+
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/commands/apply.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/commands/apply.mdx
new file mode 100644
index 0000000000..c5e81d6d65
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/commands/apply.mdx
@@ -0,0 +1,63 @@
+---
+page_title: 'Command: apply'
+sidebar_current: docs-commands-apply
+description: >-
+ The `sentinel apply` command is used to execute a policy locally for
+ development purposes.
+layout: docs
+---
+
+# Command: `apply`
+
+The `sentinel apply` command is used to execute a policy locally for development
+purposes.
+
+## Usage
+
+Usage: `sentinel apply [options] POLICY`
+
+This command executes the policy file at the path specified by POLICY. In
+addition to a path to a policy, POLICY can reference a label of a
+[policy](/sentinel/configuration#policies) entry in the configuration.
+
+Use the exit code of this command to determine the exact status of the policy
+evaluation. `0` is pass, `1` is fail, `2` is undefined (fail, but because the
+result was undefined), and `3` is a runtime error. Errors unrelated to the
+policy status itself are returned with an exit status of `9`.
+
+To control the behavior of the `apply` command, create a [configuration
+file](/sentinel/configuration). With this, you can define available
+[import plugins](/sentinel/concepts/imports), mock data, and global values.
+This can help you simulate a policy embedded within an application.
+
+As of the 0.16 release, POLICY is optional. When it is not supplied,
+`sentinel apply` will run **all** policies in the supplied configuration.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-config=path` - Path to a configuration file specifying available imports,
+ mock data, globals, etc. The default is `sentinel.[hcl|json]`.
+
+- `-json` Enable JSON output. Using this mode, all other output will be
+ suppressed and the full detail of the apply will be output in a
+ machine-readable format. Enabling this implies `-trace`. See the section on
+ [tracing JSON output](/sentinel/writing/tracing#json-output) for more details.
+
+- `-json-rule=RULE` Enable JSON output, outputting only the value of the
+ specified rule. When running against a policy set, the policy must be supplied
+ to enable per-rule filtering. Implies `-json`.
+
+- `-global key=value` - Set global values. This is the same as setting `global`
+ in the configuration file, and will override any of these respective values
+ set in the configuration. The value is either a string, or a JSON number,
+ array, or object. To force strings, use quotes.
+
+- `-param key=value` - Set parameters, the same as setting `param` in the
+ configuration file. Values are handled in the same way they are with the
+ `-global` flag.
+
+- `-trace` - Always show the execution trace. This shows intermediate
+ boolean expression values. This always shows for failed policies.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/commands/fmt.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/commands/fmt.mdx
new file mode 100644
index 0000000000..8297316d8b
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/commands/fmt.mdx
@@ -0,0 +1,33 @@
+---
+page_title: 'Command: fmt'
+sidebar_current: docs-commands-fmt
+description: The `sentinel fmt` command formats a policy source to a canonical format.
+layout: docs
+---
+
+# Command: `fmt`
+
+The `sentinel fmt` command formats a policy source to a canonical format.
+
+## Usage
+
+Usage: `sentinel fmt [options] FILE ...`
+
+This command formats all the specified policy files to a canonical format.
+
+By default, policy files are overwritten in place. This behavior can be
+changed with the `-write` flag. If a specified FILE is `-` then stdin is
+read and the output is always written to stdout.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-write=true` - Write formatted policy to the named source file. If false,
+ output will go to stdout. If multiple files are specified, the output will
+ be concatenated directly.
+
+- `-check=false` - Don't format, only check if formatting is necessary. Files
+ that require formatting are printed, and a non-zero exit code is returned if
+ changes are required.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/commands/index.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/commands/index.mdx
new file mode 100644
index 0000000000..7177093cec
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/commands/index.mdx
@@ -0,0 +1,23 @@
+---
+page_title: Commands (CLI)
+sidebar_current: docs-commands
+description: >-
+ Sentinel provides a `sentinel` command-line interface (CLI) for developing and
+ testing policies.
+layout: docs
+---
+
+# Sentinel CLI Commands
+
+The Sentinel command-line interface (CLI) allows for the developing and testing
+of policies outside of a particular Sentinel implementation. Having a standard
+workflow to develop policies is critical for our mission of [policy as
+code](/sentinel/concepts/policy-as-code).
+
+The CLI takes a subcommand to execute. The complete list of subcommands is in
+the navigation to the left.
+
+The Sentinel CLI is a well-behaved command line application. In erroneous cases,
+a non-zero exit status will be returned. It also responds to -h and --help as
+you'd expect. To view a list of the available commands at any time, just run
+`sentinel` with no arguments.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/commands/test.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/commands/test.mdx
new file mode 100644
index 0000000000..dc8de617ab
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/commands/test.mdx
@@ -0,0 +1,46 @@
+---
+page_title: 'Command: test'
+sidebar_current: docs-commands-test
+description: The `sentinel test` command runs policies against the Sentinel test framework.
+layout: docs
+---
+
+# Command: `test`
+
+The `sentinel test` command runs policies against the Sentinel test framework.
+
+To learn more about testing, see the [testing
+policies](/sentinel/writing/testing) page.
+
+## Usage
+
+Usage: `sentinel test [options] [PATH] ...`
+
+This command runs the defined test cases against a set of policies.
+
+The test cases are expected to live at the path `test//*.[hcl|json]` relative
+to the policy being tested. `` should be the name of the policy
+excluding the file extension. The files should be in the [configuration
+file structure](/sentinel/configuration). The `test` key is used for
+assertions.
+
+The PATH arguments specified may be a list of zero or more files or directories.
+When no arguments are given, it defaults to the current directory. When a
+directory is given, all policies ending with the extension `.sentinel` are
+tested.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-json` - Suppress normal output and output test results as a JSON document.
+ See the [testing JSON output](/sentinel/writing/testing#test-json-output)
+ section for more details.
+
+- `-run=regexp` - Run only tests matching the regular expression pattern.
+ A `/` splits policy name and test case name.
+
+- `-verbose` - Show tracing and log output for tests that succeed in addition
+ to tests that fail. By default, traces and logs are only shown for tests that
+ fail.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/concepts/enforcement-levels.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/concepts/enforcement-levels.mdx
new file mode 100644
index 0000000000..fc4b8062d0
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/concepts/enforcement-levels.mdx
@@ -0,0 +1,43 @@
+---
+page_title: Enforcement Levels
+sidebar_current: docs-concepts-levels
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Enforcement Levels
+
+Enforcement levels are a first class concept in Sentinel allowing pass/fail
+behavior to be associated separately from the policy logic. This enables
+any policy to be a warning, allow overrides, or be absolutely mandatory.
+Because this level is not part of the policy body itself, different uses of
+the same policy can have different enforcement levels.
+
+Sentinel has three enforcement levels:
+
+- **Advisory:** The policy is allowed to fail. However, a warning should be
+ shown to the user or logged. Advisory is the default enforcement level.
+
+- **Soft Mandatory:** The policy must pass unless an override is specified.
+ The semantics of "override" are specific to each Sentinel-enabled application.
+ The purpose of this level is to provide a level of privilege separation
+ for a behavior. Additionally, the override provides non-repudiation since
+ at least the primary actor was explicitly overriding a failed policy.
+
+- **Hard Mandatory:** The policy must pass no matter what. The only way to
+ override a hard mandatory policy is to explicitly remove the policy.
+ It should be used in situations where an override is not possible.
+
+## Configuring Enforcement Levels
+
+Enforcement levels are configured when a policy is deployed to a
+Sentinel-enabled application. The exact mechanism that the level is specified
+is determined by each application. Please reference the documentation for
+your Sentinel-enabled application for more information.
+
+Enforcement levels are not configured and are not known by the policy body
+itself. All policies should be written to describe exactly the behavior
+they're attempting to control. For example, a policy that restricts deploys
+to business hours should be written exactly like so. When that policy is
+configured on an application, the operator may specify that it is advisory,
+soft, or hard mandatory.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/concepts/imports.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/concepts/imports.mdx
new file mode 100644
index 0000000000..1306e1512e
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/concepts/imports.mdx
@@ -0,0 +1,32 @@
+---
+page_title: Imports
+sidebar_current: docs-concepts-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom imports](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only
+local context for making policy decisions.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that the
+import calendar allows access to engineer calendars. This import only exists
+for the purpose of this example and at the time of writing is not a real
+available import.
+
+```json sentinel
+{
+ "policy": "import \"calendar\"\n// Get the calendar for Bob for today\nbob_calendar = calendar.of(\"bob\").today\n\n// Allow this policy to pass if Bob is not on vacation.\nmain = rule { not bob_calendar.has_event(\"vacation\") }",
+ "mocks": {
+ "calendar": "has_event = func(event) {\n\treturn true\n}\nof = func(name) {\n\treturn { \"today\": { \"has_event\": has_event } }\n}"
+ }
+}
+```
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/concepts/index.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/concepts/index.mdx
new file mode 100644
index 0000000000..ce7ac51454
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/concepts/index.mdx
@@ -0,0 +1,15 @@
+---
+page_title: Basic Concepts
+sidebar_current: docs-concepts
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Basic Concepts
+
+This section covers some high level basic concepts that are important
+to understand for day to day Sentinel usage. Every page in
+this section is recommended reading for anyone consuming
+or extending Sentinel.
+
+Please use the navigation to the left to learn more about a topic.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/concepts/language.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/concepts/language.mdx
new file mode 100644
index 0000000000..ce91f3eb8f
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/concepts/language.mdx
@@ -0,0 +1,91 @@
+---
+page_title: Policy Language
+sidebar_current: docs-concepts-language
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Policy Language
+
+Sentinel defines and uses its own [policy language](/sentinel/language).
+
+The language was designed to be approachable by non-programmers, since
+there are many use cases where the individual defining policy may not
+be a developer. However, the language includes constructs that
+are familiar to developers to enable powerful policies.
+
+To learn more about the language, please see the
+[writing policy section](/sentinel/writing)
+or the [language reference](/sentinel/language).
+
+An example of the policy language is shown below:
+
+```sentinel playground
+import "time"
+
+# Validate time is between 8 AM and 4 PM
+valid_time = rule { time.now.hour >= 8 and time.now.hour < 16 }
+
+# Validate day is M - Th
+valid_day = rule {
+ time.now.weekday_name in ["Monday", "Tuesday", "Wednesday", "Thursday"]
+}
+
+main = rule { valid_time and valid_day }
+```
+
+## Why?
+
+To explain why Sentinel defines and uses its own language, the question
+can be more easily split into roughly two types of languages: _configuration_ languages
+and _programming_ languages.
+
+### Configuration Languages
+
+Configuration languages are formats such as JSON, YAML, XML, etc. Many applications
+use configuration languages as their ACL format. Configuration languages
+are good for static, declarative information. ACL systems are a good fit
+for this. ACL systems are focused, providing the limiting options necessary
+to restrict access to a system.
+
+Configuration languages are not good for dynamic or logical rules. They
+typically only contain limited conditions, loops, or functions. Further
+configuration is often declarative, which can introduce complexities to
+logical statements that depend on ordering.
+
+The use cases that Sentinel was built for require the ability to perform
+complex behavior that didn't model well into a configuration format or a
+declarative form.
+
+### Programming Languages
+
+The Sentinel language was designed with the following goals:
+
+- **Non-programmer friendly.** Sentinel was built to be used by non-programmers.
+ For the use cases we discovered, non-programmers needed the ability to
+ enforce certain rules within a system. For example, a person responsible for
+ compliance may need to insert rules into a system.
+
+- **Programmer friendly.** At the same time, the language needed to support
+ programmer-friendly constructs such as conditionals, loops, and functions
+ for complex policies that a programmer may be writing.
+
+- **Embeddable.** Sentinel is embedded in existing software. The language
+ itself needs to be easily embeddedable. Further, Sentinel was designed
+ to be embedded in [HashiCorp](https://www.hashicorp.com) software
+ written in Go, so it needed to be easily embeddable in Go.
+
+- **Safe.** The language is used in highly security-sensitive environments.
+ It must not be able to crash its host system or access system resources
+ without explicit approval.
+
+Prior to building our own language, Sentinel evaluated other languages.
+Some languages worked quite well. As we continued to develop Sentinel, we
+found that the flexibility and control over designing our own language
+outweighed the costs.
+
+Because the language itself doesn't need to be general purpose, building
+a language focused on policy proved to be the best route for Sentinel.
+It allows us to build powerful tracing capabilities for debugging, make
+interesting performance choices, and extend the language as needed in the
+future.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/concepts/policy-as-code.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/concepts/policy-as-code.mdx
new file mode 100644
index 0000000000..6dac593e18
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/concepts/policy-as-code.mdx
@@ -0,0 +1,72 @@
+---
+page_title: Policy as Code
+sidebar_current: docs-concepts-pac
+description: >-
+ Policy as code is the idea of writing code in a high-level language to manage
+ and automate policies. By representing policies as code in text files, proven
+ software development best practices can be adopted such as version control,
+ automated testing, and automated deployment.
+layout: docs
+---
+
+# Policy as Code
+
+Policy as code is the idea of writing code in a high-level language to
+manage and automate policies. By representing policies as code in text files,
+proven software development best practices can be adopted such as version
+control, automated testing, and automated deployment.
+
+Many existing policy or ACL systems do not practice policy as code. Many
+policies are set by clicking in a GUI, which isn't easily repeatable nor
+versionable. They usually don't provide any system for testing policies
+other than testing an action that would violate the policy. This makes it
+difficult for automated testing. And the policy language itself varies by
+product.
+
+Sentinel is built around the idea and provides all the benefits of policy as code.
+
+## Benefits
+
+Policy as code provides a number of benefits:
+
+- **Sandboxing.** Policies provide the guardrails for other automated systems.
+ As the number of automated systems grow, there is also a growing need to
+ protect those automated systems from performing dangerous actions. Manual
+ verification is too slow; policies need to be represented as code to
+ keep up with other automated systems.
+
+- **Codification.** By representing policy logic as code, the information
+ and logic about a policy is directly represented in code and can be augmented
+ with comments rather than relying on oral tradition to learn about the
+ reason for policies.
+
+- **Version Control.** Policies are encouraged to be stored as simple text
+ files managed by a version control system. This lets you gain all the
+ benefits of a modern VCS such as history, diffs, pull requests, and more.
+
+- **Testing.** Policies are just code. Their syntax and behavior can be
+ [easily validated with Sentinel](/sentinel/commands/test). This also encourages
+ automated testing such as through a CI. Paired with a VCS system, this
+ allows a pull request workflow to verify that a policy keeps the system
+ behavior as expected before merging.
+
+- **Automation.** With all policies as code in simple text files, various
+ automation tools can be used. For example, it is trivial to create tools
+ to automatically deploy the policies into a system.
+
+## Sentinel and Policy as Code
+
+Sentinel fully embraces policy as code in a number of ways:
+
+- **Language.** All Sentinel policies are written using the
+ [Sentinel language](/sentinel/concepts/language). This language is
+ made to be inputted directly to text files. As an additional benefit,
+ all Sentinel-enabled applications share the same policy language.
+
+- **Development.** Sentinel provides a [CLI](/sentinel/commands)
+ for development and testing. This local CLI can be used to verify policies
+ before deploying them to a system.
+
+- **Testing.** Sentinel provides a [test framework](/sentinel/commands/test)
+ designed specifically for automation. This allows developers and CI systems
+ to further verify policies.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/configuration/index.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/configuration/index.mdx
new file mode 100644
index 0000000000..712fffcbed
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/configuration/index.mdx
@@ -0,0 +1,400 @@
+---
+page_title: Sentinel CLI Configuration File Syntax
+sidebar_current: docs-configuration
+description: >-
+ The Sentinel CLI's configuration file can be used to control the
+ behavior of the simulator during apply and test operations.
+layout: docs
+---
+
+# Sentinel CLI Configuration File Syntax
+
+The Sentinel CLI's configuration file can be used to control the behavior
+of the simulator during [`apply`](/sentinel/commands/apply) and
+[`test`](/sentinel/commands/test) operations.
+
+## Usage
+
+The configuration file is used in different ways depending on what operation you
+are trying to execute.
+
+### Apply
+
+When using `sentinel apply`, configuration files that have the `.hcl`
+extension are loaded into a single configuration. This allows for configuration
+to be split across multiple files, which is useful for large policy sets. To
+supply a single configuration file, the `-config=FILE` flag can be used, where
+`FILE` is the path to the configuration file. This argument also allows for a
+`.json` configuration file to be supplied. The default is `sentinel.[hcl|json]`.
+
+See the [apply command](/sentinel/commands/apply) reference for more details.
+
+### Test
+
+When using `sentinel test`, each file matching `test//*.[hcl|json]` is a
+configuration file representing a single test case, where `` is the name
+of the policy being tested. Configure assertions for each [test
+case](#test-cases) using the test section.
+
+See the [test command](/sentinel/commands/test) reference for more details.
+
+#### Remote sources
+
+When declaring a `policy` or `module` block within the configuration, a
+`source` attribute must be supplied. This `source` should declare the location
+of the resource, which can be either a local file or a URL pointing to a remote
+file. Examples of local `source` attributes are detailed both in the
+[Policies](#policies) and [Modules](#modules) sections of this page. Below
+are a few examples of remote `source` attributes.
+
+Example:
+
+```hcl
+policy "foo" {
+ source = "git::https://github.com/hashicorp/sentinel-example.git//main.sentinel"
+ enforcement_level = "hard-mandatory"
+}
+```
+
+The above example will fetch the policy from the provided URL and place it into
+the cache ready for use. Be sure to read the
+[Remote Sources](/sentinel/configuration/remote-sources) page for an
+in-depth overview.
+
+## Configuration File Reference
+
+The format of the configuration file is either JSON or HCL. The available
+blocks are:
+
+- [`mock`](#mock-imports) - A mock import specification.
+- [`policy`](#policies) - Configuration for a [policy](/sentinel/writing).
+- [`import`](#imports) - Configuration for a [module](/sentinel/extending/modules), [standard import](/sentinel/imports), [plugin](/sentinel/extending/plugins) or
+ [static import](/sentinel/extending/static-imports).
+- [`global`](#globals) - Data that is inserted into the global scope.
+- [`param`](#parameters) - Values for [parameters](/sentinel/language/parameters) defined in the policy.
+- [`test`](#test-cases) - Test cases for the [test command](/sentinel/commands/test).
+
+### Mock Imports
+
+Mock imports allow running a policy with an
+[import](/sentinel/concepts/imports) that you may not have access to or is
+too difficult to run. For example, it can be easier to mock data for [Terraform
+Enterprise](https://www.terraform.io/docs/enterprise/sentinel/index.html)
+locally instead of having to run a workspace through a plan/policy check cycle.
+
+Mock imports are specified using the `mock` block, labelled by the import
+name that you want to mock. For example, if you wanted to mock the `time`
+import, you could create an entry with the `time` label pointing to the data
+you want to mock.
+
+The data can take one of two forms:
+
+#### Mocking static data
+
+Static data can be mocked directly via HCl using the `data` attribute on the
+`mock` block.
+
+Example:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ hour = 9
+ minute = 42
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"time\"\n\nmain = rule { time.now.hour is 9 }",
+ "mocks": {
+ "time": "now = { \"hour\": 9, \"minute\": 42 }"
+ }
+}
+```
+
+#### Mocking with Sentinel code
+
+There are some Sentinel types that raw data cannot mock, such as functions,
+and maps that don't have string keys. To mock this data, you can use
+a Sentinel file itself. In this case, you can set the `module` attribute to
+a file with the Sentinel code in it, relative to the current working directory in
+the event of `apply`, or relative to the configuration file location in the
+event of `test`.
+
+Note that `main` is not required in a mock data file.
+
+Example:
+
+```hcl
+mock "foo" {
+ module {
+ source = "mock-foo.sentinel"
+ }
+}
+```
+
+`mock-foo.sentinel` would contain:
+
+```sentinel
+bar = func() {
+ return "baz"
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"foo\"\n\nmain = rule { foo.bar() is \"baz\" }",
+ "mocks": {
+ "foo": "bar = func() { return \"baz\" }"
+ }
+}
+```
+
+### Policies
+
+The `policy` block allows for the the configuration of policies to assist
+with integrating into other commands.
+
+Each `policy` block has a label to identify the policy, with the block
+providing configuration of the policy. The available configuration options for
+policy are `source` and `enforcement_level`. The `source` key provides the
+location of the policy, while `enforcement_level` is currently used by
+integrations such as [Terraform Cloud](https://www.terraform.io/docs/cloud/sentinel/manage-policies.html#enforcement-levels). For more information on the `source`
+value, see [Policy and Module Sources](#policy-and-module-sources).
+
+```hcl
+policy "foo" {
+ source = "foo.sentinel"
+ enforcement_level = "hard-mandatory"
+}
+```
+
+If `enforcement_level` is not supplied, it will fallback to the `advisory` level.
+
+### Imports
+
+Import configuration allows you to configure [modules](/sentinel/extending/modules),
+[standard imports](/sentinel/imports) and [import plugins](/sentinel/extending/plugins).
+
+The `import` is a multi-label block, with the first label determining the kind of import
+being configured. Currently the supported values for this label are:
+
+* [`"module"`](#import-modules) for configuring import modules
+* [`"plugin"`](#import-plugins) for configuring standard imports and import plugins
+* [`"static"`](#static-imports) for configuring static data files as imports
+
+#### Import Modules
+
+The `import "module"` block allows you to map a Sentinel import to a
+[module](/sentinel/extending/modules). The Sentinel CLI will parse the module
+source and make it available for evaluation with the policy.
+
+Each block has a label aligning to the name of the import, with
+the block set to the configuration object for the module. Currently, the only
+value within the configuration object is `source`, which points to the
+location of the module. For more information on the `source` value, see
+[Policy and Module Sources](#policy-and-module-sources).
+
+```hcl
+import "module" "foo" {
+ source = "modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "modules/bar.sentinel"
+}
+```
+
+Note when using [`sentinel test`](/sentinel/commands/test), module paths must be
+specified relative to the location of the configuration file.
+
+```hcl
+import "module" "foo" {
+ source = "../../modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "../../modules/bar/sentinel"
+}
+```
+
+See [Writing and Using a
+Module](/sentinel/extending/modules#writing-and-using-a-module) for more details
+on using a module with this configuration.
+
+### Import Plugins
+
+Import `"plugin"` configuration allows you to configure both [standard imports](/sentinel/imports)
+as well as [import plugins](/sentinel/extending/plugins) that can be used within
+a policy. If defining a custom import plugin, the Sentinel CLI will launch the
+plugin, connect to it, configure it, and execute it as needed by the policy.
+
+The available configuration attributes for an `import "plugin"` are:
+
+- `source` (string, required) - Path to the import plugin executable.
+- `args` (list of string, optional) - A list of arguments to pass to the
+ executable when starting it.
+- `env` (map of string to string, optional) - A set of environmental variables
+ to set when launching the plugin.
+- `config` (map, optional) - Configuration for the plugin itself. This is
+ specific to each individual plugin. Please reference the documentation for
+ the plugin for more information.
+
+Standard import example:
+
+```hcl
+import "plugin" "time" {
+ config = { "timezone": "Australia/Brisbane" }
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"time\"\n\nmain = time.now.zone_string is \"+10:00\"",
+ "mocks": {
+ "time": "now = { \"zone_string\": \"+10:00\" }"
+ }
+}
+```
+
+Custom plugin example:
+
+```hcl
+# flipper receives a boolean and returns the opposite
+import "plugin" "flipper" {
+ source = "/path/to/flipper"
+}
+```
+
+```json sentinel
+{
+ "policy": "import \"flipper\"\n\nmain = flipper.flip(false)",
+ "mocks": {
+ "flipper": "flip = func(input) { return not input }"
+ }
+}
+```
+
+#### Static Imports
+
+Static import configuration allows you to supply a data file and make
+it available to a policy via an import statement.
+
+The available configuration attributes for an `import "static"` are:
+
+- `source` (string, required) - Path to the static data file.
+- `format` (string, required) - The format of the static data. Currently, only
+ the "json" value is supported.
+
+Static import example:
+
+```hcl
+import "static" "people" {
+ source = "./data/people.json"
+ format = "json"
+}
+```
+
+```json sentinel
+{
+ "policy": "import \"people\"\n\nmain = length(people.names) > 0",
+ "mocks": {
+ "people": "names = [\"John Smith\", \"Jane Smith\"]"
+ }
+}
+```
+
+### Globals
+
+Global data is injected directly into the global scope of the policy.
+This can be used to simulate global data that may be injected by a
+Sentinel-enabled application.
+
+Global data is specified by the `global` key. The value is a map of
+variables to inject directly into the running policy.
+
+Example:
+
+```hcl
+global "time" {
+ value = {
+ now = {
+ day = 31
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass. Notice
+that we don't have to do any `import`. The values of `global` are
+injected directly into the global scope.
+
+```json sentinel
+{
+ "policy": "main = time.now.day == 31",
+ "globals": {
+ "time": {
+ "now": {
+ "day": 31
+ }
+ }
+ }
+}
+```
+
+### Parameters
+
+The `param` section allows you to supply values for the
+[parameters](/sentinel/language/parameters) found in a policy. Values
+entered here will satisfy required parameters in a policy, in addition to
+override any defaults. Note that any of the manual parameter value methods
+supported by `sentinel apply` will override the values set here.
+
+```hcl
+param "foo" {
+ value = "bar"
+}
+```
+
+### Test Cases
+
+Test cases specify the test cases for the [test command](/sentinel/commands/test).
+
+Tests are specified by the `test` key. The value of this is a map of
+string to boolean. The key is the name of a rule and the value is the
+expected value of that rule. If a rule is not specified, than any value is
+allowed.
+
+Example:
+
+```hcl
+test {
+ rules = {
+ main = true
+ days = []
+ }
+}
+```
+
+For the policy:
+
+```json sentinel
+{
+ "policy": "valid_days = rule {\n\tfilter days as d {\n\td is \"monday\"\n\t}\n}\n\nmain = rule { valid_days is empty }",
+ "globals": {
+ "days": []
+ }
+}
+```
+
+For more information, read about the [test command](/sentinel/commands/test).
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/configuration/overrides.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/configuration/overrides.mdx
new file mode 100644
index 0000000000..ca1a6842b1
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/configuration/overrides.mdx
@@ -0,0 +1,80 @@
+---
+page_title: Override Files
+sidebar_current: docs-configuration-overrides
+description: >-
+ The Sentinel CLI's configuration can be overriden using override files.
+ Override files merge additional settings into existing configuration.
+layout: docs
+---
+
+# Override Files
+
+As outlined in the configuration [overview](/sentinel/configuration#Apply),
+the normal behavior of Sentinel is to load all `.hcl` files into a single
+configuration object.
+
+In addition to appending multiple configuration files, Sentinel allows for the
+rare case of overriding configuration through override files. Override files
+are any files that match either `_override.{hcl,json}` or `override.{hcl,json}`.
+
+Sentinel will parse override files after it has loaded the primary
+configuration, and then process each override file in turn (in lexicographical
+order). All top level blocks other than [test](/sentinel/configuration#test-cases)
+require the block to already be defined in the primary configuration. It will
+then attempt to merge the override block into the existing configuration.
+
+## Example
+
+If you had a Sentinel configuration `sentinel.hcl` that contained the following:
+
+```hcl
+policy "main" {
+ source = "./main.sentinel"
+ enforcement_level = "advisory"
+}
+```
+
+And you created a file `override.hcl` that contained the following:
+
+```hcl
+policy "main" {
+ enforcement_level = "soft-mandatory"
+}
+```
+
+Sentinel will merge the contents of the override into the former, providing the
+following configuration:
+
+```hcl
+policy "main" {
+ source = "./main.sentinel"
+ enforcement_level = "soft-mandatory"
+}
+```
+
+## Merging Behavior
+
+The general rule, which applies in most cases, is:
+
+- A top-level block in an override file merges with a block in a normal
+ configuration file that has the same block header. The block header is the
+ block type and any quoted labels that follow it.
+- Within a top-level block, an attribute argument within an override block
+ replaces any argument of the same name in the original block.
+- Within a top-level block, any nested blocks within an override block replace
+ all blocks of the same type in the original block. Any block types that do not
+ appear in the override block remain from the original block.
+- The contents of nested configuration blocks are not merged.
+- The resulting merged block must still comply with any validation rules that
+ apply to the given block type.
+
+If more than one override file defines the same top-level block, the overriding
+effect is compounded, with later blocks taking precedence over earlier blocks.
+Overrides are processed in order first by filename (in lexicographical order)
+and then by position in each file.
+
+## Required Attributes
+
+It is important to note that all attributes inside an override file are
+considered to be optional, meaning that an override file can itself fail
+validation rules for block types.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/configuration/remote-sources.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/configuration/remote-sources.mdx
new file mode 100644
index 0000000000..b64e4d3abc
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/configuration/remote-sources.mdx
@@ -0,0 +1,165 @@
+---
+page_title: Remote Sources
+sidebar_current: docs-configuration-remote-sources
+description: >-
+ There are a number of options for formatting the URL provided to the
+ source attribute on policy and module blocks.
+layout: docs
+---
+
+# Remote Sources
+
+There are a number of options for formatting the URL provided to the
+`source` attribute on `policy` and `import "module"` blocks. This flexibility
+should solve most use cases and allow for reusable policies and modules.
+
+### Supported Protocols
+
+-> **NOTE:** All protocols are supported on the CLI, however each production
+Sentinel integration may not. Be sure to read the appropriate integration
+documentation to check the supported protocols.
+
+- Local filesystem
+- Git
+- Mercurial
+- HTTP
+- Amazon S3
+- Google GCP
+
+### Forced Protocols
+
+Due to the ambiguous nature of URLs, it is possible to force a particular
+protocol to be used during the fetch. To achieve this, simply prefix the url
+with the appropriate protocol as listed below:
+
+- `file` - Local filesystem
+- `git` - Git
+- `hg` - Mercurial
+- `http` or `https` - HTTP
+- `s3` - Amazon S3
+- `gcp` - Google GCP
+
+Example:
+
+```
+git::http://github.com/hashicorp/sentinel.git
+```
+
+The above would download the provided HTTP URL using the Git protocol.
+
+### Protocol Options
+
+In addition to some global options, there are options available to specific
+protocols to perform features such as authentication.
+
+#### Subdirectories / File Pathing
+
+When supplying URLs that point to a directory (eg. `git`), a subdirectory and
+file can be provided by suffixing the URL with `//` and the path. For example,
+if we have a git repository that has a policy, `main.sentinel` in the root, we
+could directly fetch the file by using the following:
+
+```
+git::https://github.com/hashicorp/sentinel-docs-example.git//main.sentinel
+```
+
+Or, if the file was nested in the `policies` folder:
+
+```
+git::https://github.com/hashicorp/sentinel-docs-example.git//policies/main.sentinel
+```
+
+#### Checksumming
+
+For downloads of any protocol, you can automatically verify a checksum. To
+checksum a file, append a `checksum` query parameter to the URL. The parameter
+value can be in the format of type:value or just value, where type is "md5",
+"sha1", "sha256", "sha512" or "file" . The "value" should be the actual
+checksum value or download URL for "file". When type part is omitted, type will
+be guessed based on the length of the checksum string.
+
+```
+./foo.sentinel?checksum=md5:b7d96c89d09d9e204f5fedc4d5d55b21
+
+./foo.sentinel?checksum=b7d96c89d09d9e204f5fedc4d5d55b21
+
+./foo.sentinel?checksum=file:./foo.txt.sha256sum
+```
+
+#### Unarchiving
+
+An `archive` query parameter can be supplied to explicitly state what format
+to attempt to extract, if required. The supported formats are:
+
+- `tar.gz` and `tgz`
+- `tar.bz2` and `tbz2`
+- `tar.xz` and `txz`
+- `zip`
+- `gz`
+- `bz2`
+- `xz`
+
+If a URL has an extension that matches one of the supported formats, it will
+use that format to unarchive.
+
+```
+./file.zip
+```
+
+The above would use the `zip` format to unarchive.
+
+```
+./path/to/file?archive=zip
+```
+
+Would force the `zip` format. If for some reason you required archiving to be
+disabled, this can also be achieved by using the `false` value.
+
+```
+./path/to/file?archive=false
+```
+
+#### Git (`git`)
+
+- `ref` - The Git ref to checkout. This is a ref, so it can point to a commit
+ SHA, a branch name, etc. If it is a named ref such as a branch name, it will
+ be updated to the latest on each get.
+- `sshkey` - An SSH private key to use during clones. The provided key must be
+ a base64-encoded string. For example, to generate a suitable sshkey from a
+ private key file on disk, you would run base64 -w0 \.
+ **Note:** Git 2.3+ is required to use this feature.
+- `depth` - The Git clone depth. The provided number specifies the last `n`
+ revisions to clone from the repository.
+
+The git getter accepts both URL-style SSH addresses like
+`git::ssh://git@example.com/foo/bar`, and "scp-style" addresses like
+`git::git@example.com/foo/bar`. In the latter case, omitting the `git::` forced
+prefix is allowed if the username prefix is exactly git@.
+
+The "scp-style" addresses cannot be used in conjunction with the `ssh://`
+scheme prefix, because in that case the colon is used to mark an optional port
+number to connect on, rather than to delimit the path from the host.
+
+#### Mercurial (`hg`)
+
+- `rev` - The Mercurial revision to checkout.
+
+#### HTTP (`http` or `https`)
+
+Basic Authentication
+
+To use HTTP basic authentication, simply prepend `username:password@` to the
+hostname in the URL such as `https://Aladdin:OpenSesame@www.example.com/index.html`.
+All special characters, including the username and password, must be URL
+encoded.
+
+#### S3 (`s3`)
+
+The following query parameters are available and will take priority when
+authenticating against an S3 bucket.
+
+- `aws_access_key_id` - AWS access key.
+- `aws_access_key_secret` - AWS access key secret.
+- `aws_access_token` - AWS access token if this is being used.
+- `aws_profile` - Use this profile from local ~/.aws/ config. Takes priority
+ over the other three.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/consul.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/consul.mdx
new file mode 100644
index 0000000000..7a3aaaa697
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/consul.mdx
@@ -0,0 +1,47 @@
+---
+page_title: Consul and Sentinel
+sidebar_current: docs-app-consul
+description: >-
+ Consul Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies are applied during writes to
+ the KV Store.
+layout: docs
+---
+
+# Consul
+
+[Consul Enterprise](https://www.hashicorp.com/products/consul/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies are applied during writes to the KV Store.
+
+Sentinel policies have access to the key/value being written. They can be used to
+allow or deny the modification. The information that Sentinel policies have access
+to will expand over time.
+
+The Consul integration with Sentinel is documented in depth in the
+[Consul Enterprise documentation](https://www.consul.io/docs/guides/sentinel.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Input validation depending on the name of the key.**
+
+```sentinel
+main = rule { valid_key() }
+
+required = [
+ ["port", "\\d+"], # ports must be integers
+ ["name", "\\w+"], # name must be a word
+]
+
+valid_key = func() {
+ for required as v {
+ if key is v[0] {
+ return value matches v[1]
+ }
+ }
+
+ return false
+}
+```
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/extending/index.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/extending/index.mdx
new file mode 100644
index 0000000000..a39cb0384d
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/extending/index.mdx
@@ -0,0 +1,52 @@
+---
+page_title: Extending Sentinel
+sidebar_current: docs-extending
+description: Learn how to create your own Sentinel imports to extend Sentinel's functionality.
+layout: docs
+---
+
+# Extending Sentinel
+
+-> **NOTE:** Sentinel's extensibility is currently undergoing large
+improvements - watch this space for future updates!
+
+Sentinel can be extended by writing additional
+[imports](/sentinel/language/imports). These imports can bring new functionality
+to Sentinel by allowing access to new data or by the addition of new functions.
+This section is designed to show you how to accomplish this extensibility and
+describe how it works.
+
+Currently, the only usable way to add additional imports is via
+[modules](#modules). While [plugins](#plugins) currently work under the Sentinel
+CLI, no Sentinel integration currently supports their use.
+
+-> You may also be interested in advanced details on [import
+internals](/sentinel/extending/internals).
+
+## Modules
+
+Modules allow you to re-use Sentinel code as an import. Modules are loaded
+external of policies and mapped to imports. This is usually configured via a
+file such as the [CLI configuration file](/sentinel/configuration).
+
+See the [modules](/sentinel/extending/modules) section for more details.
+
+## Plugins
+
+-> **NOTE:** Plugins are currently only supported on the CLI and not in any
+production Sentinel integration, with the exception of existing configuration
+in [Nomad](https://www.nomadproject.io/docs/configuration/sentinel).
+
+Imports can also be implemented as plugins when Sentinel code itself is too
+restrictive to represent the import, or if you need access to features not
+normally available to the Sentinel runtime.
+
+See the [plugins](/sentinel/extending/plugins) section for more details.
+
+## Static Imports
+
+Static imports provide support for loading data files into the Sentinel runtime,
+making them available as an import.
+
+See the [static imports](/sentinel/extending/static-imports) section for more
+details.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/extending/internals.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/extending/internals.mdx
new file mode 100644
index 0000000000..11be9d3d67
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/extending/internals.mdx
@@ -0,0 +1,252 @@
+---
+page_title: >-
+ Extending Sentinel: Internals
+sidebar_current: docs-extending-internals
+description: This Section covers some details about Sentinel's import internals.
+layout: docs
+---
+
+# Extending Sentinel: Internals
+
+-> **Advanced Topic!** This page covers low-level technical details of Sentinel.
+You don't need to understand these details to effectively use Sentinel. The
+details are documented here for those who wish to learn about them.
+
+This section covers some of the internal details of Sentinel's import system.
+The goal of this section is to help remove notion of "magic" from Sentinel, to
+allow you to trust and understand what Sentinel is doing with imports, and also
+to help practitioners and developers alike understand the differences between
+the ways that imports can be loaded into Sentinel.
+
+## Language and Runtime
+
+Understanding the import system in Sentinel generally requires understanding of
+two key concepts within Sentinel: the _language_, and the _runtime
+implementation_ of the language.
+
+The Sentinel language and the rules governing it are detailed in the [Sentinel
+Language Specification](/sentinel/language/spec). A runtime implementation must
+conform to the rules laid out in the specification at a minimum. However, to
+meet the design goals of a particular implementation, the runtime may implement
+features on top of the language. The subtle implementation details within the
+runtime may also vary while still being compliant with the specification.
+
+The core runtime included within the [Sentinel CLI](/sentinel/commands) is
+sometimes referred to as the _reference implementation_ of the Sentinel
+language. This runtime is also the main runtime embedded within each
+Sentinel-enabled HashiCorp product such as Terraform Cloud and Enterprise, Vault
+Enterprise, Nomad Enterprise, and Consul Enterprise. The runtime includes an API
+to allow these integrations implement Sentinel in as robust or as restrictive of
+a way as possible, so depending on the implementation, your experience may vary.
+
+## Sentinel's Import Model
+
+The concept of Imports within Sentinel is a _language_ feature. You can see the
+exact rules governing imports within the
+[Imports](/sentinel/language/spec#imports) section of the specification.
+
+Within the runtime, there are currently two major classifications of imports:
+
+- **Modules:** The mapping of Sentinel code to an import, essentially mapping a
+ scope of values to a particular package.
+- **Binary Imports:** A grouping of embedded and external plugins written using
+ the Sentinel SDK. These are comprised of internal or embedded imports (usually
+ the [standard imports](/sentinel/imports) and imports included by an
+ integration) and import plugins.
+
+Below is a diagram explaining in brief the relationship between the langauge
+and runtime features.
+
+
+
+This kind of topology ultimately minimizes the visibility of implementation
+internals to the actual policy language. This allows us to keep the Sentinel
+language itself simple and keep concerns such as module or plugin management,
+validation, and configuration out of the language. These kinds of things would
+impact the readability of a policy, possibly present security challenges, and
+would ultimately be of no use to more minimal embedded applications.
+
+### Implementation Differences Between Import Types
+
+As one would imagine, both modules and binary imports are loaded in much
+different ways, and the methods that they expose data to Sentinel differ as
+well. The effect is generally the same however across both, and we take care to
+ensure that both modules and binary imports behave as close as possible to each
+other, however there are some subtle differences:
+
+- Modules currently support the exporting of rules, but do not support function
+ calls with object receiver data (as seen in some standard imports such as
+ [`decimal`](/sentinel/imports/decimal), [`http`](/sentinel/imports/http), and
+ [`time`](/sentinel/imports/time)). This will be coming in a later release.
+- Binary imports cannot export rules. However, they do support object receiver
+ data (see [`framework.New`](/sentinel/extending/plugins#framework-new) in
+ [Extending Sentinel: Plugins](/sentinel/extending/plugins)).
+
+## Modules
+
+_Modules_ are essentially Sentinel code that is intended to be re-used in multiple
+policies as an import.
+
+The mechanism of module loading is fairly straightforward: during initialization
+of the Sentinel runtime, modules are parsed along with policies. The parsed
+_abstract syntax tree_ (AST) for each module is loaded into the runtime under
+the specified _import path_. These are then passed to the lower-level
+interpreter during the evaluation of each policy.
+
+As with policy code, during evaluation, a module's AST is walked to execute the
+code within that specific module. The module data is then stored within an
+specific scope mapped to the import name. The module data can then be accessed
+through the import via selector expressions and function calls (e.g.,
+`foo.some_map` or `foo.some_func()` for a module loaded via `import "foo"`).
+
+The AST for a module is only walked if a policy imports it, and it is _only
+walked once_. That means if a policy and a module import the same module, or a
+policy imports the same module twice (using [import
+aliases](/sentinel/language/imports#aliases)), those instances will share state.
+If you call a function that alters a singleton variable within the module, that
+singleton will be modified for all instances of the import.
+
+-> **Fun fact!** When you [mock an import with Sentinel
+code](/sentinel/configuration#mocking-with-sentinel-code), you are actually
+using a module. The module is loaded with a flag that allows it to override an
+existing import, which would normally give a runtime error.
+
+### Map and List Cloning for Module Calls
+
+It's also worthwhile noting that map and list values are cloned for modules.
+
+Consider the case where you have a module with a map singleton.
+
+```sentinel
+// modules/foo.sentinel
+a_map = {"a": "b"}
+```
+
+Normally, assignment to the singleton, even when using an index expression, is
+blocked, so `foo.a_map["c"] = "d"` would not pass semantic checking.
+
+However, even when you try to do assignment via an intermediary, you will still
+be only modifying the copy, and the original will not be affected:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+a_map_copy = foo.a_map
+a_map_copy["c"] = "d" // Only modifies copy, does not modify module singleton
+print(foo.a_map) // Still only prints {"a": "b"}
+```
+
+This is to ensure that modules are consistent with binary imports in how return
+data is handled, and the expectation that most non-object data within an import
+is read-only, with the exception of modification of singleton data via
+functions. As return of complex object and collection data in a binary import is
+always via copy, it's not possible to modify any value elements within the
+import in a similar fashion.
+
+There are also some other implications of this cloning that are of note:
+
+- Rules are not cloned, either within a list or map, or by themselves. This is
+ to ensure that [memoization](/sentinel/language/rules#lazy-and-memoized)
+ functions correctly. As only a module can export rules at this time, there is no
+ parity for this in binary imports.
+- Functions are _not_ cloned. This mainly has implications for scope - for
+ example, if you assign a function to another value, or assign an object to a
+ value that has a method that utilizes a global module variable, those calls will
+ reference and/or modify those values.
+
+Functions, while represented differently in binary imports, generally follow the
+same logic here - as they would be invoked in the same package within the binary
+import, any singleton values they accessed there would be affected in a similar
+fashion. As rules are pseudo-functions, this generally makes sense here too.
+
+## Binary Imports
+
+_Binary imports_ is a grouping referring to all imports that are designed with
+the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). These can be
+either internally embedded, or executed via a plugin.
+
+All imports found in the [standard library](/sentinel/imports) are binary
+imports, embedded internally.
+
+The main difference between internally embedded imports and external plugins is
+whether or not the runtime needs to execute a plugin binary as part of
+[lifecycle management](#lifecycle), and whether or not it needs to
+[communicate](#communication) over RPC. Internal binary imports do not require
+these steps, so their execution model is somewhat simpler; however, technically,
+they still are the same as external plugins in terms of design model and value
+conversion. Most standard imports are even tested using the [SDK test
+suite](/sentinel/extending/plugins#testing), which builds the import as an
+external plugin.
+
+The remainder of this section discusses topics mostly relevant to external
+plugins. A large amount of technical detail regarding binary import development
+(also applicable to embedded binary imports) can be seen on the
+[Plugins](/sentinel/extending/plugins) page.
+
+-> **NOTE:** At this time, plugins can only be written for the Sentinel CLI, and
+cannot be used with any of Sentinel's integrations.
+
+### Plugin Basics
+
+Sentinel plugins are built on top of the HashiCorp [go-plugin
+system](https://github.com/hashicorp/go-plugin). This is the same plugin system
+powering all pluggable HashiCorp tools such as
+[Terraform](https://www.terraform.io), [Vault](https://www.vaultproject.io), and
+more. go-plugin is a system that has been used in production for millions of
+users for over 5 years.
+
+Plugins are executable binaries. Sentinel is responsible for launching and
+managing the lifecycle of plugins. Once a plugin is running, Sentinel
+communicates with the plugin via [gRPC](https://grpc.io/). The [protocol is open
+source](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/import.proto)
+to allow anyone to write a Sentinel plugin.
+
+### Lifecycle
+
+Sentinel launches plugins and is responsible for plugin lifecycle.
+
+The runtime optimizes for latency by launching the plugin as soon as it is
+configured, rather than when a policy requires it. This ensures that the plugin
+is ready to be used immediately. A plugin is only closed when Sentinel is
+reconfigured to no longer allow that plugin or if the Sentinel-enabled
+application is closing.
+
+When a plugin is removed from the configuration, Sentinel may wait to shut down
+the plugin until all currently executing policies that are using that plugin
+complete. New policy executions will not be allowed to use the old plugins.
+
+Plugins are automatically restarted if they shut down unexpectedly. Policies
+that were executing while this happens may fail. The Sentinel system is
+improving to more gracefully understand locations where it is safe to retry an
+execution.
+
+### Communication
+
+An [initial
+handshake](https://github.com/hashicorp/go-plugin/blob/master/docs/internals.md)
+is done via stdout to determine the main communication location. Following the
+handshake, communication will either occur on a local Unix domain socket or via
+a local-only TCP socket. This communication is done mostly over the low-level
+[`Get`](https://github.com/hashicorp/sentinel-sdk/blob/2b4db07dd12b55142f0c016f301af80aa4422520/proto/import.proto#L12)
+RPC call.
+
+#### Value Conversion
+
+As can generally be seen in [Developing an Import
+Plugin](/sentinel/extending/plugins#developing-an-import-plugin), the Sentinel
+type system is not exposed to binary imports. While explicit values for null and
+undefined exist, values are mostly converted over the wire from their Go values
+to their Sentinel equivalents.
+
+This has a few implications:
+
+- As discussed in [Map and List Cloning for Module
+ Calls](#map-and-list-cloning-for-module-calls), Map and list data returned from
+ binary import value lookups or function calls is always cloned. These can be
+ freely modified without affecting anything within the binary import.
+- It's currently impossible to represent certain Sentinel types such as
+ [rules](/sentinel/language/rules) within a binary import. This is not a major
+ issue at this point in time as practitioners generally do not look to binary
+ imports for rules; we may examine this as a later time if this situation
+ changes.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/extending/modules.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/extending/modules.mdx
new file mode 100644
index 0000000000..9846d263e5
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/extending/modules.mdx
@@ -0,0 +1,135 @@
+---
+page_title: >-
+ Extending Sentinel: Modules
+sidebar_current: docs-extending-modules
+description: >-
+ Modules allow you to re-use Sentinel code as an import.
+layout: docs
+---
+
+# Extending Sentinel: Modules
+
+Modules allow you to re-use Sentinel code as an import. This allows for the
+export of any general construct that Sentinel supports, including values,
+functions, and even rules. As such, it's a great way to package code that is
+useful across multiple policies, reducing boilerplate and making final policy
+code simpler.
+
+You may want to use modules for:
+
+- **Writing a simple re-usable helper library.** If you mostly know Sentinel or
+ have helpers that you have written in Sentinel, moving these helpers to a module
+ will offer a low-friction way of facilitating their re-use.
+- **Writing re-usable rules.** If you have a set of
+ [rules](/sentinel/language/rules) you know you want to use across multiple
+ policies, bundling them into a module is a great way of abstracting the logic
+ away.
+- **Abstracting existing Sentinel imports.** Using a module is a great way to
+ extend existing Sentinel imports by abstracting the common functionality that
+ you use within an import to your own workflow.
+
+This page describes how you can use modules within the context of the Sentinel
+CLI. For instructions for a particular integration, see the documentation for
+that product.
+
+-> **Not all Sentinel-enabled applications support modules.** Please refer
+to the documentation of the specific Sentinel-enabled application. Some
+applications disable modules for security reasons.
+
+## Configuring a Module
+
+A module is configured within the Sentinel CLI by adding an entry for it in
+within the `imports` section of the [CLI configuration
+file](/sentinel/configuration). The key for each entry specifies the path that
+the module is imported as.
+
+```hcl
+import "module" "foo" {
+ source = "modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "modules/bar.sentinel"
+}
+```
+
+In this example, we have two modules:
+
+- Import path `foo`, being loaded from the code within `modules/foo.sentinel`.
+- Import path `bar`, being loaded from the code within `modules/bar.sentinel`.
+
+See the [Import Modules](/sentinel/configuration#import-modules) section within
+the CLI configuration file syntax page for more details.
+
+### Remote Modules
+
+In addition to loading from a local source, modules can be loaded from a remote
+location. This can increase reusability and allow for sharing modules across
+multiple configurations.
+
+### Testing Using Modules
+
+As with [mocks](/sentinel/configuration#mocking-with-sentinel-code), modules
+are loaded relative to the configuration file location when using `sentinel test`. As such, you need to account for this in your test configuration files:
+
+```hcl
+import "module" "foo" {
+ source = "../../modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "../../modules/bar.sentinel"
+}
+```
+
+This could be a test file for a policy (example: `policy.sentinel`), residing
+under the correct test file directory for this policy (example:
+`test/policy/pass.json`).
+
+## Writing and Using a Module
+
+Writing a module is nearly the same as writing a Sentinel policy, except for the
+following two exceptions:
+
+- Modules do not require [`main`](/sentinel/writing/basic#the-simplest-policy)
+ to be present. It can be, but it will not carry any extra significance as it
+ does within a policy.
+- [`param`](/sentinel/language/parameters) declarations cannot be present in a module. If they are encountered,
+ a runtime error is given.
+
+Once you have a complete module ready for use and configured, using the module
+is as simple as importing it.
+
+Building on the above [configuration example](#configuring-a-module), we can
+export a simple test function in the module `foo` by adding this code to
+`modules/foo.sentinel`:
+
+```sentinel
+// modules/foo.sentinel
+hello = func() {
+ print("hello world!")
+ return undefined
+}
+```
+
+We can then import it within our policy and use it:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+// prints "hello world" in the trace
+foo.hello()
+
+main = true
+```
+
+Note that modules are considered [imports](/sentinel/language/spec#imports) by
+the Sentinel runtime and as such conform to the specification. This means that
+you cannot directly assign values to anything behind a module scope. You can,
+however, use functions to change state.
+
+-> **NOTE:** At this time, modules do not support object receiver data in the
+way that some imports do, like [`time`](/sentinel/imports/time),
+[`decimal`](/sentinel/imports/decimal), and [`http`](/sentinel/imports/http).
+Support for this will be coming in later versions of Sentinel.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/extending/plugins.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/extending/plugins.mdx
new file mode 100644
index 0000000000..e80de5d4b1
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/extending/plugins.mdx
@@ -0,0 +1,521 @@
+---
+page_title: >-
+ Extending Sentinel: Plugins
+sidebar_current: docs-extending-plugins
+description: >-
+ Plugins allow you to write imports as standalone executables, allowing you to
+ extend Sentinel in ways not normally possible with policy code alone.
+layout: docs
+---
+
+# Extending Sentinel: Plugins
+
+-> **NOTE:** Plugins are currently only supported on the CLI and not in any
+production Sentinel integration, with the exception of existing configuration
+in [Nomad](https://www.nomadproject.io/docs/configuration/sentinel).
+
+Plugins allow you to write imports as standalone executables, allowing you to
+extend Sentinel in ways not normally possible with policy code alone.
+
+You might want to write or use a plugin when you need to:
+
+- **Use a provider or API integration for Sentinel.** In this case, you will
+ probably be working with a provider SDK, or other libraries that will be making
+ complex network requests to external services. Sentinel only provides limited
+ capabilities for these kinds of operations in the standard library, so writing
+ this kind of import as a module is not optimal.
+- **Introduce novel functionality not currently supported by Sentinel.**
+ Sentinel's policy language is limited by design to encourage simple policies,
+ reduce its runtime footprint, and to keep it efficient and safe. This will
+ mean that some functionality may be difficult or impossible to express as
+ Sentinel code, and would require a plugin to write.
+- **Extend a complex module.** Additionally, modules are restricted to a single
+ file of Sentinel code, so these will naturally become more and more unwieldy as
+ you continue to add to them. Eventually, there might be a time where a plugin is
+ better suited for the functionality, especially if it's a general-purpose
+ library.
+
+This section details how import plugins are currently configured in the Sentinel
+CLI, and some detail on how they are written. As we continue to build on the
+ability to use import plugins, we will extend this section with more details.
+
+-> **Not all Sentinel-enabled applications will support plugins.** Some
+applications will disable plugins for security reasons. For those that do enable
+plugins, the method to configure plugins will vary.
+
+## Installing Plugins
+
+Sentinel plugins are standalone executables. Sentinel-enabled applications such
+as the CLI launch these plugins and communicate with them via
+[RPC](https://en.wikipedia.org/wiki/Remote_procedure_call). To install a plugin,
+the first step is to download the plugin executable.
+
+After downloading the plugin, you must add it in the CLI configuration file,
+setting its name, path, and any arguments, environment variables, or
+configuration. An example is below:
+
+```hcl
+import "plugin" "custom_time" {
+ source = "/path/to/sentinel-import-time"
+ config = { "fixed_time": 1504155600 }
+}
+```
+
+-> **NOTE:** We are using "custom_time" as standard import paths cannot be
+overriden. See [Configuration File Syntax](/sentinel/configuration#imports) for
+more details.
+
+After configuring the plugin, it will be available on the next `sentinel apply`
+or `sentinel test`.
+
+For more details on configuration, see the
+[plugins](/sentinel/configuration#plugins) section of the [CLI configuration
+file syntax](/sentinel/configuration).
+
+## Debugging Plugins
+
+The Sentinel CLI logs when it launches, configures, and closes a plugin. The
+plugin may also log additional information when it is running.
+
+To debug issues with a plugin, you can usually refer to these logs. Depending on
+the plugin configuration, the logs you see may vary - refer to the plugin
+documentation on how to enable logs for a particular plugin.
+
+## Developing an Import Plugin
+
+Anyone can develop a Sentinel import plugin using the [Sentinel
+SDK](https://github.com/hashicorp/sentinel-sdk). The SDK contains a high-level
+framework for writing plugins in [Go](https://golang.org) including a test
+framework. Additionally, the SDK contains the low-level [gRPC protocol buffers
+definition](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/plugin.proto)
+for writing plugins in other languages.
+
+The rest of this page will document how to write a new Sentinel plugin in Go
+using the Sentinel SDK and the high-level framework provided. You are expected
+to already know the basics of Go and have a properly configured Go installation.
+
+Throughout this page, we'll be developing a simple plugin to access time values.
+
+-> **NOTE:** The example here is not reflective of the actual implementation of
+the [`time`](/sentinel/imports/time) standard import, so make sure to not
+get the two confused.
+
+### Plugin Framework
+
+The Sentinel SDK provides a high-level
+[framework](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework)
+for writing plugins. We recommend using this framework.
+
+#### Basic Concepts
+
+This framework works by implementing the correct Go interfaces.
+
+As a general overview:
+[`framework.Plugin`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Plugin)
+implements the
+[`sdk.Plugin`](https://godoc.org/github.com/hashicorp/sentinel-sdk#Plugin)
+interface and therefore is a valid import plugin. You must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface to configure the plugin. The root may then delegate nested access to
+various
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+implementations. Other interfaces are implemented to provide functionality past
+what is provided by the basic `framework.Namespace` implementation - these are
+documented below.
+
+This all may sound like a lot of interfaces, but each interface typically only
+requires a single function implementation and you're only required to implement
+a single namespace (`framework.Namespace`).
+
+As we move on, we'll use real examples to make this clear.
+
+#### Implementing framework.Root
+
+To begin, you must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface. This is the interface representing the root of your plugin. The root
+itself must be implement
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace),
+which allows values to be accessed. Note that the root can optionally implement
+other interfaces, but they're more advanced and omitted here for simplicity.
+
+For our custom time example, our root may look like this:
+
+```sentinel
+type root struct {
+ time time.Time
+}
+
+// framework.Root impl.
+func (m *root) Configure(raw map[string]interface{}) error {
+ if _, ok := raw["timestamp"]; !ok {
+ raw["timestamp"] = time.Now().Unix()
+ }
+
+ v := raw["timestamp"]
+ timestamp, ok := v.(int)
+ if !ok {
+ return fmt.Errorf("invalid timestamp type %T", v)
+ }
+
+ m.time = time.Unix(timestamp, 0).UTC()
+ return nil
+}
+
+// framework.Namespace impl.
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "minute":
+ return m.time.Minute(), nil
+ }
+
+ return nil, nil
+}
+```
+
+This example implements `framework.Root` and `framework.Namespace` and would
+enable the following within a Sentinel policy once the completed plugin is
+installed.
+
+```sentinel
+import "custom_time"
+
+print(custom_time.minute)
+main = true
+```
+
+#### framework.Namespace
+
+The
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+interface implements a namespace of values. You saw above that
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+must itself be a namespace. However, a namespace `Get` implementation may
+further return namespaces to access nested values.
+
+This enables behavior such as `custom_time.month.string` vs. `custom_time.month.index`.
+Notice each of these examples accesses a nested value within `month`. This can
+be modeled as namespaces within the framework.
+
+In the example below, we return a new namespace for `month` to do just this:
+
+```sentinel
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "month":
+ return &namespaceMonth{Month: m.time.Month()}, nil
+ }
+
+ // ...
+}
+
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil nil
+}
+```
+
+#### Primitive Types and Structs
+
+A namespace may also return any primitive Go type as well as structs. The
+framework automatically exposes primitive values as you would expect. For
+structs, exported fields are lowercased and exposed to the policies. The
+`sentinel` struct tag can be used to control how Sentinel can access the field.
+
+In the example below, we expose a struct directly from the root namespace:
+
+```sentinel
+type Location struct {
+ Name string
+ TimeZone string `sentinel:"time_zone"`
+}
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "location":
+ return &Location{Name: "somewhere", TimeZone: "some zone"}, nil
+ }
+
+ // ...
+}
+```
+
+This makes the following values work within a Sentinel policy:
+`time.location.name`, `time.location.time_zone`.
+
+If a field has an empty `sentinel` struct tag (example: `sentinel:""`),
+then that field will not be accessible from a policy.
+
+#### Optional Interfaces
+
+The following are optional interfaces that can be implemented on top of
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace).
+All of these interfaces can be implemented at any level, except for
+[`framework.New`](#framework-new), which only works at the root level.
+
+##### `framework.Call`
+
+The
+[`framework.Call`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Call)
+interface can be implemented to support function calls.
+
+Implementing this interface is very similar to attribute access, except instead
+of returning the attribute value, you return a function. The framework uses Go
+reflection to determine the argument and result types and calls it. If the
+argument types do not match or the signature is otherwise invalid, an error is
+returned.
+
+For example, let's implement a function to add months to the current month:
+
+```sentinel
+func (m *root) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *root) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.time.AddDate(0, n, 0).Month()}
+}
+```
+
+You can now call the function. If today was in the month of September,
+the policy below would pass:
+
+```sentinel
+import "custom_time"
+
+main = custom_time.add_month(4).string == "January"
+```
+
+##### `framework.Map`
+
+The optional
+[`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+interface allows an alternative method to to present a namespace back to a
+Sentinel policy as a map.
+
+`framework.Map` is best paired with
+[`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys),
+which allows you to quickly fetch the necessary data via `Get` calls on the
+namespace:
+
+```
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil, nil
+}
+
+func (m *namespaceMonth) Map() (map[string]interface{}, error) {
+ return framework.MapFromKeys(m, []string{"string", "index"})
+}
+```
+
+##### `framework.New`
+
+The optional
+[`framework.New`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#New)
+interface can be implemented on a root namespace to add _methods_ to your
+namespaces.
+
+Data returned from a namespace is memoized and returned as a map, and is
+normally not callable - to call a function to operate on the data, you would
+need to create a new namespace from the top-level of the plugin, and then make a
+function call on the result within the same expression.
+
+Let's consider the [root example](#implementing-framework-root). Let's say,
+instead of hard-coding the time value at configuration, we wanted to load it
+on-demand using a `time.now` key, and allow `add_month` to be callable on that
+value. Our root and de-coupled time namespace would now look like:
+
+```
+type root struct{}
+
+func (m *root) Configure(raw map[string]interface{}) error { return nil }
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "now":
+ return &namespaceTime{Time: time.Now()}
+ }
+
+ return nil
+}
+
+func (m *root) New(data map[string]interface{}) (framework.Namespace, error) {
+ if v, ok := data["unix"]; ok {
+ if t, ok := v.(int64); !ok {
+ return &namespaceTime{Time: time.Unix(t, 0)}
+ }
+
+ return nil, fmt.Errorf("expected timestamp to be int64, got %T", v)
+ }
+
+ return nil, nil
+}
+
+type namespaceTime struct {
+ Time time.Time
+}
+
+func (m *namespaceTime) Get(key string) interface{} {
+ switch key {
+ case "unix":
+ return m.Time.Unix()
+
+ // case ...
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) Get(key string) (interface{}, error) {
+ return framework.MapFromKeys(m, []string{"unix", "..."})
+}
+
+func (m *namespaceTime) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.Time.AddDate(0, n, 0).Month()}
+}
+```
+
+Via this example, we can now create and assign a `namespaceTime` using `t = custom_time.now`, and then call `t.add_month(months_to_add)` to return a
+`namespaceMonth` for the corresponding month.
+
+Methods on a namespace can also _modfiy_ the receiver data. So you can, for
+example, add a method named `increment_month` that takes no arguments, but
+increments the time stored in `namespaceTime.Time` by a month. Subsequent
+statements using the receiver would see the new value.
+
+Note that there are some restrictions and guidelines that you should take into
+account when using `framework.New`:
+
+- Only data that makes it back to a policy can be used as receiver data to
+ instantiate new namespaces. As such it's recommended to make use of
+ [`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+ and
+ [`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys)
+ to return all of the attribute data you need to construct new objects.
+- `framework.New` is only supported on the root namespace and has no effect on
+ namespaces below the root. Check all possible cases of receiver data within
+ the top-level `New` function and return the appropriate namespace based on the
+ input data.
+- Do not return general errors from your `New` method. When an unknown lookup is
+ made off of memoized data, it will hit your `New` method for possible
+ instantiation and key calls. This will ensure `undefined` is correctly
+ returned for these cases.
+- `framework.New` is designed to add utility to plugins where calling methods on
+ a value is more intuitive than just making a function call, or just returning
+ all of a data set as a memoized map. Use it sparingly and avoid using it on
+ recursively complex data sets.
+
+### Testing
+
+The SDK exposes a testing framework to verify your plugin works as
+expected. This test framework integrates directly into `go test` so it
+is part of a familiar workflow.
+
+The test framework works by dynamically building your plugin and
+running it against the `sentinel` CLI to verify it behaves as expected.
+This ensures that your plugin builds, your plugin communicates via RPC
+correctly, and that the behavior within a policy is also correct.
+
+The example below shows a complete ready table-driven test for our time plugin.
+You can run this with a normal `go test`.
+
+```sentinel
+package main
+
+import (
+ "os"
+ "testing"
+
+ "github.com/hashicorp/sentinel-sdk"
+ plugintesting "github.com/hashicorp/sentinel-sdk/testing"
+)
+
+func TestMain(m *testing.M) {
+ exitCode := m.Run()
+ plugintesting.Clean()
+ os.Exit(exitCode)
+}
+
+func TestPlugin(t *testing.T) {
+ cases := []struct {
+ Name string
+ Data map[string]interface{}
+ Source string
+ }{
+ {
+ "month",
+ map[string]interface{}{"timestamp": 1495483674},
+ `main = subject.month.string == "September"`,
+ },
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.Name, func(t *testing.T) {
+ plugintesting.TestPlugin(t, plugintesting.TestPluginCase{
+ Config: tc.Data,
+ Source: tc.Source,
+ })
+ })
+ }
+}
+```
+
+### Building Your Plugin
+
+To build your plugin, you must first implement the `main` function.
+The main function should just use the `rpc.Serve` method to serve the
+plugin over the Sentinel RPC layer:
+
+```sentinel
+package main
+
+import (
+ "github.com/hashicorp/sentinel-sdk"
+ "github.com/hashicorp/sentinel-sdk/rpc"
+)
+
+func main() {
+ rpc.Serve(&rpc.ServeOpts{
+ PluginFunc: func() sdk.Plugin {
+ return &framework.Plugin{Root: &root{}}
+ },
+ })
+}
+```
+
+You can then build this using `go build`. The output can be named
+anything. Once your plugin is built, [install it](#installing-plugins)
+and try it out!
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/extending/static-imports.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/extending/static-imports.mdx
new file mode 100644
index 0000000000..cb51beb5d8
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/extending/static-imports.mdx
@@ -0,0 +1,48 @@
+---
+page_title: >-
+ Extending Sentinel: Static Imports
+sidebar_current: docs-extending-static-imports
+description: >-
+ Static imports allow you to write imports that use static data files.
+layout: docs
+---
+
+# Extending Sentinel: Static Imports
+
+Static imports allow you to write imports that use static data files. This can
+provide methods of consuming data in ways that are not available via
+[modules](./modules) or [plugins](./plugins).
+
+### Configuring a Static Import
+
+The [configuration page](/sentinel/configuration#static-imports) details how to
+configure static imports.
+
+### Usage
+
+Using a static import is not that different to using a plugin or module. The
+import must first be introduced to the policy via an import statement.
+
+```sentinel
+import "people"
+```
+
+From here, the static data can be used throughout the policy as you would any
+normal Sentinel value.
+
+```sentinel
+admins = filter people as person {
+ person.role is "Admin"
+}
+```
+
+One differentiator for static imports is that you can directly reference the
+import without the use of selectors. For example, to print the value;
+
+```sentinel
+print(people)
+```
+
+This is due to the static data being directly loaded and converted into a
+Sentinel value, ready for use. Static imports, like modules and plugins, cannot
+be assigned a new value.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/functions/append.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/functions/append.mdx
new file mode 100644
index 0000000000..81395154f6
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/functions/append.mdx
@@ -0,0 +1,46 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Append'
+sidebar_current: docs-funcs-append
+description: The built-in function `append` appends a value to the end of a list.
+layout: docs
+---
+
+# Builtin Function: append
+
+The built-in function `append` appends a value to the end of a list. The list
+is modified in-place. The return value will always be [undefined](/sentinel/language/undefined).
+
+`append` called on any value other than a list or undefined will result
+in an immediate error.
+
+Examples:
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+### Why does this function return undefined?
+
+This function returns `undefined` to avoid unintended side effects of the function in the context
+of a [rule](/sentinel/language/rules).
+
+For example, if `append` returned the list value as a result, the following policy would depend
+on the order in which rules are referenced:
+
+```sentinel
+list = []
+a = rule { append(list, 1) contains 1 }
+b = rule { append(list, 2) contains 2 }
+
+// Scenario 1: list becomes [1, 2]
+main = rule { a and b }
+
+// Scenario 2: list becomes [2, 1]
+main = rule { b and a }
+```
+
+This sort of side effect behavior introduces complex and difficult to track logical errors in programs.
+As a result, functions like this return `undefined` and modify values in-place.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/functions/delete.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/functions/delete.mdx
new file mode 100644
index 0000000000..3b60803997
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/functions/delete.mdx
@@ -0,0 +1,25 @@
+---
+page_title: 'Sentinel Language - Builtin Function: delete'
+sidebar_current: docs-funcs-delete
+description: The built-in function `delete` deletes an element from a map.
+layout: docs
+---
+
+# Builtin Function: delete
+
+The built-in function `delete` deletes an element from a map.
+
+If the element to delete does not exist, the map is left unchanged.
+Calling `delete` on a value that is not a map or
+[undefined](/sentinel/language/undefined)
+is an immediate error. The result of `delete` is always `undefined`.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/functions/error.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/functions/error.mdx
new file mode 100644
index 0000000000..96d253b662
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/functions/error.mdx
@@ -0,0 +1,15 @@
+---
+page_title: 'Sentinel Language - Builtin Function: error'
+sidebar_current: docs-funcs-error
+description: >-
+ The built-in function `error` is used to exit immediately with an error
+ message.
+layout: docs
+---
+
+# Builtin Function: error
+
+The built-in function `error` is used to exit immediately with an error message.
+
+Please see the [page on errors](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/functions/index.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/functions/index.mdx
new file mode 100644
index 0000000000..4e12143ad0
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/functions/index.mdx
@@ -0,0 +1,13 @@
+---
+page_title: Sentinel Language - Builtin Functions
+sidebar_current: docs-funcs
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Language: Builtin Functions
+
+Builtin functions are
+[functions](/sentinel/language/functions)
+that are available in all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in function.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/functions/keys.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/functions/keys.mdx
new file mode 100644
index 0000000000..a96cafe897
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/functions/keys.mdx
@@ -0,0 +1,22 @@
+---
+page_title: 'Sentinel Language - Builtin Function: keys'
+sidebar_current: docs-funcs-keys
+description: The built-in function `keys` returns the keys of a map.
+layout: docs
+---
+
+# Builtin Function: keys
+
+The built-in function `keys` returns the keys of a map.
+
+`keys` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned keys are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+```
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/functions/length.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/functions/length.mdx
new file mode 100644
index 0000000000..8b874b14c4
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/functions/length.mdx
@@ -0,0 +1,23 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Length'
+sidebar_current: docs-funcs-length
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Builtin Function: length
+
+The built-in function `length` returns the length of a collection or string.
+
+The length of a string is the number of bytes in the string. It is not
+necessarilly the number of characters. The length of a collection is the
+number of elements in that collection. The length of
+[undefined](/sentinel/language/undefined) is `undefined`.
+
+Examples:
+
+```sentinel
+length("foo") // 3
+length([1, 2, 3, 4]) // 4
+length({ "key": "value" }) // 1
+```
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/functions/print.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/functions/print.mdx
new file mode 100644
index 0000000000..bcf441d09d
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/functions/print.mdx
@@ -0,0 +1,13 @@
+---
+page_title: 'Sentinel Language - Builtin Function: print'
+sidebar_current: docs-funcs-print
+description: The built-in function `print` is used for logging and debugging.
+layout: docs
+---
+
+# Builtin Function: print
+
+The built-in function `print` is used for logging and debugging.
+
+Please see the [page on logging](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/functions/range.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/functions/range.mdx
new file mode 100644
index 0000000000..0f86bfc402
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/functions/range.mdx
@@ -0,0 +1,49 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Range'
+sidebar_current: docs-funcs-range
+description: The built-in function `range` returns a list of numbers in a range.
+layout: docs
+---
+
+# Builtin Function: range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it
+defaults to `1`.
+
+Negative steps are permitted and count down from `start` towards `end`, instead
+of up.
+
+The range ends when the next value given by the sum of the last value and `step`
+would exceed `end`, regardless of if it has been reached.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4] - starts at 1 instead of 0
+range(1, 5, 2) // [1,3] - 3+2 does not satisfy r[-1] < end
+range(0, -3, -1) // [0,-1,-2] - example of negative range
+```
+
+Impossible ranges, or ranges where `start` will never reach `end` given `step`,
+yield an empty list.
+
+```
+range(1, 1) // [] - already starting at 1
+range(0, 1, -1) // [] - negative step will never reach 1
+```
+
+Supplying an undefined value to any argument of `range` yields an undefined
+value back.
+
+A range with a zero step is a runtime error.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/functions/values.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/functions/values.mdx
new file mode 100644
index 0000000000..8f98d3ab56
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/functions/values.mdx
@@ -0,0 +1,22 @@
+---
+page_title: 'Sentinel Language - Builtin Function: values'
+sidebar_current: docs-funcs-values
+description: The built-in function `values` returns the values of a map.
+layout: docs
+---
+
+# Builtin Function: values
+
+The built-in function `values` returns the values of a map.
+
+`values` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned values are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+values(data) // [3, 2]
+```
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/imports/base64.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/imports/base64.mdx
new file mode 100644
index 0000000000..4e58429268
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/imports/base64.mdx
@@ -0,0 +1,46 @@
+---
+page_title: 'Import: base64'
+sidebar_current: docs-imports-base64
+description: The base64 import enables a Sentinel policy to encode and decode Base64 values.
+layout: docs
+---
+
+# Import: base64
+
+The base64 import enables a Sentinel policy to encode and decode Base64 values.
+
+### base64.encode(str)
+
+Encodes the string `str` into a Base64 encoded string, using the standard
+encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-4).
+
+```sentinel
+enc = base64.encode("foo") // "Zm9v"
+```
+
+### base64.decode(str)
+
+Decodes the Base64 encoded string `str`, returning the string result,
+using the standard encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-4).
+
+```sentinel
+dec = base64.decode("Zm9v") // "foo"
+```
+
+### base64.urlencode(str)
+
+Encodes the string `str` into a Base64 encoded string, using the alternate
+encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-5).
+
+```sentinel
+enc = base64.urlencode("foo") // "Zm9v"
+```
+
+### base64.urldecode(str)
+
+Decodes the Base64 encoded string `str`, returning the string result, using the
+alternate encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-5).
+
+```sentinel
+dec = base64.urldecode("Zm9v") // "foo"
+```
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/imports/decimal.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/imports/decimal.mdx
new file mode 100644
index 0000000000..a6633a38e0
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/imports/decimal.mdx
@@ -0,0 +1,318 @@
+---
+page_title: 'Import: decimal'
+sidebar_current: docs-imports-decimal
+description: |-
+ The decimal import provides functions for operating on numbers represented
+ as decimals.
+layout: docs
+---
+
+# Import: decimal
+
+The decimal import provides functions for operating on numbers represented
+as decimals.
+
+Binary floating-point numbers provided by the `float` type are unable to
+fully represent numbers like `1.1` and `2.2`. The decimal import can
+represent these numbers exactly, and so should be used when exactness is
+required.
+
+Decimals are created through the `new` function, which takes any type that
+can represent a number. Arguments to the decimal operators can also take
+a value of any of these types. The full list is:
+
+- float
+- int
+- string
+- existing decimal value
+
+### decimal.infinity(sign)
+
+Creates an infinite-value decimal. Infinite-value decimals are always larger
+or smaller, depending on sign, than any non-infinite decimals.
+
+```sentinel
+decimal.infinity(1) // +Infinity
+decimal.infinity(-1) // -Infinity
+```
+
+Also available as `decimal.inf`.
+
+### decimal.nan
+
+A decimal representing a "not-a-number" value. NaNs are not equal to any
+other number, even themselves.
+
+```sentinel
+decimal.new(-1).sqrt() // NaN
+decimal.new(0).mul(decimal.inf(1)) // NaN
+decimal.nan.is(decimal.nan) // false
+```
+
+### decimal.is_infinite(d)
+
+Evaluates to true if the decimal is infinite.
+
+```sentinel
+decimal.is_infinite(100) // false
+decimal.is_infinite("Infinity") // true
+```
+
+Also available as `decimal.is_inf`, `decimal.isinf` and `decimal.isinfinite`.
+
+### decimal.is_nan(d)
+
+Evaluates to true if the decimal is not-a-number.
+
+```sentinel
+decimal.is_nan("NaN") // true
+```
+
+Also available as `decimal.isnan`.
+
+### decimal.new(v)
+
+Constructs a decimal from another value.
+
+```sentinel
+decimal.new("1.1") // Constructs a decimal from a string.
+decimal.new(2.2) // Constructs a decimal from a float.
+decimal.new(3) // Constructs a decimal from an integer.
+decimal.new("1.1234E+400") // Constructs a decimal from a string using E-notation.
+```
+
+## Type: number
+
+Numbers are represented internally by a sign, coefficient, and exponent.
+The value is calculated with the formula `sign * coefficient * 10^exponent`.
+Exponent is limited to 32 bits, and the coefficient is limited by the total
+precision.
+
+The maximum precision a number can represent is 100 digits. This includes
+both before and after the decimal place.
+
+### number.string
+
+A string representation of the decimal.
+
+```sentinel
+decimal.new(1.5).string // 1.5
+```
+
+### number.sign
+
+A number between `-1` and `+1` representing the sign of the decimal.
+
+```sentinel
+decimal.new(1.5).sign // 1
+```
+
+### number.coefficient
+
+The coefficient component of the decimal.
+
+```sentinel
+decimal.new(1.5).coefficient // 15
+```
+
+### number.exponent
+
+The exponent component of the decimal.
+
+```sentinel
+decimal.new(1.5).exponent // -1
+```
+
+### number.float
+
+A floating-point representation of the decimal.
+
+```sentinel
+decimal.new(1.5).float // 1.500000
+```
+
+### number.int
+
+An integer representation of the decimal. This always rounds down to the nearest integer.
+
+```sentinel
+decimal.new(1.5).int // 1
+```
+
+### number.is(v)
+
+Test for equality with another value.
+
+```sentinel
+decimal.new(1.5).is(1) // false
+```
+
+### number.is_not(v)
+
+Test for inequality with another value.
+
+```sentinel
+decimal.new(1.5).is_not(1) // true
+```
+
+### number.less_than(v)
+
+Test that the decimal is less than another value.
+Can also be called as `number.lt(v)`
+
+```sentinel
+decimal.new(1.5).less_than(1) // false
+```
+
+### number.less_than_or_equals(v)
+
+Test that the decimal is less than or equals to another value.
+Can also be called as `number.lte(v)`
+
+```sentinel
+decimal.new(1.5).less_than_or_equals(1) // false
+```
+
+### number.greater_than(v)
+
+Test that the decimal is greater than another value.
+Can also be called as `number.gt(v)`
+
+```sentinel
+decimal.new(1.5).greater_than(1) // true
+```
+
+### number.greater_than_or_equals(v)
+
+Test that the decimal is greater than or equals to another value.
+Can also be called as `number.gte(v)`
+
+```sentinel
+decimal.new(1.5).greater_than_or_equals(1) // true
+```
+
+### number.add(v)
+
+Add another number to the decimal.
+
+```sentinel
+decimal.new(1.5).add(1) // 2.5
+```
+
+### number.subtract(v)
+
+Subtract another number from the decimal.
+Can also be called as `number.sub(v)`
+
+```sentinel
+decimal.new(1.5).subtract(1) // 0.5
+```
+
+### number.multiply(v)
+
+Multiply the decimal by a number.
+Can also be called as `number.mul(v)`
+
+```sentinel
+decimal.new(1.5).multiply(2) // 3.0
+```
+
+### number.divide(v)
+
+Divide the decimal by a number.
+Can also be called as `number.div(v)`
+
+```sentinel
+decimal.new(3.0).divide(1.5) // 2
+```
+
+### number.modulo(v)
+
+Find the remainder after dividing the decimal by a number.
+Can also be called as `mod`, `remainder`, or `rem`.
+
+```sentinel
+decimal.new(1.5).modulo(1) // 0.5
+```
+
+### number.power(v)
+
+Raise the decimal to a power.
+Can also be called as `number.pow(v)`
+
+```sentinel
+decimal.new(1.5).power(2) // 2.25
+```
+
+### number.exp()
+
+The natural exponent of the decimal. This calculates `e^number`.
+
+```sentinel
+decimal.new(1.5).exp() // 4.4816...
+```
+
+### number.loge()
+
+The natural logarithm of the decimal.
+Can also be called as `number.ln()`
+
+```sentinel
+decimal.new(1.5).loge() // 0.4054...
+```
+
+### number.log10()
+
+The base-10 logarithm of the decimal.
+Can also be called as `number.log()`
+
+```sentinel
+decimal.new(1.5).log10() // 0.1760...
+```
+
+### number.square_root()
+
+The square root of the decimal.
+Can also be called as `number.sqrt()`
+
+```sentinel
+decimal.new(1.5).square_root() // 1.2247...
+```
+
+### number.ceiling()
+
+Round the decimal to the smallest integer greater or equal to itself.
+Can also be called as `number.ceil()`
+
+```sentinel
+decimal.new(1.5).ceiling() // 2
+```
+
+### number.floor()
+
+Round the decimal to the largest integer less than or equal to itself.
+
+```sentinel
+decimal.new(1.5).floor() // 1
+```
+
+### number.absolute()
+
+The absolute value of the decimal.
+Can also be called as `number.abs()`
+
+```sentinel
+decimal.new(1.5).absolute() // 1.5
+decimal.new(-1.5).absolute() // 1.5
+```
+
+### number.negate()
+
+The negated value of the decimal. A positive decimal will become negative,
+and a negative decimal will become positive.
+Can also be called as `number.neg()`
+
+```sentinel
+decimal.new(1.5).negate() // -1.5
+decimal.new(-1.5).negate() // 1.5
+```
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/imports/http.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/imports/http.mdx
new file mode 100644
index 0000000000..f94ae98c27
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/imports/http.mdx
@@ -0,0 +1,370 @@
+---
+page_title: 'Import: http'
+sidebar_current: docs-imports-http
+description: The http import enables the use of HTTP-accessible data from outside the runtime in Sentinel policy rules.
+layout: docs
+---
+
+# Import: http
+
+The http import enables the use of HTTP-accessible data from outside
+the runtime in Sentinel policy rules.
+
+The simplest example of the import in use would be:
+
+```sentinel
+import "http"
+
+resp = http.get("https://example.hashicorp.com")
+main = rule { resp.body contains "something" }
+```
+
+By default, any request response that is not a successful "200 OK" HTTP
+response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information and
+how to customize this behavior.
+
+For more advanced requests which require setting request headers, use a
+[`request` type](#type-request):
+
+```sentinel
+import "http"
+import "json"
+
+param token
+
+req = http.request("https://example.hashicorp.com").with_header("Authorization", "token "+token)
+resp = json.unmarshal(http.get(req).body)
+main = rule { resp["some_key"] is true }
+```
+
+The `with_header` function above returns the `request` object with the
+`Authorization` HTTP header set to value of the variable `some_value`. Many
+of the methods within the http import API are designed around this concept
+of method chaining, allowing for complex building of HTTP requests
+encapsulated in functions. The following is an idiomatic example further
+demonstrating this pattern:
+
+```sentinel
+import "http"
+import "json"
+
+param github_user
+param github_token
+
+client = func(req) {
+ return http.with_timeout(5).with_retries(3)
+}
+
+github_request = func(path) {
+ full_url = "https://api.github.com" + path
+ return http.request(full_url).
+ with_basic_auth(github_user, github_token).
+ with_header("Accept", "application/vnd.github.v3+json").
+ with_header("Custom", "foo"),
+}
+
+default_response = client.get(github_request("/example"))
+
+# Override the Custom header for this single request
+custom_header_response = json.unmarshal(
+ client.get(
+ github_request("/example").with_header("Custom", "bar"),
+ ),
+)
+
+# Override the timeout value for a known slower request
+slow_response = json.unmarshal(
+ client.with_timeout(15).get(github_request("/slow-endpoint")),
+)
+
+some_key_is_true = rule { json.unmarshal(default_response.body)["some_key"] is true }
+body_contains_text = rule { custom_header_response.body contains "something" }
+response_is_ok = rule { slow_response.status_code is 200 }
+
+main = rule { some_key_is_true and body_contains_text and response_is_ok }
+```
+
+### http.client
+
+Retrieve the base HTTP client with default settings. The returned client may be
+configured by calling configuration functions on it; all of these configuration
+functions on are also aliased as top-level functions in this namespace. See
+[`client`](#type-client) below.
+
+### http.request(url)
+
+Create a new [`request`](#type-request) to the specified `url` (string). A
+`request` type enables customization of headers and other concerns before then
+providing the constructed `request` to [`get()`](#client-get-url_or_request),
+[`post()`](#client-post-url_or_request) or [`send()`](#client-send-request).
+
+```sentinel
+req = http.request("example.hashicorp.com/foo").with_header("Foo", "bar")
+resp = http.get(req)
+```
+
+### http.methodPost
+
+Returns a string containing the accepted verb for POST requests, `"POST"`.
+
+### http.methodGet
+
+Returns a string containing the accepted verb for GET requests, `"GET"`.
+
+## Type: client
+
+All of the following configuration functions on the default client are also
+aliased as top-level functions in the import. This allows a short-hand (and
+idiomatic) way of configuring a new client without having to directly access
+`http.client` each time:
+
+```sentinel
+# The following two lines are semantically the same:
+resp = http.client.with_timeout(5).get(url)
+resp = http.with_timeout(5).get(url)
+```
+
+### url_or_request
+
+The [`get()`](#client-get-url_or_request) and [`post()`](#client-post-url_or_request)
+functions accept either a URL string or a request object, noted as
+`url_or_request` throughout the documentation.
+
+When `url_or_request` is a string, a request is made with the URL
+specified by the string. To customize HTTP headers and other settings, pass
+a request. By default, a request has a timeout value of 10 seconds and 1
+retry. These settings can be adjusted with [`with_timeout()`](#clientwith_timeoutinteger) and
+[`with_retries()`](#clientwith_retriesinteger), respectively.
+
+### Redirects
+
+If the response is one of the following redirect codes, the client follows the
+redirect, up to a maximum of 10 redirects:
+
+- 301 (Moved Permanently)
+- 302 (Found)
+- 303 (See Other)
+- 307 (Temporary Redirect)
+- 308 (Permanent Redirect)
+
+If the redirect limit is exceeded, the result is a policy error.
+
+By default, after any redirects are followed (up to the maximum), any request
+response that is not a successful "200 OK" response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information
+and how to customize this behavior.
+
+### client.get(url_or_request)
+
+Issue a GET request with a URL string or a `request` type. Returns a `response`
+type.
+
+```sentinel
+import "http"
+
+resp = http.get("https://example.hashicorp.com")
+main = rule { resp.body contains "something" }
+```
+
+### client.post(url_or_request)
+
+Issue a POST request with a URL string or a `request` type. Returns a `response`
+type.
+
+```sentinel
+import "http"
+
+req = http.request("https://example.hashicorp.com")
+ .with_body(json.marshal({"foo": "bar"}))
+resp = http.post(req)
+main = rule { resp.body contains "something" }
+```
+
+### client.send(request)
+
+Make a request using the supplied `request` type. Returns a `response`.
+
+```sentinel
+import "http"
+
+req = http.request("https://example.hashicorp.com")
+resp = http.send(req)
+main = rule { resp.body contains "something" }
+```
+
+### client.accept_status_codes(list)
+
+Configures a client to not automatically cause a policy failure if
+the resulting response's status code is in `list`. Any status code received
+that is not present in this list will trigger a runtime error.
+
+By default, any request response that is not a successful "200 OK" response
+causes a policy error. This is for convenience, as the most common scenario
+by far is to receive a response and immediately signal a policy error if the
+status code is not 200. Use this scoping to specify a list of non-redirect
+HTTP codes that you wish to accept without error and evaluate the response
+yourself.
+
+Redirects are not considered final status codes in this context. That is,
+redirects are always followed up to the maximum allowed value (see [`Redirects`](#redirects))
+and the final response code in the redirect chain is validated against
+`list`.
+
+```sentinel
+resp = http.accept_status_codes([200, 404]).get(url)
+main = rule { resp.status_code is 404 }
+```
+
+### client.accepted_status_codes
+
+Returns a list of the client's accepted status codes.
+
+```sentinel
+client = http.accept_status_codes([200, 404])
+main = rule { client.accepted_status_codes contains 404 }
+```
+
+### client.with_retries(integer)
+
+Sets the maximum number of retries, specified by `integer`, that should be
+attempted on an unsuccessful request. An unsuccessful request is considered
+to be any 5xx response except `501 (Not Implemented)` or a request timeout.
+By default, one retry is attempted.
+
+The maximum number of retries is 10, for a total of 11 request attempts.
+When a request exceeds the maximum number of retries without a response, the
+result is a policy error. Specifying a number larger than this will result
+in a runtime error.
+
+Between each retry, an exponentially increasing delay is observed, allowing
+time for the server to recover. This delay starts at 1 second for the first
+retry and increases each attempt thereafter. The maximum delay for a single
+attempt is 30 seconds.
+
+Retries can be disabled by specifying 0.
+
+### client.retries
+
+Returns the client's configured number of retries as an integer.
+
+### client.with_timeout(integer)
+
+Sets the request timeout to the number of seconds indicated by `integer`.
+
+Each individual request performed by the HTTP client will be limited by the
+given request timeout. This timeout includes connection time, any redirects,
+and reading the response body.
+
+A maximum of 30 seconds is allowed. Specifying a number larger than this
+will result in a runtime error.
+
+By default, requests will time out after 10 seconds.
+
+### client.timeout
+
+Returns the client's configured timeout in whole seconds as an integer.
+
+### client.without_certificate_verification()
+
+Skips verification of TLS certificates during secure HTTP operations,
+allowing for requests to `https://` endpoints which are secured with a TLS
+certificate signed by an untrusted certificate authority. Takes no arguments.
+
+By default, the HTTP client will trigger a runtime error if a TLS certificate
+presented by a server is from an untrusted certificate authority.
+
+Do not change this setting unless you are aware of the risks involved.
+Without verification, TLS accepts any certificate presented by the server
+and any host name in that certificate. In this mode, TLS is susceptible to
+man-in-the-middle attacks. This option should only be used for testing or in
+trusted networks with self-signed certificates.
+
+```sentinel
+http.without_certificate_verification().get(url)
+```
+
+### client.certificate_verification
+
+Returns true if the client requires TLS certificates to be verifiable.
+
+## Type: request
+
+### request.url
+
+Returns the request URL as a string.
+
+### request.headers
+
+Returns the configured request headers as a string-keyed map of strings.
+
+### request.body
+
+Returns the configured body as a string.
+
+### request.with_header(key, value)
+
+Returns a `request` with the header `key` (string) set to `value` (string).
+
+Values are overridden on subsequent calls to the same `key`. To specify
+multiple values, use the existing value when setting the new one.
+
+```sentinel
+original = http.request("http://example.hashicorp.com").with_header("Foo", "bar")
+overridden = original.with_header("Foo", "baz")
+multi_value = original.with_header("Foo", original.headers["Foo"] + ",baz")
+
+main = rule {
+ original.headers["Foo"] is "bar" and
+ overridden.headers["Foo"] is "baz" and
+ multi_value.headers["Foo"] is "bar,baz"
+}
+```
+
+### request.with_basic_auth(username, password)
+
+Returns a `request` with the `Authorization` header set to use HTTP Basic
+Authentication with the provided username and password.
+
+Note that with HTTP Basic Authentication the provided username and password
+are encoded, but not encrypted.
+
+### request.with_body(body)
+
+Returns a `request` with the `body` set. This value will then be sent as a body
+as part of the request. Commonly used along with the [`post()`](#client-post-url_or_request) function.
+
+```sentinel
+req = http.request("http://example.hashicorp.com")
+ .with_header("Content-Type", "application/json")
+ .with_body(json.marshal({ "foo": "bar" }))
+
+resp = http.post(req)
+```
+
+## Type: response
+
+### response.status_code
+
+The response status code as an integer, e.g. `200`.
+
+### response.headers
+
+The response headers as a map.
+
+### response.body
+
+The response body as a string. Callers should unmarshal the content to a useful
+representation as necessary based on the content type. For example, if the
+response is in JSON:
+
+```sentinel
+import "http"
+import "json"
+
+# This example endpoint returns {"some_key": true}
+req = http.request("https://example.hashicorp.com/some-json")
+
+resp = json.unmarshal(http.get(req).body)
+main = rule { keys(resp) contains "some_key" and resp["some_key"] is true }
+```
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/imports/index.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/imports/index.mdx
new file mode 100644
index 0000000000..e45bf81b81
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/imports/index.mdx
@@ -0,0 +1,18 @@
+---
+page_title: Sentinel Language - Standard Imports
+sidebar_current: docs-imports
+description: >-
+ Standard Imports are the built-in imports that are available to all Sentinel
+ policies.
+layout: docs
+---
+
+# Standard Imports
+
+Standard Imports are the built-in [imports](/sentinel/language/imports)
+that are available to all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in import.
+
+Sentinel-embedded applications can choose to allow or deny certain
+standard imports. Please reference the documentation for the Sentinel-enabled
+application you're using to determine if all standard imports are available.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/imports/json.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/imports/json.mdx
new file mode 100644
index 0000000000..3355458f14
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/imports/json.mdx
@@ -0,0 +1,31 @@
+---
+page_title: 'Import: json'
+sidebar_current: docs-imports-json
+description: The json import enables a Sentinel policy to parse and access a JSON document.
+layout: docs
+---
+
+# Import: json
+
+The json import enables a Sentinel policy to parse and access a JSON
+document.
+
+### json.unmarshal(obj)
+
+Unmarshals the JSON object `obj` into a native Sentinel structure.
+
+All native JSON types can be represented perfectly as Sentinel native types.
+
+```sentinel
+// Typically the input for this would come from an external source.
+config = json.unmarshal("{ \"foo\": 42 }")
+config.foo // 42
+config.bar // undefined (as usual for accessing a non-existent map key)
+```
+
+### json.marshal(obj)
+
+Marshals the Sentinel object `obj` into a JSON object encoded as a string.
+
+Functions and `undefined` cannot be natively encoded as JSON. If values of
+either type are found in the structure, this will return undefined.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/imports/runtime.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/imports/runtime.mdx
new file mode 100644
index 0000000000..6493bbbb22
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/imports/runtime.mdx
@@ -0,0 +1,16 @@
+---
+page_title: 'Import: runtime'
+sidebar_current: docs-imports-runtime
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: runtime
+
+The runtime import contains various information about the Sentinel runtime. It
+can be used to get information about the runtime as it's embedded in the CLI or
+a specific integration, such as validating a specific runtime version.
+
+### runtime.version
+
+Returns the version of Sentinel within this implementation.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/imports/sockaddr.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/imports/sockaddr.mdx
new file mode 100644
index 0000000000..a6842ba5f0
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/imports/sockaddr.mdx
@@ -0,0 +1,41 @@
+---
+page_title: 'Import: sockaddr'
+sidebar_current: docs-imports-sockaddr
+description: The sockaddr import makes working with IP addresses easy.
+layout: docs
+---
+
+# Import: sockaddr
+
+The sockaddr import makes working with IP addresses easy.
+
+### sockaddr.is_contained(outer, inner)
+
+The is_contained function returns true if `inner` is an address that
+is contained within `outer`.
+
+```sentinel
+sockaddr.is_contained("192.168.0.0/24", "192.168.0.32") // true
+sockaddr.is_contained("192.168.0.0/24", "192.174.1.1") // false
+```
+
+### sockaddr.is_equal(addr1, addr2)
+
+The is_equal function returns true if addr1 is equal to addr2.
+
+```sentinel
+sockaddr.is_equal("192.168.12.24", "192.168.12.24") // true
+```
+
+### sockaddr.is_ipv4(addr)
+
+The is_ipv4 function returns true if addr is an IPv4 address.
+
+```sentinel
+sockaddr.is_ipv4("192.168.12.24") // true
+sockaddr.is_ipv4("2001:0db8:85a3:0000:0000:8a2e:0370:7334") // false
+```
+
+### sockaddr.is_ipv6(addr)
+
+The is_ipv6 function returns true if addr is an IPv6 address.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/imports/strings.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/imports/strings.mdx
new file mode 100644
index 0000000000..823c5d8f07
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/imports/strings.mdx
@@ -0,0 +1,91 @@
+---
+page_title: 'Import: strings'
+sidebar_current: docs-imports-strings
+description: The strings import exposes common string operations.
+layout: docs
+---
+
+# Import: strings
+
+The strings import exposes common string operations.
+
+### strings.has_prefix(s, prefix)
+
+Returns true if `s` starts with `prefix`.
+
+```sentinel
+strings.has_prefix("billing-id", "billing-") // true
+strings.has_prefix("bill-id", "billing-") // false
+```
+
+### strings.has_suffix(s, suffix)
+
+Returns true if `s` ends with `suffix`.
+
+```sentinel
+strings.has_suffix("billing-id", "id") // true
+strings.has_suffix("billing-name", "id") // false
+```
+
+### strings.join(a, sep)
+
+Joins a list with the string supplied by `sep`.
+
+Multi-dimensional lists are supported, and non-string primitive
+types will be converted to their string equivalents. Maps and
+other complex non-list types are not supported.
+
+```sentinel
+strings.join(["foo", "bar", "baz"], ".") // "foo.bar.baz"
+strings.join([["foo", "bar"], "baz"], ".") // "foo.bar.baz"
+strings.join(["a", 1, 1.01, true], ",") // "a,1,1.01,true"
+```
+
+### strings.trim_prefix(s, prefix)
+
+Trim the prefix from the string `s`. If the string doesn't have the
+prefix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_prefix("billing-id", "billing-") // "id"
+strings.trim_prefix("bill-id", "billing-") // "bill-id"
+```
+
+### strings.trim_suffix(s, suffix)
+
+Trim the suffix from the string `s`. If the string doesn't have the
+suffix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_suffix("billing-id", "-id") // "billing"
+strings.trim_suffix("bill-id", "-foo") // "bill-id"
+```
+
+### strings.to_lower(s)
+
+Lowercase the string s.
+
+```sentinel
+strings.to_lower("FoO") // "foo"
+```
+
+### strings.to_upper(s)
+
+Uppercase the string s.
+
+```sentinel
+strings.to_upper("FoO") // "FOO"
+```
+
+### strings.split(s, sep)
+
+Split the string 's' via a substring 'sep'. If 'sep' is not contained in
+'s', the return value will be a single-element list containing only 's'.
+As a special-case, if the input is already a list, it will be returned
+as-is. This allows calling the split function when not knowing if the input
+is already a list or not.
+
+```sentinel
+strings.split("foo,bar,baz", ",") // ["foo", "bar", "baz"]
+strings.split(["foo", "bar", "baz"], ",") // ["foo", "bar", "baz"]
+```
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/imports/time.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/imports/time.mdx
new file mode 100644
index 0000000000..3a675e2bbd
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/imports/time.mdx
@@ -0,0 +1,257 @@
+---
+page_title: 'Import: time'
+sidebar_current: docs-imports-time
+description: The time import provides access to the execution time and time functions.
+layout: docs
+---
+
+# Import: time
+
+The time import provides access to the execution time and time functions.
+
+During the execution of a policy the time is fixed to the moment of
+execution. This gives the illusion that a policy instantly executes at a
+single moment of time.
+
+The import uses Coordinated Universal Time (UTC).
+
+As an example of this, if a policy accessed `time.now.second` multiple times,
+for each access the same value would be returned even if they are several seconds apart.
+This is the execution `timespace`, which is mapped to `time.now`.
+
+It is also possible to run functions on a custom time by using the
+`time.load()` function to create a new timespace from the specified value.
+See the documentation for available actions on timespaces.
+
+Times specified as the reference time or via the `time.load()` function can
+be specified in a `timeish` fashion. This is either one of:
+
+- The number of seconds since the Unix epoch (Thursday, 1 January 1970,
+ 00:00:00 UTC),
+- A timestamp matching the format outlined in RFC3339, either in the
+ format `2006-01-02T15:04:05+07:00`, which includes a timezone offset, or
+ `2006-01-02T15:04:05Z`, which indicates UTC.
+
+### time.now
+
+Create a `timespace` locked either to execution time or, if set, the
+reference time. In a given execution, this will always produce the same
+value.
+
+```sentinel
+time.now // {"day": 17, "hour": 23, "minute": 19, "month": 11 ... }
+```
+
+### time.load(timeish)
+
+Create a timespace using the given value. Please see the import
+documentation for valid values for "timeish".
+
+```sentinel
+time.load("2019-11-16T01:20:30Z") // {"day": 16, "hour": 1, "minute": 20, "month": 11 ... }
+```
+
+### time.nanosecond
+
+A nanosecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.nanosecond * 1) // 2019-11-16T01:20:30.000000001Z
+```
+
+### time.microsecond
+
+A microsecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.microsecond * 1) // 2019-11-16T01:20:30.000001Z
+```
+
+### time.millisecond
+
+A millisecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.millisecond * 1) // 2019-11-16T01:20:30.001Z
+```
+
+### time.second
+
+A second duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.second * 1) // 2019-11-16T01:20:31Z
+```
+
+### time.minute
+
+A minute for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.minute * 1) // 2019-11-16T01:21:30Z
+```
+
+### time.hour
+
+An hour duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+## Type: timespace
+
+### timespace.hour
+
+The hour from 0 to 23.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").hour // 1
+```
+
+### timespace.minute
+
+The minute from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").minute // 20
+```
+
+### timespace.second
+
+The second from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").second // 30
+```
+
+### timespace.day
+
+The day of the month, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").day // 16
+```
+
+### timespace.month
+
+The month of the year as an integer, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month // 11
+```
+
+### timespace.month_name
+
+The name of the month of the year, in English. Example: `January`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month_name // November
+```
+
+### timespace.weekday
+
+The weekday as an integer, where 0 is Sunday and 6 is Saturday.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday // 6
+```
+
+### timespace.weekday_name
+
+The name of the day of the week, in English. Example: `Sunday`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday_name // Saturday
+```
+
+### timespace.year
+
+The year as an integer.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").year // 2019
+```
+
+### timespace.unix
+
+The number of seconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix // 1573867230
+```
+
+### timespace.unix_nano
+
+The number of nanoseconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix_nano // 1573867230000000000
+```
+
+### timespace.zone
+
+The timezone offset, in seconds. This can be a negative number to indicate
+time west of UTC.
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone // -28800
+```
+
+### timespace.zone_string
+
+The timezone offset, in the format `+HH:MM` (example: `-08:00` for PST).
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone_string // -08:00
+```
+
+### timespace.before(timeish_or_timespace)
+
+Check if the time in the timespace is before the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").before("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.after(timeish_or_timespace)
+
+Check if the time in the timespace is after the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").after("2019-11-15T01:20:30Z") // true
+```
+
+### timespace.equal(timeish_or_timespace)
+
+Check if two times are equivalent
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").equal("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.add(duration)
+
+Add a duration in nanoseconds to the time in the timespace and return a new timespace. The
+duration can be negative. The duration should be specified as an integer
+multiplied with the correct unit.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+### timespace.sub(timeish_or_timespace)
+
+Subtract a time from the time in the timespace and return a duration in nanoseconds.
+
+```sentinel
+// Gives a duration of 3 hours.
+duration = time.load(
+ "2019-11-16T01:20:30Z",
+).sub("2019-11-16T04:20:30Z")
+
+// Returns 2019-11-16T01:20:30Z
+time.load(
+ "2019-11-16T04:20:30Z",
+).add(duration)
+```
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/imports/types.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/imports/types.mdx
new file mode 100644
index 0000000000..fcfdbd2ae5
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/imports/types.mdx
@@ -0,0 +1,35 @@
+---
+page_title: 'Import: types'
+sidebar_current: docs-imports-types
+description: The types import enables a Sentinel policy to parse an object's type.
+layout: docs
+---
+
+# Import: types
+
+The types import enables a Sentinel policy to parse an object's type.
+
+### types.type_of(obj)
+
+Returns the object's type as a string
+
+```sentinel playground
+import "types"
+
+// Typically the inputs would come from an external source.
+is_boolean = rule { types.type_of(true) is "bool" }
+is_string = rule { types.type_of("Hello!") is "string" }
+is_integer = rule { types.type_of(42) is "int" }
+is_float = rule { types.type_of(42.123) is "float" }
+is_null = rule { types.type_of(null) is "null" }
+is_undefined = rule { types.type_of(undefined) is "undefined" }
+
+main = rule {
+ is_boolean and
+ is_string and
+ is_integer and
+ is_float and
+ is_null and
+ is_undefined
+}
+```
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/imports/units.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/imports/units.mdx
new file mode 100644
index 0000000000..e2ad3442f9
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/imports/units.mdx
@@ -0,0 +1,39 @@
+---
+page_title: 'Import: units'
+sidebar_current: docs-imports-units
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: units
+
+The units import provides access to quick calculations for various byte
+units.
+
+Note that this import uses base-2 terminology:
+
+- One kilobyte is 1024 bytes
+- One megabyte is 1024^2 = 1048576 bytes
+- One gigabyte is 1024^3 = 1073741824 bytes
+- One terabyte is 1024^4 = 1099511627776 bytes
+- One petabyte is 1024^5 = 1125899906842624 bytes
+
+### units.kilobyte
+
+The base-2 representation of a kilobyte (1024 bytes).
+
+### units.megabyte
+
+The base-2 representation of a megabyte (1048576 bytes).
+
+### units.gigabyte
+
+The base-2 representation of a gigabyte (1073741824 bytes).
+
+### units.terabyte
+
+The base-2 representation of a terabyte (1099511627776 bytes).
+
+### units.petabyte
+
+The base-2 representation of a petabyte (1125899906842624 bytes).
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/imports/version.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/imports/version.mdx
new file mode 100644
index 0000000000..59366c1087
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/imports/version.mdx
@@ -0,0 +1,151 @@
+---
+page_title: 'Import: version'
+sidebar_current: docs-imports-version
+description: |-
+ The version import provides functions for parsing versions and version constraints,
+ and verifying versions against a set of constraints.
+layout: docs
+---
+
+# Import: version
+
+The version import provides functions for parsing versions and version constraints,
+and verifying versions against a set of constraints.
+
+Versions are created through the `new` function, which accepts a string type in dotted-tri format (i.e. 1.0.0-alpha.1+001) that can represent a version.
+
+### version.new(v)
+
+Constructs a version from string value.
+
+```sentinel
+version.new("1.0.0-alpha.1+001")
+```
+
+### version.major
+
+An integer representation of the Major number for a given version .
+
+```sentinel
+version.new("1.0.0-alpha.1+001").major // 1
+```
+
+### version.minor
+
+An integer representation of the Minor number for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").minor // 0
+```
+
+### version.patch
+
+An integer representation of the Patch number for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").patch // 0
+```
+
+### version.prerelease
+
+A string representation of the Prerelease for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").prerelease // "alpha.1"
+```
+
+### version.metadata
+
+A string representation of the Metadata for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").metadata // "001"
+```
+
+### version.version
+
+A string representation of the version including pre-release and metadata information.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").version // "1.0.0-alpha.1+001"
+```
+
+### version.greater_than(v)
+
+Tests that the version is greater than a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").greater_than("0.12.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").gt("1.0.1-alpha.1+001") // False
+```
+
+### version.greater_than_or_equals(v)
+
+Tests that the version is greater than or equal to a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").greater_than_or_equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").gte("1.0.1-alpha.1+001") // False
+```
+
+### version.less_than(v)
+
+Tests that the version is less than a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").less_than("1.0.1-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").lt("0.12.0-alpha.1+001") // False
+```
+
+### version.less_than_or_equals(v)
+
+Tests that the version is less than or equal to a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").less_than_or_equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").lte("0.12.0-alpha.1+001") // False
+```
+
+### version.less_than_or_equals(v)
+
+Tests that the version equals a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").eq("0.12.0-alpha.1+001") // False
+```
+
+### version.compare(v)
+
+Compares one version with a given version. Compare returns -1, 0, or 1 if the version is less, equal, or greater than the other version, respectively.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").compare("0.12.0-alpha.1+001") // -1
+version.new("1.0.0-alpha.1+001").compare("1.0.0-alpha.1+001") // 0
+version.new("0.12.0-alpha.1+001").cmp("1.0.0-alpha.1+001") // 1
+```
+
+### version.satisfies(v, x)
+
+Tests that a version adheres to one or more version constraints. Satisfies supports the following restriction operators:
+
+- =
+- !=
+- \>
+- <
+- \>=
+- <=
+- ~>
+
+Satisfies supports the following usage scenarios:
+
+- A constraint with a pre-release can only match a pre-release version that contains the same major, minor and patch identifiers.
+- A constraint without a pre-release can only match a version without a pre-release.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").satisfies("> 1.0.0-alpha.1+001") // false
+version.new("1.0.0-alpha.1+001").satisfies(">= 1.0.0-alpha.1+001") // true
+version.new("1.0.0-alpha.1+001").satisfies(">= 1.0.0-alpha.1+001, < 1.0.0-beta+001") // true
+version.new("1.0.0").sat("= 1.0.0") // true
+version.new("0.12.7").sat("~> 0.12.0") // true
+```
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/index.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/index.mdx
new file mode 100644
index 0000000000..4497b40b83
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/index.mdx
@@ -0,0 +1,31 @@
+---
+layout: 'docs'
+page_title: 'Documentation'
+sidebar_current: 'docs-home'
+description: |-
+ Sentinel is a language and framework for policy built to be embedded in existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+[introduction](/sentinel/intro) to understand what Sentinel is, how it
+compares to other software, and more.
+
+
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/language/arithmetic.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/language/arithmetic.mdx
new file mode 100644
index 0000000000..06b1256a1b
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/language/arithmetic.mdx
@@ -0,0 +1,67 @@
+---
+page_title: Sentinel Language - Arithmetic
+sidebar_current: docs-language-arith
+description: >-
+ Sentinel supports arithmetic operators for integers and floats. Sentinel
+ supports sum, difference, product, quotient, and remainder.
+layout: docs
+---
+
+# Language: Arithmetic
+
+Sentinel supports arithmetic operators for integers and floats. Sentinel
+supports sum, difference, product, quotient, and remainder.
+
+```plaintext
++ sum
+* difference
+* product
+/ quotient
+% remainder
+```
+
+These operators work in a typical infix style:
+
+```sentinel
+4 + 8 // 12
+8 * 2 // 16
+8 / 4 // 2
+8 / 5 // 1
+8 % 5 // 3
+```
+
+## Order of Operations
+
+Arithmetic follows a standard mathematical order of operations.
+Grouping with parentheses can be used to affect ordering.
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+(4 + 5) * 2 // 18
+```
+
+A full table of operator precendence can be found on the
+[boolean expressions page](/sentinel/language/boolexpr). This
+shows how arithmetic operators relate to other operators.
+
+## Integer Division
+
+Integer division with a remainder rounds down to the nearest integer.
+Example: `8 / 3 is 2`.
+
+If the divisor is zero, an error occurs.
+
+## Mixed Numeric Operations
+
+Mixed numeric operations between integer and floating-point values are
+permitted. The result is a floating-point operation with the integer converted
+to a floating-point value for purposes of calculation.
+
+```sentinel
+import "types"
+
+a = 1.1 + 1 // 2.1
+types.type_of(a) // "float"
+```
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/language/boolexpr.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/language/boolexpr.mdx
new file mode 100644
index 0000000000..22e13e05c4
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/language/boolexpr.mdx
@@ -0,0 +1,207 @@
+---
+page_title: Sentinel Language - Boolean Expressions
+sidebar_current: docs-language-boolexpr
+description: >-
+ Boolean expressions are expressions that evaluate to a boolean value from the
+ result of a combination of one or more comparisons and logical operators.
+layout: docs
+---
+
+# Language: Boolean Expressions
+
+Boolean expressions are expressions that evaluate to a boolean value
+from the result of a combination of one or more comparisons and logical
+operators. Boolean expressions form the basis of policy since policy can be broken
+down to a set of logical decisions that turn into true or false.
+
+A single boolean expression is the body of a [rule](/sentinel/language/rules).
+Additionally, boolean expressions are used with [conditionals](/sentinel/language/conditionals),
+and more.
+
+## Order of Operations
+
+The precedence of operators is shown below. Operators with higher
+precedence values (larger numbers) are evaluated first. Operators with
+the same precedence value are evaluated left-to-right.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Examples of this precedence are shown below:
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+```
+
+## Logical Operators
+
+Logical operators are used to change or combine logical expressions.
+There are three binary operators `and`, `or`, and `xor`. There is
+a single unary operator `not` (which can also be specified as `!`).
+
+The binary operators require two boolean operands and the unary
+operator requires a single boolean operand. In both case, the operands
+are values or expressions that are boolean types.
+
+Below is a basic explanation of the logical operators directly from
+the specification:
+
+```sentinel
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+Using parentheses to group boolean expressions, you can combine
+boolean expressions to become more complex or affect ordering:
+
+```sentinel
+(p and q) or r
+p and (q or r)
+```
+
+## Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+For any comparison, the two operands must be equivalent types. The one
+exception are integers and floats. When an integer is compared with
+a float, the integer is promoted to a floating point value.
+
+The available comparison operators are:
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+The behavior of `is` with `==` and `is not` with `!=` is identical.
+
+Example comparisons:
+
+```sentinel
+name is "Mitchell"
+idnumber > 42
+idnumber <= 12
+```
+
+Using the logical operators, these can be combined:
+
+```sentinel
+name is "Mitchell" and idnumber > 42
+```
+
+The language specification provides more detail on exactly
+[how comparison behaves](/sentinel/language/spec#comparison-operators).
+
+## Set Operators
+
+The set operators `contains` and `in` test for inclusion in a collection
+(a list or map).
+
+Set operators may 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.
+
+`contains` tests if the left-hand collection contains the right-hand value.
+`in` tests if the right-hand collection contains the left-hand value. For
+maps, "contains" looks at the keys, not the values.
+
+Examples:
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+```
+
+## Matches Operator
+
+The `matches` operator tests if a string matches a regular expression.
+The `matches` operator can be negated by prefixing the operator with
+`not`, such as `not matches`.
+
+The left-hand value is the string to test. The right-hand value is
+a string value representing a regular expression. The syntax of the regular
+expression is the same general syntax used by Python, Ruby, and other languages.
+The precise syntax accepted is the syntax accepted by RE2.
+
+Regular expressions are not anchored by default; any anchoring must be
+explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+## Any, All Expressions
+
+`any` and `all` expressions allow testing that any or all elements of a
+collection match some boolean expression. The result of an `any` and `all`
+expression is a boolean.
+
+`any` returns the boolean value `true` if any value in the collection expression
+results in the body expression evaluating to `true`. If the body expression
+evalutes to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression
+result in the body expression evaluating to `true`. If any value in the
+collection expression result in the body expression evaluating to `false`,
+the `all` expression returns `false`.
+
+For empty collections, `any` returns `false` and `all` returns `true`.
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" }
+any group.tasks as t { t.driver is "vmware" }
+```
+
+Since `any` and `all` expressions are themselves boolean expressions, they
+can be combined with other operators:
+
+```sentinel
+any ["a", "b"] as char { char is "a" } or
+other_value is "another"
+```
+
+## Emptiness Comparison
+
+The expressions `is empty` and `is not empty` provide a convenience
+method for determining the emptiness of a value. It supports the same types
+as the [built-in length](/sentinel/functions/length), collections and strings.
+
+```sentinel
+[] is empty // true
+[] is not empty // false
+["foo"] is empty // false
+["foo"] is not empty // true
+undefined is empty // undefined
+undefined is not empty // undefined
+```
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/language/collection-operations.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/language/collection-operations.mdx
new file mode 100644
index 0000000000..41da8f3584
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/language/collection-operations.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Sentinel Language - Collection Operations
+sidebar_current: docs-language-collection-operations
+description: |-
+ Operations for interacting with collections (list, map).
+layout: docs
+---
+
+# Language: Collection Operations
+
+Collection operations are expressions that are performed on a list or map to
+return a variation of the initial data.
+
+At the moment, `filter` is the only collection operation available.
+
+## Filter Expression
+
+`filter` is a [quantifier
+expression](/sentinel/language/spec#quantifier-expressions-any-all-filter-map) that
+returns a subset of the provided collection. Only elements whose filter body
+returns true will be returned. If any of the elements filter body returns
+undefined, the final result will be undefined.
+
+Filter uses the same syntax as the `any` and `all` [boolean
+expressions](/sentinel/language/boolexpr#any-all-expressions):
+
+```sentinel
+filter list as value { condition } // Single-iterator, list
+filter list as idx, value { condition } // Double-iterator, list
+
+filter map as key { condition } // Single-iterator, map
+filter map as key, value { condition } // Double-iterator, map
+```
+
+Examples:
+
+```sentinel
+l = [1, 1, 2, 3, 5, 8]
+evens = filter l as v { v % 2 is 0 } // [2, 8]
+
+m = { "a": "foo", "b": "bar" }
+matched_foo = filter m as _, v { v is "foo" } // { "a": "foo" }
+```
+
+## Map Expression
+
+`map` is a [quantifier
+expression](/sentinel/language/spec#quantifier-expressions-any-all-filter-map) that
+returns a list, regardless of the input collection type. Each element within the
+input collection is evaluted according to the map expression body and appended
+to the result.
+
+```sentinel
+l = [1, 2]
+r = map l as v { v % 2 } // [false, true]
+
+m = { "a": "foo", "b": "bar" }
+r = map m as k, v { v } // ["foo", "bar"]
+```
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/language/conditionals.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/language/conditionals.mdx
new file mode 100644
index 0000000000..1736795dbb
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/language/conditionals.mdx
@@ -0,0 +1,161 @@
+---
+page_title: Sentinel Language - Conditionals
+sidebar_current: docs-language-conditional
+description: |-
+ Conditional statements allow your policy to behave differently depending on a condition.
+layout: docs
+---
+
+# Language: Conditionals
+
+Conditional statements allow your policy to behave differently depending
+on a condition.
+
+Conditional statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## If Statements
+
+`if` statements only execute their bodies if a condition is met.
+The syntax of an `if` statement is:
+
+```sentinel
+if condition {
+ // ... this is executed if condition is true
+}
+```
+
+The `condition` must result in a boolean, such as by calling a function
+or evaluating a [boolean expression](/sentinel/language/boolexpr). If
+the `condition` is `true`, the body (within the `{}`) is executed. Otherwise,
+the body is skipped.
+
+Examples:
+
+```sentinel
+// This would execute the body
+value = 12
+if value is 18 {
+ print("condition met")
+}
+
+// Direct boolean values can be used
+value = true
+if value {
+ print("condition met")
+}
+
+// This would not execute the body since the boolean expression will
+// result in undefined.
+value = {}
+if value["key"] > 12 {
+ print("condition met")
+}
+```
+
+### Else, Else If
+
+An `else` clause can be given to an `if` statement to execute a body
+in the case the condition is _not met_. By putting another `if` statement
+directly after the `else`, multiple conditions can be tested for.
+The syntax is:
+
+```sentinel
+if condition {
+ // ...
+} else {
+ // ...
+}
+
+if condition {
+ // ...
+} else if other_condition {
+ // ...
+} else {
+ // ...
+}
+```
+
+### Scoping
+
+The body of an `if` statement does not create a new
+[scope](/sentinel/language/scope). Any variables assigned within the body
+of an if statement will modify the scope that the `if` statement itself
+is in.
+
+Example:
+
+```sentinel
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+```sentinel
+a = 18
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+## Case Statements
+
+`case` statements are a selection control mechanism that execute a clause based
+on matching expressions. It is worth noting that the expression for `case` is
+optional. When no expression is provided, it defaults the expression to `true`.
+Additionally, the order of clauses is important, as they are evaluated from top
+to bottom, executing the first match.
+The syntax of a case statement is:
+
+```sentinel
+case expression {
+ when clause_expression:
+ // executed when clause_expression and expression are equal
+ else:
+ // executed if no clause matches expression
+}
+```
+
+### When Clause
+
+Any clause that has an expression for comparison must use the `when` keyword.
+It accepts a list of expressions, seperated by a `,`.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+}
+```
+
+```sentinel
+case {
+ when x > 40:
+ return true
+}
+```
+
+### Else Clause
+
+The `else` keyword allows for capturing any expressions that have no matching
+`when` clause.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+ else:
+ return false
+}
+```
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/language/functions.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/language/functions.mdx
new file mode 100644
index 0000000000..73702acc3e
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/language/functions.mdx
@@ -0,0 +1,173 @@
+---
+page_title: Sentinel Language - Functions
+sidebar_current: docs-language-functions
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Functions
+
+Functions allow you to create reusable code to perform computations.
+
+Below is a trivial example function that doubles a number and shows
+the main rule using the function:
+
+```sentinel playground
+double = func(x) {
+ return x * 2
+}
+
+main = rule { double(12) is 24 }
+```
+
+Functions may contain any expressions,
+[conditionals](/sentinel/language/conditionals),
+and [loops](/sentinel/language/loops). They must return a value
+at the end of their execution. If no reasonable return value exists,
+the function should return [undefined](/sentinel/language/undefined).
+
+## Creating a Function
+
+A function is created by assigning a variable to a `func`.
+
+A function can have zero or more parameters. These parameters are specified
+within parentheses `()` separated by commas. If a function has no parameters,
+the parentheses must still be present.
+
+A function must terminate with a `return` statement to return a value back to
+the caller. Only a single value can be returned. The type of a return can
+vary between return statements.
+
+Example:
+
+```sentinel
+add1 = func(x) { return x + 1 }
+```
+
+This example creates a function that adds 1 to the parameter `x`. It is
+assigned to the variable `add1`.
+
+## Calling a Function
+
+Functions are called by accessing their name followed by parentheses with
+a list of comma-separated values for the parameters. If the function takes no
+parameters, an empty set of parentheses must still be used to denote a
+function call.
+
+To call the function in the example above:
+
+```sentinel
+x = 1
+y = add1(x)
+```
+
+## Scoping
+
+The body of a `func` creates a new [scope](/sentinel/language/scope).
+
+The parent scope is the scope in which the function is created. This allows
+for the creation of functions within functions that can access their outer
+scopes.
+
+Example:
+
+```sentinel
+f = func() {
+ a = 42
+ print(a) // 42
+ return undefined
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+f = func() {
+ a = 42
+ return undefined
+}
+
+print(a) // 18
+```
+
+And below is an example of creating a function in a function which uses
+outer values:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double()
+}
+
+print(f()) // 84
+```
+
+A more complex example below shows how scoping works when passing
+functions around as arguments or results:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double
+}
+
+double = f()
+print(double()) // 84
+```
+
+## Pass By Value
+
+The parameters to a function are passed by value, but not deep copied.
+This means that elements of collections can still be modified but the
+root value cannot be changed.
+
+Example:
+
+```sentinel
+f = func(x) {
+ x = "value"
+ return x
+}
+
+x = "outside"
+f(x)
+print(x) // "outside"
+```
+
+```sentinel
+f = func(x) {
+ append(x, "value")
+ return x
+}
+
+x = []
+f(x)
+print(x) // ["value"]
+```
+
+## Recursion
+
+A function is allowed to call other functions, including itself.
+This can be used to implement recursion. For example, the fibonacci sequence
+is implemented below:
+
+```sentinel
+fib = func(x) {
+ if x <= 0 {
+ return undefined
+ }
+
+ if x == 1 {
+ return 1
+ } else {
+ return x + fib(x - 1)
+ }
+}
+```
+
+Note that this example also shows using
+[undefined](/sentinel/language/undefined)
+as a return value in cases where a function has undefined behavior.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/language/imports.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/language/imports.mdx
new file mode 100644
index 0000000000..0ed1541b85
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/language/imports.mdx
@@ -0,0 +1,116 @@
+---
+page_title: Sentinel Language - Imports
+sidebar_current: docs-language-imports
+description: >-
+ Imports enable a Sentinel policy to access reusable libraries and external
+ data and functions.
+layout: docs
+---
+
+# Language: Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external
+data and functions. The exact list of available imports is dependent on the
+host system. Host systems may also make the available imports configurable
+via plugins.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that
+the import `calendar` allows access to engineer calendars. This import
+only exists for the purpose of this example and at the time of writing is
+not a real available import.
+
+```sentinel
+import "calendar"
+
+// Get the calendar for Bob for today
+bob_calendar = calendar.for("bob").today
+
+// Allow this policy to pass if Bob is not on vacation.
+main = rule { not bob_calendar.has_event("vacation") }
+```
+
+This example shows an import being used to deny a behavior if Bob is on
+vacation. Hopefully real policies do not depend on a single person being
+in the office, but the example shows the power of imports.
+
+In addition to consuming imports, you can also
+[extend Sentinel by writing custom imports](/sentinel/extending)
+This allows you to add any arbitrary behavior to your Sentinel-protected
+systems.
+
+## Importing
+
+Whether accessing a built-in library or an external plugin, the syntax
+for an import is the same:
+
+```sentinel
+import "name"
+```
+
+This adds the import with the name `name`. It can be referenced using
+the identifier `name`. For example, if the import above exposed first
+and last name data, you could access it via `name.first` or `name.last`.
+
+You can shadow imports by assigning a variable. In the example below,
+the import `name` becomes unavailable within the function because it is
+shadowed by a variable of the same name.
+
+Shadowing an import is not the same as overwriting a variable. Imports
+are not part of the [scope](/sentinel/language/scope), meaning that
+the shadow only exists for the scope it is created within. For everything
+else, the import works even after it is shadowed. The example below shows that
+as well.
+
+```sentinel
+import "name"
+
+f = func() {
+ name = "jane"
+ return name
+}
+
+print(f()) // "jane"
+print(name.first) // "bob"
+```
+
+## Aliases
+
+An import can be aliased to another name using the syntax below:
+
+```sentinel
+import "name" as other
+```
+
+This would make the import "name" available as `other`.
+
+An import may only be imported once, regardless of aliases. The following
+would be **invalid** and would result in an error:
+
+```sentinel
+import "name" as one
+import "name" as two
+```
+
+## Accessing Import Data
+
+Imports are accessed with dot-separated identifiers, such as `name.first` or
+`name.stage.first`. These are called _selector expressions_. An import may
+return nested data that further can be accessed via selector expressions.
+
+In the first example on this page with the "calendar" import, we see this:
+
+```sentinel
+import "calendar"
+
+a = calendar.for("bob") // selector expression calendar.for
+b = a.today // selector expression on the result
+c = b.vacation // accessing further nested data
+```
+
+The exact structure of an import is dependent on the import. Please reference
+the documentation for an import to learn more.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/language/index.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/language/index.mdx
new file mode 100644
index 0000000000..7f588c94cd
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/language/index.mdx
@@ -0,0 +1,114 @@
+---
+page_title: Sentinel Language
+sidebar_current: docs-language-basics
+description: |-
+ Sentinel policies are written using the Sentinel language. This language
+ is easy to learn and easy to write. You can learn the Sentinel language
+ and be productive within an hour. Learning Sentinel doesn't require any
+ formal programming experience.
+layout: docs
+---
+
+# Sentinel Language
+
+Sentinel policies are written using the Sentinel language. This language
+is easy to learn and easy to write. You can learn the Sentinel language
+and be productive within an hour. Learning Sentinel doesn't require any
+formal programming experience.
+
+This language guide will contain many details that are not necessary to
+be productive with Sentinel or are targeted to those who are looking for
+exact information about the language (such as what types of line endings are
+allowed). You can safely ignore these sentences.
+
+You may also view the
+[official language specification](/sentinel/language/spec)
+This is a specific and detailed document on the syntax and behavior of
+the language primarily intended for implementation creators and to disambiguate
+the language.
+
+## Simplest Example
+
+The example below is about the simplest practical example of Sentinel.
+It is reasonable to imagine this as a realistic policy. This shows that
+in most cases, Sentinel will be extremely simple:
+
+```sentinel
+main = rule { request.method is "GET" and request.headers contains "X-Key" }
+```
+
+## Files
+
+Sentinel policies are single files that end in the `.sentinel` file extension.
+There is currently no built-in mechanism to Sentinel for merging multiple
+files. This is purposefully done to make Sentinel policies easy to submit
+to systems that support Sentinel policies.
+
+Sentinel policy files must be UTF-8 encoded and can end in both
+Unix (LF) or Windows (CRLF) line breaks. When running the
+[auto-formatter](#),
+line endings will always use Unix line breaks.
+
+## Ordering
+
+Sentinel policies are executed top-down. For example:
+
+```sentinel
+a = 1 // a = 1 here
+b = a + 1 // b = 2 here
+a = 3 // a = 3, b = 2
+```
+
+In this example, the value of `a` and `b` is shown at each line. Since Sentinel
+executes values top-down, the final value of `a` is 3 and `b` is 2. `b` _does
+not_ become 4.
+
+## Main
+
+Sentinel expects there to be a `main` [rule](/sentinel/language/rules).
+The value of this rule is the result of the entire policy.
+
+The result of the policy depends on the evaluated contents of the `main` rule.
+For booleans, a policy passes on a `true` value, and fails on a `false` value.
+Other types generally follow a zero or zero-length pattern for determining
+success.
+
+| Type | Passing Condition | Failing Condition |
+| ------- | ----------------- | -------------------------- |
+| Boolean | `true` | `false` |
+| String | `""` | Any non-zero length string |
+| Integer | `0` | Any non-zero value |
+| Float | `0.0` | Any non-zero value |
+| List | `[]` | Any non-zero length list |
+| Map | `{}` | Any non-zero length map |
+
+A value of main that falls outside of the above types will result in a policy
+error. As a special case, if `main` evaluates to an undefined value, the error
+message will indicate as such, with a reference to where the undefined value was
+encountered.
+
+## More Complex Example
+
+The simple example above is a full working example. In our experience with
+Sentinel, many policies can be representing using this simple form. However,
+to show more features of the language, a more complex example is shown below.
+This example is also a realistic example of what Sentinel may be used for.
+
+```sentinel
+import "units"
+
+memory = func(job) {
+ result = 0
+ for job.groups as g {
+ for g.tasks as t {
+ result += t.resources.memory else 0
+ }
+ }
+
+ return result
+}
+
+main = rule {
+ memory(job) < 1 * units.gigabyte
+}
+```
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/language/lists.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/language/lists.mdx
new file mode 100644
index 0000000000..bc5269d8eb
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/language/lists.mdx
@@ -0,0 +1,135 @@
+---
+page_title: Sentinel Language - Lists
+sidebar_current: docs-language-lists
+description: Lists are a collection of zero or more values.
+layout: docs
+---
+
+# Language: Lists
+
+Lists are a collection of zero or more values.
+
+Lists can be created using by wrapping values in `[]` and separating them
+by commas. An optional trailing comma is allowed. List elements can be
+differing types. Examples:
+
+```sentinel
+[] // An empty list
+["foo"] // Single element list
+["foo", 1, 2, true] // Multi element list with different types
+["foo", [1, 2]] // List containing another list
+```
+
+A list can be [sliced](/sentinel/language/slices) to create sublists.
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for value inclusion in a list.
+
+## Accessing Elements
+
+List elements can be accessed with the syntax `name[index]` where
+`index` is zero-indexed.
+
+A negative index accesses the list in reverse. It is the same as
+reversing a list and then using a positive index. Similar to a positive
+index, it is bounded by the length of the list as a negative value.
+
+Accessing beyond the length of the list results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+a = ["foo", 1, true, [1, 2]]
+
+a[0] // "foo"
+a[2] // true
+a[4] // undefined
+a[-2] // true
+a[-4] // "foo"
+a[-5] // undefined
+a[3][1] // 2
+```
+
+## List Append
+
+Values can be appended to a list using the built-in
+[append](/sentinel/functions/append) function.
+
+This modifies the list in-place and returns
+[undefined](/sentinel/language/undefined). For more information on
+why `append` behaves this way, please read the full documentation for the
+[append](/sentinel/functions/append) function.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append([1,2], "foo") // [1, 2, "foo"]
+append([1,2], [3]) // [1, 2, [3]]
+append(1, 3) // error()
+```
+
+## List Concatenation
+
+Two lists can be concatenated using the `+` operator or the shorthand
+`+=` assignment operator. For the `+` operator, a new list is returned.
+For `+=`, the left-hand list is modified in place.
+
+Examples:
+
+```sentinel
+[1] + [2] // [1, 2]
+[1] + [[1]] // [1, [1]]
+[1] + 1 // error
+
+a = [1]
+a += [2] // a = [1, 2]
+a += 3 // error
+```
+
+## List Length
+
+The length of a list can be retrieved using the
+[length](/sentinel/functions/length) function.
+
+Examples:
+
+```sentinel
+length([]) // 0
+length(["foo"]) // 1
+```
+
+## Removing Items From a List
+
+You can use a combination of [list concatenation](#list-concatenation) and
+[slicing](/sentinel/language/slices) to remove elements from a list.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+a = a[:2] + a[3:] // [1, 2, 4, 5]
+```
+
+The shorthand shown here is effectively the same as `a[0:2] + a[3:length(a)]`,
+which creates a new list out of the concatenation two sub-lists composed of the
+first two elements, and the rest of the list starting at index 3. This
+effectively removes the 3rd element from the list (index 2).
+
+## List Comparison
+
+Lists may be [compared](/sentinel/language/boolexpr#comparison-operators)
+for equality. Lists are equal if they are of equal length and their
+corresponding elements are comparable and equal. Lists with the same elements
+but in a different order are not equal.
+
+```sentinel
+[1, 2] is [1, 2] // true
+[1, 2] is [2, 1] // false
+["a"] is ["a", "b"] // false
+["a", ["b", "c"]] is ["a", ["b", "c"]] // true
+```
+
+List comparison speed is O(N), meaning that the speed of the comparison is
+_linearly_ proportional to the number of elements in the list. The more
+elements, the more iterations that are necessary to verify equality.
+
+-> The N-value quoted above should account for the sum of the elements of _all_
+lists in the subjects of comparison, as list comparison will recurse into these
+lists to check for equality.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/language/logging-errors.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/language/logging-errors.mdx
new file mode 100644
index 0000000000..705a72cbd4
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/language/logging-errors.mdx
@@ -0,0 +1,52 @@
+---
+page_title: Sentinel Language - Logging and Errors
+sidebar_current: docs-language-log
+description: The built-in functions `print` and `error` can be used for logging and errors.
+layout: docs
+---
+
+# Language: Logging and Errors
+
+The built-in functions `print` and `error` can be used for logging
+and errors. Logging is helpful to understand how a policy is executing
+in development. And errors are useful to halting execution immediately in
+certain scenarios.
+
+## Print
+
+The `print` function is used to output debug information. The exact location
+where this print statements go is dependent on the host application and
+the Sentinel runtime itself.
+
+The `print` function takes zero or more Sentinel values as arguments.
+Each value is formatted for human-friendly output and space-separated.
+Example:
+
+```sentinel
+value = 42
+print("the value is", value) // the value is 42
+
+map = { "foo": false }
+print(map) // { "foo": false }
+
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // false
+```
+
+## Errors
+
+Certain actions in Sentinel, such as accessing an undefined variable,
+attempting to add two incompatible types, etc. result in _runtime errors_.
+These errors halt the execution of a policy immediately. The pass/fail result
+of a policy is considered fail.
+
+Runtime errors can be manually triggered using the `error` function.
+The `error` function behaves exactly like the `print` function except that
+execution is halted immediately.
+
+This should only be used in scenarios where the policy _must fail_ due to
+some unexpected or invalid condition. The line between `error` and
+[undefined](/sentinel/language/undefined) can sometimes be unclear. Undefined
+is recommended when a policy can conceivably recover or safely ignore the
+undefined behavior. An error should be used when the policy should fail and
+notify someone regardless.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/language/loops.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/language/loops.mdx
new file mode 100644
index 0000000000..622732bd35
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/language/loops.mdx
@@ -0,0 +1,92 @@
+---
+page_title: Sentinel Language - Loops
+sidebar_current: docs-language-loops
+description: >-
+ Loop statements allow you to execute a body of code for each element in a
+ collection or for some fixed number of times.
+layout: docs
+---
+
+# Language: Loops
+
+Loop statements allow you to execute a body of code for each element in
+a collection or for some fixed number of times.
+
+Loop statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## For Statements
+
+`for` statements allow repeated execution of a block for each
+element in a collection.
+
+Example:
+
+```sentinel
+// A basic sum
+count = 0
+for [1, 2, 3] as num {
+ count += num
+}
+```
+
+The syntax is `for COLLECTION as value`. This will iterate over the
+collection, assigning each element to `value`. In the example above,
+each element is assigned to `num`. The body is executed for each element.
+In the example above, the body adds `num` to the `count` variable. This
+creates a basic sum of all values in the collection.
+
+For a map, the assigned element is the key in the map. In the example
+below, `name` would be assigned map keys.
+
+```sentinel
+list = []
+for { "a": 1, "b": 2 } as name {
+ append(list, name)
+}
+
+print(list) // ["a" "b"]
+```
+
+An alternate syntax is `for COLLECTION as key, value`. This will assign
+both the key and value to a variable. For a list, the key is the element
+index. For a map, it is the key and value is assigned the element value.
+Example:
+
+```sentinel
+count = 0
+for { "a": 1, "b": 2 } as name, num {
+ count += num
+}
+
+print(count) // 3
+```
+
+## Scoping
+
+The body of a `for` statement creates a new
+[scope](/sentinel/language/scope). If a variable is assigned within
+the body of a for statement that isn't assigned in a parent scope,
+that variable will only exist for the duration of the body execution.
+
+Example:
+
+```sentinel
+for list as value {
+ a = 42
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+for list as value {
+ a = 42
+}
+
+print(a) // 18
+```
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/language/maps.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/language/maps.mdx
new file mode 100644
index 0000000000..298bf9a567
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/language/maps.mdx
@@ -0,0 +1,121 @@
+---
+page_title: Sentinel Language - Maps
+sidebar_current: docs-language-maps
+description: >-
+ Maps are a collection of zero or more key/value pairs. These are useful for
+ storing values that are looked up by a unique key.
+layout: docs
+---
+
+# Language: Maps
+
+Maps are a collection of zero or more key/value pairs. These are useful
+for storing values that are looked up by a unique key.
+
+Maps can be created using `{}` and specifying key/value pairs. Keys and
+values can be differing types. An optional trailing comma is allowed.
+Examples:
+
+```sentinel
+// Empty map
+{}
+
+// Map with a single value on one line
+{ "key": "value" }
+
+// Map with multiple values with differing types on multiple lines
+{
+ "key": "value",
+ 42: true,
+}
+```
+
+Maps are **unordered**. When
+[looping](/sentinel/language/loops)
+over a map, the key/value pairs can be returned in any order.
+
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for key inclusion in a map.
+
+## Accessing Elements
+
+Map elements can be accessed with the syntax `name[key]`. This looks up
+a value by a key.
+
+Accessing a key that doesn't exist results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+map = { "key": "value", 42: true, }
+
+map["key"] // "value"
+map[42] // true
+map[0] // undefined
+```
+
+## Modifying or Adding Elements
+
+Elements can be added or modified in a map by assigning to `name[key]`.
+If the key doesn't exist, the value is added. If the key already exists,
+the value is overridden.
+
+```sentinel
+map = { "key": "value" }
+
+map[42] = true // Add a new key/value
+map["key"] = 12 // Modify the value of "key"
+```
+
+## Deleting Elements
+
+An element can be deleted from a map using the
+[delete](/sentinel/functions/delete) function.
+
+Examples:
+
+```sentinel
+map = { "key": "value" }
+delete(map, "key") // map is now empty
+delete(map, "other") // no effect for non-existent key
+```
+
+## Keys and Values
+
+The keys and values of a map can be retrieved as
+[lists](/sentinel/language/lists) using the
+[keys](/sentinel/functions/keys) and
+[values](/sentinel/functions/values) functions.
+
+Because maps are unordered, the keys and values are returned in an
+unspecified order. It should not be assumed that keys and values will be
+returned in a consistent order.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+## Map Comparison
+
+Maps may be [compared](/sentinel/language/boolexpr#comparison-operators) for
+equality. Maps are equal if they are of equal length and both their
+corresponding keys and values are comparable and equal.
+
+```sentinel
+{"foo": "bar"} is {"foo": "bar"} // true
+{"foo": "bar"} is {"baz": "bar"} // false
+{"foo": "bar"} is {"foo": "baz"} // false
+{"foo": "bar"} is {"foo": "bar", "baz": "qux"} // false
+{1: "a"} is {1.0: "a"} // true (int/float comparable)
+
+// also true (maps are not ordered):
+{"m": {"a": "b"}, "l": ["a"]} is {"l": ["a"], "m": {"a": " b"}}
+```
+
+-> The current worst-case comparison speed for maps is O(N²), or _quadratic
+time_. This is the square of the cumulative elements or the parent and all child
+maps contained within the map. Consider this when making comparisons on larger,
+more complex maps. This may be optimized in the future.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/language/parameters.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/language/parameters.mdx
new file mode 100644
index 0000000000..348acf2593
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/language/parameters.mdx
@@ -0,0 +1,133 @@
+---
+page_title: Sentinel Language - Parameters
+sidebar_current: docs-language-parameters
+description: >-
+ Parameteres allow a Sentinel policy to describe variables that are expected
+ to be supplied by the calling application, in addition to any default value.
+layout: docs
+---
+
+# Language: Parameters
+
+Sentinel allows a policy author to supply parameters to help facilitate policy
+reuse and ensure sensitive values do not need to be hard-coded in a policy.
+
+Parameters are supplied by using the `param` keyword, followed by an identifier.
+A default value can also be supplied by using the `default` keyword.
+
+```sentinel
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+```
+
+Once declared, parameters can be used like any other variable, including being
+re-assigned.
+
+```sentinel
+param foo default 1 // 1 (default)
+
+foo = foo + 1 // 2
+```
+
+## Variable Descriptions
+
+You can supply a description to a parameter by adding a comment at the top of
+it. This value can be communicated to a specific implementation of Sentinel to
+provide information about what the parameter is for during configuration.
+
+```sentinel
+// An example parameter. Must be supplied or the policy will fail.
+param foo
+```
+
+## Supplying Parameter Values Using the Sentinel CLI
+
+In a production implementation, supplying parameters to a policy is an
+implementation-specific detail - see the documentation for your particular
+implementation for details.
+
+Using the [Sentinel CLI](/sentinel/commands), you can supply parameters one of
+four ways.
+
+### Supplying Parameter Values Using the Configuration File
+
+You can supply parameters using the
+[`param`](/sentinel/configuration#parameters) section of the
+[configuration file](/sentinel/configuration).
+
+```hcl
+param "foo" {
+ value = "bar"
+}
+```
+
+This method works for both `sentinel apply` and `sentinel test`.
+
+### Supplying Parameter Values Using the Environment
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can supply a value using environment variables - prefix the parameter with
+`SENTINEL_PARAM_`, using the name of the parameter to supply.
+
+```plaintext
+SENTINEL_PARAM_foo=bar sentinel apply policy.sentinel
+```
+
+### Supplying Parameter Values Using the CLI
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can also use the `-param` CLI argument to supply parameter in a `key=value`
+pair.
+
+```plaintext
+sentinel apply -param foo=bar policy.sentinel
+```
+
+### Interactive CLI Prompting
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+If a required value has not been supplied when a policy is run with `sentinel apply`, it will be prompted for, along with its description:
+
+
+
+ $ sentinel apply policy.sentinel
+
+ policy.sentinel:2:7: requires value for parameter foo
+
+ An example parameter. Must be supplied or the policy will fail.
+
+
+ Values can be strings, floats, or JSON array or object values. To
+ force
+
+ strings, use quotes.
+
+
+ Enter a value: bar
+
+
+
+ Pass
+
+
+
+
+### CLI Value Format
+
+-> **NOTE:** This section contains details for the parameter features supported by `sentinel apply`.
+
+The CLI takes either strings, or JSON numbers, arrays, or maps. If you need a
+literal string value, quote the value.
+
+```sentinel
+foo // string
+42 // number (float)
+"42" // string ("42", without quotes)
+[1, 2] // array (list)
+{"a": "b"} // object (map)
+```
+
+-> **NOTE:** Boolean values are not supported by this method.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/language/rules.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/language/rules.mdx
new file mode 100644
index 0000000000..e31924d414
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/language/rules.mdx
@@ -0,0 +1,204 @@
+---
+page_title: Sentinel Language - Rules
+sidebar_current: docs-language-rules
+description: >-
+ Rules form the basis of a policy by representing behavior that is either
+ passing or failing (true or false).
+layout: docs
+---
+
+# Language: Rules
+
+Rules form the basis of a policy by representing behavior that is either passing
+or failing. A policy can be broken down into a set of rules. Breaking down a
+policy into a set of rules can make it more understandable and aids with
+testing.
+
+An example is shown below:
+
+```sentinel playground
+weather = "sunny"
+day = "wednesday"
+is_sunny = rule { weather is "sunny" }
+is_wednesday = rule { day is "wednesday" }
+
+main = rule {
+ is_sunny and
+ is_wednesday
+}
+```
+
+A rule contains a single expression. This can be a simple [boolean
+expression](/sentinel/language/boolexpr), or an expression representing the
+discovery of a set of violations using more in-depth expressions like [`filter`
+and `map`](/sentinel/language/collection-operations). You can split expressions
+into multiple lines for readability, as you would with any other expression
+within Sentinel.
+
+A rule is also _lazy_ and _memoized_. _Lazy_ means that the rule is only
+evaluated when it is actually required, not at the point that it is
+created. This is covered in more detail in the [lazy](#lazy-and-memoized) section.
+_Memoized_ means that the value is only computed once and then saved. Once
+a rule is evaluated, the result of it is reused anytime the rule is referenced
+again.
+
+## Making rules easier to understand
+
+Rules are an abstraction that make policies much more understandable
+by making it easier for humans to see what the policy is trying to do.
+
+For example, consider the policy before which doesn't abstract into rules:
+
+```sentinel
+main = rule {
+ ((day is "saturday" or day is "sunday") and homework is "") or
+ (day in ["monday", "tuesday", "wednesday", "thursday", "friday"] and
+ not school_today and homework is "")
+}
+```
+
+Assume that `day`, `homework`, and `school_day` are available.
+
+In plain English, this policy is trying to say: you can play with your friends
+only on the weekend as long as there is no homework, or during the week if
+there is no school and no homework.
+
+Despite the relatively simple nature of this policy (a few lines, about 5
+logical expressions), it is difficult to read and understand, especially if
+you've not seen it before.
+
+The same policy using rules as an abstraction:
+
+```sentinel
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+is_valid_weekend = rule { is_weekend and homework is "" }
+is_valid_weekday = rule { not is_weekend and not school_today and homework is "" }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+By reading the names of the rules, its much clearer to see what each individual
+part of the policy is trying to achieve. The readability is improved even
+further when adding comments to the rules to explain them further, which is
+a recommended practice:
+
+```sentinel
+// A weekend is Sat or Sun
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+// A valid weekend is a weekend without homework
+is_valid_weekend = rule { is_weekend and homework is "" }
+
+// A valid weekday is a weekday without school
+is_valid_weekday = rule { not is_weekend and not school_today and homework is "" }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+## "When" Predicates
+
+A rule may have an optional `when` predicate attached to it. When this is
+present, the rule is only evaluated when the predicate results in `true`.
+Otherwise, the rule is not evaluated and the result of the rule is always
+`true`.
+
+"When" predicates should be used to define the context in which the rule
+is semantically meaningful. It is common when translating human language
+policy into Sentinel to have scenarios such as "when the key has a prefix
+of `/account/`, the remainder must be numeric." In that example, when the
+key _does not_ have the specified prefix, the policy doesn't apply.
+
+Assume you have rules `is_prefix` and `is_numeric` to check both the
+conditions in the example above. An example is shown with and without
+the "when" predicate:
+
+```sentinel
+example_no_when = rule { (is_prefix and is_numeric) or not is_prefix }
+
+example_when = rule when is_prefix { is_numeric }
+```
+
+The rules are equivalent in behavior for all values of `is_prefix` and
+`is_numeric`, but the second rule is more succint and easier to understand.
+
+## Testing
+
+To verify a policy works correct, the
+[built-in Sentinel test framework](/sentinel/writing/testing)
+uses rules as the point where you can assert behavior. You say that
+you expect certain rules to be true or false. By doing so, you ensure that
+the policy results in the value you expect using the rule flow that you
+expect.
+
+Therefore, in addition to readability, we recommend splitting policies into
+rules to aid testability.
+
+## Non-Boolean Values
+
+Sentinel supports non-boolean values in rules. This can be useful for passing
+more detail along to a calling integration when more detail is required than a
+boolean value would be able to communicate. This data shows up in the [policy
+trace](/sentinel/writing/tracing) and can be utilized in different ways
+depending on the integration you are using Sentinel with.
+
+Consider a different take on our policy above:
+
+```sentinel
+// A policy to check a set of scheduled days to determine what days you can't
+// come out and play, based on the day not being a weekend day and there being
+// homework to do.
+
+param days
+
+main = rule {
+ filter days as d {
+ d.day not in ["saturday", "sunday"] and
+ d.homework is not ""
+ }
+}
+```
+
+When given a value in the set of `days` that has a `day` that is a weekday, and
+`homework` present, the policy will fail. Additional, when tracing was enabled,
+the days that were detected as violating the policy would be given in the trace
+for the `main` rule.
+
+Generally, for non-boolean values, a policy fails on non-zero data. See the
+details on [the `main` rule](/sentinel/language#main) for more details.
+
+The result of a rule must be either a boolean, string, integer, float, list, or
+map. All other types will result in a runtime error.
+
+## Lazy and Memoized
+
+A rule is _lazy_ and _memoized_.
+
+_Lazy_ means that the rule is only evaluated when it is actually required,
+not at the point that it is created. And _memoized_ means that the value is
+only computed once and then saved. Once a rule is evaluated, the result of it
+is reused anytime the rule is referenced again.
+
+Both of these properties have important implications for Sentinel
+policies:
+
+**Performance:** Rules are a way to improve the performance of a Sentinel
+policy. If you have a boolean expression that is reused a lot, a rule will
+only be evaluated once. In the example shown above, `is_weekend` is used
+multiple times, but will only have to be evaluated once.
+
+**Behavior:** Rules accessing variables see the value of those variables
+at _the time they are evaluated_. This can lead to surprising behavior
+in some cases. For example:
+
+```sentinel playground
+a = 1
+b = rule { a == 1 }
+a = 2
+main = b
+```
+
+In this example, `main` will actually result to `false`. It is evaluated
+when it is needed, which is when the policy executes `main`. At this point,
+`a` is now 2 and `b` has not been evaluated yet. Therefore, `b` becomes `false`.
+All future references to `b` will return `false` since a rule is memoized.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/language/scope.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/language/scope.mdx
new file mode 100644
index 0000000000..fb81cdef59
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/language/scope.mdx
@@ -0,0 +1,41 @@
+---
+page_title: Sentinel Language - Scope
+sidebar_current: docs-language-scope
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Scope
+
+Scope is the context in which variables are created and accessed. If a
+variable exists in a parent scope, it is accessed and can be modified.
+Otherwise, the variable is created in your current scope.
+
+Scopes are created with _blocks_. A block is a possibly empty sequence
+of statements within matching brace brackets `{}`. You may nest blocks
+to create nested scopes.
+
+In addition to explicitly created blocks, there are implicit blocks:
+
+1. The policy block encapsulates an entire policy.
+2. Each `any`, `all`, and `for` statement is considered to be in
+ its own block. Note that `if` statements _do not_ create their
+ own block.
+
+The example policy below shows various effects of scopes:
+
+```sentinel
+a = 1
+print(a) // 1
+
+f = func() {
+ print(a) // 1
+ a = 12
+ b = 1
+ return undefined
+}
+f()
+
+print(a) // 12
+print(b) // undefined
+```
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/language/slices.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/language/slices.mdx
new file mode 100644
index 0000000000..82dc7f4c18
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/language/slices.mdx
@@ -0,0 +1,45 @@
+---
+page_title: Sentinel Language - Slices
+sidebar_current: docs-language-slices
+description: >-
+ Slices are an efficient way to create a substring or sublist from an existing
+ string or list, respectively.
+layout: docs
+---
+
+# Language: Slices
+
+Slices are an efficient way to create a substring or sublist from an
+existing string or list, respectively.
+
+The syntax for creating a slice is:
+
+```sentinel
+a[low : high]
+```
+
+`low` is the lowest index to slice from. The resulting slice includes
+the value at `low`. `high` is the index to slice to. The resulting slice
+will not include the value at `high`. The length of the resulting list
+or string is always `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+
+a = "hello"
+b = a[1:4] // "ell"
+```
+
+## Convenience Shorthands
+
+Some convenience shorthands are available by omitting the value for either the
+low or high index in the slice expression: omitting `low` implies a low index of
+0, and omitting `high` implies a high index of the length of the list.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+
+b = a[:2] // [1, 2] (same as a[0:2])
+b = a[2:] // [3, 4, 5] (same as a[2:length(a)])
+```
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/language/spec.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/language/spec.mdx
new file mode 100644
index 0000000000..fb436296a1
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/language/spec.mdx
@@ -0,0 +1,1329 @@
+---
+page_title: Sentinel Language Specification
+sidebar_current: docs-language-spec
+description: This is the specification for the Sentinel policy language.
+layout: docs
+---
+
+# Sentinel Language Specification
+
+This is the specification for the Sentinel policy language.
+
+The Sentinel language is designed with policy enforcement in mind. It is dynamically typed and garbage collected and has explicit support for _rule_ construction representing boolean logic.
+
+The language is designed to be easy to learn and use by non-programmers. It is expected to be embedded within applications.
+
+## Table of Contents
+
+
+
+
+- [Source code representation](#source-code-representation)
+- [Declarations and Scope](#declarations-and-scope)
+- [Blocks](#blocks)
+- [Lexical Elements](#lexical-elements)
+ - [Comments](#comments)
+ - [Identifiers](#identifiers)
+ - [Keywords](#keywords)
+ - [Operators and Delimiters](#operators-and-delimiters)
+ - [Integer Literals](#integer-literals)
+ - [Floating-point Literals](#floating-point-literals)
+ - [String Literals](#string-literals)
+ - [Implicit Line Joining](#implicit-line-joining)
+ - [Whitespace](#whitespace)
+ - [Semicolons](#semicolons)
+- [Variables](#variables)
+- [Undefined](#undefined)
+- [Expressions](#expressions)
+ - [Operand](#operand)
+ - [Primary Expressions](#primary-expressions)
+ - [Null](#null)
+ - [Booleans](#booleans)
+ - [Boolean Literals](#boolean-literals)
+ - [Boolean Expressions](#boolean-expressions)
+ - [List Literals](#list-literals)
+ - [Map Literals](#map-literals)
+ - [Function Literals](#function-literals)
+ - [Rule Expressions](#rule-expressions)
+ - [Index Expressions](#index-expressions)
+ - [Selectors](#selectors)
+ - [Slice Expressions](#slice-expressions)
+ - [Calls](#calls)
+ - [Operators](#operators)
+ - [Operator Precedence](#operator-precedence)
+ - [Arithmetic Operators](#arithmetic-operators)
+ - [Integer operators](#integer-operators)
+ - [Integer overflow](#integer-overflow)
+ - [Floating-point operators](#floating-point-operators)
+ - [String Concatenation](#string-concatenation)
+ - [List Concatenation](#list-concatenation)
+ - [Comparison Operators](#comparison-operators)
+ - [Logical Operators](#logical-operators)
+ - [Set Operators](#set-operators)
+ - [Matches Operator](#matches-operator)
+ - [Else Operator](#else-operator)
+ - [Quantifier Expressions (any, all, filter, map)](#quantifier-expressions-any-all-filter-map)
+- [Statements](#statements)
+ - [Expression Statements](#expression-statements)
+ - [Assignments](#assignments)
+ - [If Statements](#if-statements)
+ - [Case Statements](#case-statements)
+ - [For Statements](#for-statements)
+ - [Break Statements](#break-statements)
+ - [Continue Statements](#continue-statements)
+ - [Return Statements](#return-statements)
+- [Imports](#imports)
+- [Parameters](#parameters)
+- [Built-in Functions](#built-in-functions)
+ - [Length](#length)
+ - [Collections](#collections)
+ - [List Append](#list-append)
+ - [Map Delete](#map-delete)
+ - [Keys and Values](#keys-and-values)
+ - [Range](#range)
+ - [Type Conversion](#type-conversion)
+ - [Printing](#printing)
+ - [Errors](#errors)
+- [Program Execution](#program-execution)
+
+
+
+## Source code representation
+
+Source code is Unicode text encoded in UTF-8. A "character" referenced in this document refers to a Unicode code point. Each code point is distinct: upper and lower case letters are different characters.
+
+The underscore character \_ (U+005F) is considered a "letter".
+
+```ebnf
+letter = unicode_letter | "_" .
+decimal_digit = "0" … "9" .
+octal_digit = "0" … "7" .
+hex_digit = "0" … "9" | "A" … "F" | "a" … "f" .
+```
+
+## Declarations and Scope
+
+A declaration binds an identifier to a value. An identifier is declared at the point it is first assigned. An assignment is considered a first assignment if the identifier hasn't already been previously declared in the current scope or any parent scopes.
+
+The scope of an identifier is the extent of source text in which the identifier denotes the specified value. Sentinel is lexically scoped using blocks.
+
+An identifier declared in a block may be redeclared in an inner block. While the identifier of the inner declaration is in scope, it denotes the value assigned in the inner declaration.
+
+## Blocks
+
+A block is a possibly empty sequence of statements within matching brace brackets.
+
+```ebnf
+Block = "{" StatementList "}" .
+StatementList = { Statement ";" } .
+```
+
+Blocks nest and affect scoping.
+
+In addition to explicit blocks in the source code, there are implicit blocks:
+
+1. The universe block encompasses all source text.
+2. Each program has a program block containing all source text for that program.
+3. Each "any", "all", and "for" statements is considered to be in its own implicit block. "if" does not create an implicit block.
+
+## Lexical Elements
+
+### Comments
+
+Comments are sections of source text used for documentation.
+
+```plaintext
+# Single line comment
+// Single line comment
+/* multi
+line
+ comment */
+```
+
+Three forms of comments are supported: two single-line forms and one multi-line form. A single-line comment begins with the token `//` or `#`. Everything between the starting token and the end of the line is ignored.
+
+A multi-line comment begins with the token `/*` and ends with the token `*/`. Everything between `/*` and `*/` is ignored.
+
+A comment may not start inside a string literal or inside another comment.
+
+A multi-line comment containing no newlines acts like a space.
+
+### Identifiers
+
+Identifiers name program entities such as rules, variables, and functions. An identifier is a sequence of one or more letters and digits. The first character in an identifier must be a letter.
+
+```ebnf
+identifier = letter { letter | unicode_digit } .
+```
+
+```sentinel
+a
+_a
+_A
+αβ
+```
+
+#### Pre-Declared Identifiers
+
+The following identifiers are implicitly declared in the [universe block](#blocks):
+
+```plaintext
+Constants:
+ false null true undefined
+
+Functions:
+ append bool delete error float int keys length print range string values
+```
+
+### Keywords
+
+The following keywords are reserved and may not be used as identifiers:
+
+```plaintext
+all
+any
+as
+break
+case
+continue
+default
+else
+empty
+filter
+for
+func
+if
+import
+map
+param
+return
+rule
+when
+```
+
+### Operators and Delimiters
+
+The following character sequences represent operators, delimiters, and other special tokens:
+
+```plaintext
++
+-
+*
+/
+%
++=
+-=
+*=
+/=
+%=
+==
+<
+>
+=
+!
+!=
+<=
+>=
+( )
+[ ]
+{ }
+,
+.
+:
+;
+and
+contains
+else
+in
+is
+matches
+not
+or
+xor
+```
+
+As a special case, `else` is both a keyword and operator, depending on the context. See [Else Operator](#else-operator) for more details.
+
+### Integer Literals
+
+An integer literal is a sequence of digits representing an integer constant. An optional prefix sets a non-decimal base: `0` for octal, `0x` or `0X` for hexadecimal. In hexadecimal literals, letters `a-f` and `A-F` represents values 10 through 15.
+
+Integers are signed 64-bit values (-9223372036854775808 to 9223372036854775807).
+
+```ebnf
+int_lit = decimal_lit | octal_lit | hex_lit .
+decimal_lit = ( "1" … "9" ) { decimal_digit } .
+octal_lit = "0" { octal_digit } .
+hex_lit = "0" ( "x" | "X" ) hex_digit { hex_digit } .
+```
+
+```
+42
+0600
+0xBadFace
+170141183460469231731687303715884105727
+```
+
+### Floating-point Literals
+
+A floating-point literal is a decimal representation of a floating-point constant. It has an integer part, a decimal point, a fractional part, and an exponent part. The integer and fractional part comprise decimal digits; the exponent part is an e or E followed by an optionally signed decimal exponent. One of the integer part or the fractional part may be elided; one of the decimal point or the exponent may be elided.
+
+Floating-point numbers are IEEE-754 64-bit floating numbers.
+
+```ebnf
+float_lit = decimals "." [ decimals ] [ exponent ] |
+ decimals exponent |
+ "." decimals [ exponent ] .
+decimals = decimal_digit { decimal_digit } .
+exponent = ( "e" | "E" ) [ "+" | "-" ] decimals .
+```
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+### String Literals
+
+String literals are character sequences between double quotes, as in "bar". Within the quotes, any character may appear except newline and unescaped double quote. The text between the quotes forms the value of the literal.
+
+A multi-character sequence beginning with a backslash encode values in various formats.
+
+Backslash escapes allow arbitrary values to be encoded as ASCII text. There are four ways to represent the integer value as a numeric constant: `\x` followed by exactly two hexadecimal digits; `\u` followed by exactly four hexadecimal digits; `\U` followed by exactly eight hexadecimal digits, and a plain backslash `\` followed by exactly three octal digits. In each case the value of the literal is the value represented by the digits in the corresponding base.
+
+After a backslash, certain single-character escapes represent special values:
+
+```plaintext
+\a U+0007 alert or bell
+\b U+0008 backspace
+\f U+000C form feed
+\n U+000A line feed or newline
+\r U+000D carriage return
+\t U+0009 horizontal tab
+\v U+000b vertical tab
+\\ U+005c backslash
+\" U+0022 double quote
+```
+
+The three-digit octal (\nnn) and two-digit hexadecimal (\xnn) escapes represent individual bytes of the resulting string; all other escapes represent the (possibly multi-byte) UTF-8 encoding of individual characters. Thus inside a string literal \377 and \xFF represent a single byte of value 0xFF=255, while ÿ, \u00FF, \U000000FF and \xc3\xbf represent the two bytes 0xc3 0xbf of the UTF-8 encoding of character U+00FF.
+
+A string value is a (possibly empty) sequence of bytes. Strings are immutable: once created, it is impossible to change the contents of a string.
+
+The length of a string s (its size in bytes) can be discovered using the built-in function `length`. An individual character (of type string) can be accessed by integer indices 0 through `length(s)-1`.
+
+```ebnf
+string_lit = `"` { unicode_value | byte_value } `"` .
+unicode_value = unicode_char | little_u_value | big_u_value | escaped_char .
+byte_value = octal_byte_value | hex_byte_value .
+octal_byte_value = `\` octal_digit octal_digit octal_digit .
+hex_byte_value = `\` "x" hex_digit hex_digit .
+little_u_value = `\` "u" hex_digit hex_digit hex_digit hex_digit .
+big_u_value = `\` "U" hex_digit hex_digit hex_digit hex_digit
+ hex_digit hex_digit hex_digit hex_digit .
+escaped_char = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | `"` ) .
+```
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+### Implicit Line Joining
+
+Expressions can be split over more than one line. For example:
+
+```sentinel
+a or
+b or # This is a comment
+c
+```
+
+Implicitly continued lines can have trailing comments. Blank continued lines are allowed.
+
+### Whitespace
+
+Whitespace is needed to separate tokens, but no distinction is made between the number and combination of whitespace characters. Whitespace characters can be space (U+0020), horizontal tabs (U+0009), carriage returns (U+000D), and newlines (U+000A).
+
+```sentinel
+a or b
+
+a or b
+
+a or
+ b
+
+rule { a or b }
+
+rule {
+ a or b }
+
+rule {
+ a or
+ b
+}
+```
+
+### Semicolons
+
+The formal grammar uses semicolons ";" as terminators in a number of productions.
+It is idiomatic Sentinel source to omit semicolons in most cases.
+
+A semicolon is automatically inserted into the token stream immediately after
+a line's final token if that token is:
+
+- An identifier
+- An integer, float, or string literal
+- The keyword `break`, `continue`, or `return`
+- The delimiter `)`, `]`, or `}`
+
+## Variables
+
+A variable is a storage location for holding a value.
+
+A variable has a dynamic type, which is the concrete type of the value assigned to the variable at run time. The dynamic type may vary during execution and change as a result of further assignments.
+
+A variable's value is retrieved by referring to the variable in an expression; it is the most recent value assigned to the variable. If a variable has not yet been declared (initially assigned), it is an error.
+
+```sentinel
+x = 7 // x is type int
+x = "foo" // x is now type string
+
+x = y // error if y is not previously assigned
+```
+
+## Undefined
+
+The value denoted by the keyword `undefined` represents undefined behavior or values. It can be created directly using the keyword `undefined`. It is also returned as a result of expressions in specified cases.
+
+`undefined` is a valid operand for any operations. Only `undefined or true` will result in true. All other operations result in `undefined`. An exception is if `undefined` is not reached as a result of short-circuit operations.
+
+```sentinel
+undefined OR true = true
+undefined OR false = undefined
+undefined OR undefined = undefined
+undefined AND true = undefined
+undefined AND false = undefined
+undefined AND undefined = undefined
+undefined XOR true = undefined
+undefined XOR false = undefined
+undefined XOR undefined = undefined
+
+// Short-circuit examples
+false OR true OR undefined = true
+false OR undefined OR true = true
+true AND false AND undefined = false
+true AND undefined AND false = undefined
+
+// Non-logical operators
+undefined + 5 = undefined
+-undefined = undefined
+!undefined = undefined
+```
+
+If the result of the main rule is `undefined`, it is treated as `false` but is indicative of erroneous logic.
+
+## Expressions
+
+### Operand
+
+Operands denote the elementary values in an expression. An operand may be a literal, an identifier denoting a variable, rule, or function, or a parenthesized expression.
+
+```ebnf
+Operand = Literal | OperandName | MethodExpr | "(" Expression ")" .
+Literal = BasicLit | CompositeLit | FunctionLit | RuleLit .
+BasicLit = int_lit | float_lit | string_lit .
+OperandName = identifier | QualifiedIdent.
+```
+
+### Primary Expressions
+
+Primary expressions are the operands for unary and binary expressions.
+
+```ebnf
+PrimaryExpr =
+ Operand |
+ PrimaryExpr Selector |
+ PrimaryExpr Index |
+ PrimaryExpr Slice |
+ PrimaryExpr Arguments |
+ PrimaryExpr ( "is empty" | "is not empty" ).
+
+Selector = "." identifier .
+Index = "[" Expression "]" .
+Slice = "[" [ Expression ] ":" [ Expression ] "]" |
+ "[" [ Expression ] ":" Expression ":" Expression "]" .
+Arguments = "(" [ ( ExpressionList | Type [ "," ExpressionList ] ) [ "..." ] [ "," ] ] ")" .
+```
+
+```sentinel
+x
+2
+(s + ".txt")
+f(3.1415, true)
+m["foo"]
+s[i : j + 1]
+obj.color
+f.p[i].x()
+x is empty
+x is not empty
+```
+
+### Null
+
+The reserved word `null` denote the singleton value `null`. Null represents the explicit absense of a value. Behavior of `null` within expressions is specified explicitly for each expression.
+
+### Booleans
+
+### Boolean Literals
+
+The reserved words `true` and `false` denote objects that represent the boolean values `true` and `false`, respectively. These are boolean literals.
+
+```ebnf
+BoolLit = "true" | "false" .
+```
+
+#### Boolean Expressions
+
+Boolean expressions are expressions that must result in a boolean value. Any other value type becomes the `undefined` value.
+
+```ebnf
+BoolExpr = Expression .
+```
+
+### List Literals
+
+A list literal denotes a list, which is an integer indexed collection of values.
+
+```ebnf
+ListLit = "[" [ ElementList [ "," ] ] "]" .
+ElementList = Element { "," Element } .
+Element = Expression | LiteralValue .
+```
+
+A list may contain zero or more values. The number of values in a list is its length.
+
+A list has a set of indices. An empty list has an empty set of indices. A non-empty list has the index set `{0...n - 1}` where `n` is the length of the list. Attempting to access a list using an index that is not a member of its set of indices results in the `undefined` value.
+
+### Map Literals
+
+A map literal denotes a map, which is an unordered group of elements indexed by a set of unique keys.
+
+```ebnf
+MapLit = "{" [ KeyedElementList [ "," ] ] "}" .
+KeyedElementList = KeyedElement { "," KeyedElement } .
+KeyedElement = Element ":" Element .
+```
+
+Keys can only be a boolean, numeric, or string type.
+
+The value of a non-existent key is the `undefined` value.
+
+### Function Literals
+
+A function literal represents a function.
+
+```ebnf
+FunctionLit = "func" Function .
+Function = Parameters FunctionBody .
+FunctionBody = Block .
+Parameters = "(" [ IdentifierList [ "," ] ] ")" .
+IdentifierList = identifier { "," identifier } .
+```
+
+```sentinel
+func(a, b) { return b }
+```
+
+Function literals are only allowed in the file scope. They may refer to variables defined in surrounding blocks.
+
+A function must terminate with a return statement. If a function has no meaningful return value, it should return `undefined`.
+
+### Rule Expressions
+
+A rule is an expression that is evaluated lazily and the result is memoized.
+
+If the optional "when" predicate is present, the rule is evaluated only when
+the "when" boolean expression results in true. If the predicate is false,
+the rule is not evaluated and returns true. The predicate is evaluated when
+the rule would be evaluated; it is also lazy and memoized in the same way.
+
+```ebnf
+RuleExpr = "rule" [ "when" BoolExpr ] "{" Expr "}" .
+```
+
+```sentinel
+rule { x is y }
+
+rule {
+ x == "value" or
+ y == "other"
+}
+
+rule when x is y { y > 42 }
+
+rule { map ["a", "b", "c"] as _, id {
+ { "id": id }
+}}
+```
+
+### Index Expressions
+
+A primary expression of the form `a[x]` denotes the element of a list or map indexed by x. The value x is called the index or key.
+
+For `a` of type map:
+
+- If `a` contains a key `x`, `a[x]` is the map value with key `x`.
+- If `a` does not contain key `x`, `a[x]` is the `undefined` value.
+
+For `a` of type list:
+
+- `x` must be an integer
+- `x` must be in the range `[-1 * length(a), length(a)-1]`
+- If `x` is contained in the set of indices of `a`, `a[x]` is the list element at index `x`. If `x` is negative, `a[x]` is equivalent to `a[length(a)+x]`.
+- If `x` is not contained in the set of indices of `a`, `a[x]` is the `undefined` value.
+
+For `a` of value `null`:
+
+- `a[x]` is the `undefined` value.
+
+Otherwise `a[x]` is an error.
+
+### Selectors
+
+For a primary expression x, the selector expression `x.f` denotes the field `f` of the value `x`. The identifier `f` is called the selector. The type of the selector expression is the type of the selector.
+
+As a special case, selectors can be [reserved words](#keywords) and keyword [operators](#operators-and-delimiters), but cannot be any other non-identifier element.
+
+Selectors are used to access data from [imports](#imports). The first primary expression `x` in `x.f` denotes the import name. The field `f` is the selector to access data from the import.
+
+Selectors may also be used to access map data with static keys. They are syntactic sugar over index expressions. `math.pi` is equivalent to `math["pi"]` and exhibit the same limitations and behavior as an index expression. Selectors cannot, however, be used to assign values to map data.
+
+Selectors on undefined result in undefined.
+
+```sentinel
+math.pi
+time.pst.hour
+```
+
+### Slice Expressions
+
+Slice expressions construct a substring or list from a string or list.
+
+The primary expression
+
+```sentinel
+a[low : high]
+```
+
+constructs a substring or list. The indices `low` and `high` select which elements of operand `a` appear in the result. The result has indices starting at 0 and length equal to `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+```
+
+For convenience, any of the indices may be omitted. A missing `low` index defaults to zero; a missing `high` index defaults to the length of the sliced operand:
+
+```sentinel
+a[2:] // same as a[2 : length(a)]
+a[:3] // same as a[0 : 3]
+a[:] // same as a[0 : length(a)]
+```
+
+The indices are in range if `0 <= low <= high <= length(a)`, otherwise they are out of range. If the indices are out of range at run time, the result is the `undefined` value.
+
+If `a` is the value `null`, the result is the `undefined` value.
+
+If `a` is any other value type, it is an error.
+
+### Calls
+
+Given an expression `f` where `f` is a function value:
+
+```sentinel
+f(a1, a2, … an)
+```
+
+calls `f` with arguments `a1, a2, ... an`. The type of the expression is the result of `f`. The arguments are evaluated left to right. Arguments are passed by value.
+
+### Operators
+
+```ebnf
+Expression = UnaryExpr | Expression binary_op Expression .
+UnaryExpr = PrimaryExpr | unary_op UnaryExpr .
+
+binary_op = logical_op | set_op | rel_op | add_op | mul_op | else_op .
+logical_op = "and" | "or" | "xor" .
+set_op = ["not"] ( "contains" | "in" ).
+rel_op = "==" | "!=" | "<" | "<=" | ">" | ">=" |
+ "is" | "is not" | "matches" | "not matches" .
+add_op = "+" | "-" .
+mul_op = "*" | "/" | "%" .
+else_op = "else" .
+unary_op = "+" | "-" | "!" | "not" .
+```
+
+#### Operator Precedence
+
+Unary operators have the highest precedence.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Binary operators of the same precedence associate from left to right. For instance, x / y _ z is the same as (x / y) _ z.
+
+### Arithmetic Operators
+
+Arithmetic operators apply to numeric values and yield a result of the same type when both operands are the same.
+
+Arithmetic operations between integer and floating-point types result in a floating-point value with the integer operand treated as a floating-point value for purposes of calculation.
+
+Arithmetic operations between numeric values (integer, floating-point) and non-numeric values (strings, lists) are not permitted.
+
+All five supported arithmetic operators (+, -, \*, /, %) apply to both integer and floating-point types; + also applies to strings.
+
+```plaintext
++ sum integers, floats, strings, lists
+* difference integers, floats
+* product integers, floats
+/ quotient integers, floats
+% remainder integers, floats
+```
+
+#### Integer operators
+
+For two integer values x and y, the integer quotient `q = x / y` and remainder `r = x % y` satisfy the following relationships:
+
+```sentinel
+x = q*y + r and |r| < |y|
+```
+
+with x / y truncated towards zero.
+
+```plaintext
+ x y x / y x % y
+ 5 3 1 2
+-5 3 -1 -2
+ 5 -3 -1 2
+-5 -3 1 -2
+```
+
+As an exception to this rule, if the dividend x is the most negative value for the int type of x (-9223372036854775808), the quotient `q = x / -1` is equal to x (and r = 0).
+
+If the divisor is a constant, it must not be zero. If the divisor is zero at run time, an error occurs.
+
+#### Integer overflow
+
+For signed integers, the operations `+`, `-`, and `*` may legally overflow and the resulting value exists and is deterministically defined by the signed integer representation, the operation, and its operands. No exception is raised as a result of overflow.
+
+#### Floating-point operators
+
+For floating-point numbers, +x is the same as x, while -x is the negation of x. The result of a floating-point division by zero is not specified beyond the IEEE-754 standard.
+
+#### String Concatenation
+
+Strings can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = "hi"
+y = "hello"
+x = x + ", " + y // "hi, hello"
+x += " and good bye" // "hi, hello and good bye"
+```
+
+String addition creates a new string by concatenating the operands.
+
+#### List Concatenation
+
+Lists can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = [1, 2]
+x = x + [2, 3] // [1, 2, 2, 3]
+x += [4] // [1, 2, 2, 3, 4]
+```
+
+List addition creates a new list by concatenating the operands.
+
+### Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+In any comparison, the two operands must be equivalent types. The only exception are integers and floats, which are considered numeric and may be compared. Any comparison between other non-matching types results in the `undefined` value.
+
+The equality operators `==`, `!=`, `is`, and `is not` apply to operands that are comparable. The ordering operators `<`, `<=`, `>`, and `>=` apply to operands that are ordered. The behavior of `is` with `==` and `is not` with `!=` is identical. These terms and the result of the comparisons are defined as follows:
+
+- Boolean values are comparable. Two boolean values are equal if they are either both true or both false.
+- Integer values are comparable and ordered, in the usual way.
+- Floating point values are comparable and ordered, as defined by the IEEE-754 standard.
+- An integer compared with floating point value treats the integer as the converted floating point value.
+- String values are comparable and ordered, lexically byte-wise.
+- Lists are comparable. Lists are equal if they are of equal length and their
+ corresponding elements are comparable and equal.
+- Maps are comparable. Maps are equal if they are of equal length and both their
+ corresponding keys and values are comparable and equal.
+
+If either operand is the `undefined` value, the result of the expression is the `undefined` value.
+
+### Emptiness Comparisons
+
+Checking the emptiness of an object can be achieved by using one of the emptiness expressions, `is empty` or `is not empty`. Although similar to [Comparison Operators](#comparison-operators) in that they yield a boolean value and read as though a comparison is taking place, both `is empty` and `is not empty` are evaluated as a multi-word expression.
+
+An emptiness comparison can only be performed against collections, strings or `undefined`, with the same rules as the [built-in length](/sentinel/functions/length) function.
+
+```sentinel
+"" is empty // true
+"foo" is empty // false
+[] is empty // true
+[1] is empty // false
+{} is empty // true
+{"a": "b"} is empty // false
+
+"" is not empty // false
+"foo" is not empty // true
+[] is not empty // false
+[1] is not empty // true
+{} is not empty // false
+{"a": "b"} is not empty // true
+
+undefined is empty // undefined
+undefined is not empty // undefined
+```
+
+### Logical Operators
+
+Logical operators apply to boolean values and yield a boolean result.
+
+Logical operators are evaluated left-to-right and perform short-circuit logic. The right-hand side is not guaranteed to be evaluated if a short-circuit can be performed.
+
+```plaintext
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+### Set Operators
+
+The set operators `contains` and `in` test for set inclusion for lists
+and maps, and substring inclusion for strings.
+
+Set operators may be negated by prefixing the operator with `not`: `not contains` and `not in`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+`contains` tests if the left-hand collection contains the right-hand value. For lists, this tests equality of any value. For maps, this tests equality of any key. For strings, this tests for substring existence.
+
+`in` tests if the right-hand collection contains the left-hand value. The behavior is equivalent to `contains`.
+
+The collection must be a list, map, or string. If it is the `undefined` value, the result is the `undefined` value. For any other value, the result is an error.
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+
+"test" contains "est" // true
+"test" contains "best" // false
+"test" in "testing" // true
+"best" in "testing" // false
+```
+
+### Matches Operator
+
+The matches operator tests if a string matches a regular expression.
+
+The matches operators may be negated by prefixing the operator with `not`: `not matches`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+The left-hand value is the string to test. The right-hand value is a string value representing a regular expression.
+
+The syntax of the regular expression is the same general syntax used by Python, Ruby, and other languages. More precisely, it is the syntax accepted by RE2. The regular expression is not anchored by default; any anchoring must be explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+If either operand is `undefined`, the result is the `undefined` value. For any other non-string value, the result is an error.
+
+### Else Operator
+
+Else operators can be used to provide a default value for an expression that may evaluate to the `undefined` value. Else operators return their left-hand value unless it is `undefined`, otherwise they return their right-hand value.
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else null
+```
+
+### Quantifier Expressions (any, all, filter, map)
+
+Quantifiers are expressions that apply a predicate to a collection (list or map). The purpose of the quantifier is to produce a result based on its particular type.
+
+`any` and `all` expressions are existential and universal quantifiers, respectively. `any` expressions are equivalent to a chain of `or` and `all` expressions are equivalent to a chain of `and`. Both expressions implement short-circuiting equivalent to logical operators.
+
+The `map` expression is an apply-to-all quantifier and returns a new list for all values in the input collection. The output list will be the same length as the input collection.
+
+The `filter` expression is a subset quantifier and returns the subset of all values in the input collection for which the supplied predicate applies. This will be zero or more elements, up to the length of the input.
+
+The body of a quantifier expression is a boolean expression.
+
+`any` returns the boolean value `true` if any value in the collection expression results in the body expression evaluating to `true`. If the body expression evaluates to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression result in the body expression evaluating to `true`. If any value in the collection expression result in the body expression evaluating to `false`, the all expression returns `false`.
+
+For empty collections, `any` returns false and `all` returns `true`.
+
+`map` always returns a list, regardless of if the input collection is a list itself; maps (as in the data type) also return lists when processed through `map`. Each element is the result of the supplied expression body.
+
+`filter` returns a list or map, the same type as its input collection. Only the elements for which the expression body evaluated to `true` will be returned. If an iteration of the expression body results in `undefined`, the entire result set is `undefined`.
+
+When the collection is a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When the collection is a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+QuantExpr = QuantOp Expression "as" [ identifier "," ] identifier "{" BoolExpr "}" .
+QuantOp = "all" | "any" | "filter" | "map" .
+```
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" } // true if every iterations is true
+any group.tasks as t { t.driver is "vmware" } // true if any iteration is true
+map group.tasks as t { { "driver": t.driver } } // [{"driver": "vmware"}, ...]
+filter group.tasks as t { t.driver is "vmware" } // subset of tasks that is true
+```
+
+## Statements
+
+### Expression Statements
+
+Function call expressions may exist in the statement context.
+
+```ebnf
+ExpressionStmt = Expression .
+```
+
+### Assignments
+
+Assignments set the value of an expression to the value of another expression.
+
+```ebnf
+Assignment = AssignExpr assign_op Expression .
+AssignExpr = identifier | IndexExpr .
+assign_op = [ add_op | mul_op ] "=" .
+```
+
+The assignment `x op= y` is equivalent to `x = x op (y)` for supported values of `op`.
+
+```sentinel
+a = 1
+b[12] = 42
+c["key"] = "value"
+
+a *= 12
+b[12] += 8
+```
+
+Assignments to lists and maps can be carried out using [index expressions](#index-expressions), with the operands of the index expression being evaluated alongside the right hand side expression in a right-to-left (RHS, LHS) order.
+
+In addition to the rules detailed in the section describing their use, the following rules apply when assigning to maps or lists using index expressions:
+
+- The [variable](#variables) must exist and be a list or map. Attempts to use an index assignment on an unknown variable, or a variable that not a list or map, results in a runtime error.
+- Assigning to a list or map index that already exists overwrites that index's data with evaluated right hand side's value.
+- Assigning to an unknown key in a map creates a key for that map with the evaluated right hand side's value assigned to that key.
+- Attempting to assign a value to a list index that is out of range results in a runtime error.
+
+### If Statements
+
+If statements specify the conditional execution of two branches according to the value of a boolean expression. If the expression evaluates to true, the "if" branch is executed, otherwise, if present, the "else" branch is executed.
+
+```ebnf
+IfStmt = "if" BoolExpr Block [ "else" ( IfStmt | Block ) ] .
+```
+
+```sentinel
+if x < y {
+ return x
+} else if x > z {
+ return z
+} else {
+ return y
+}
+```
+
+### Case Statements
+
+Case statements specify a selection control mechanism to conditionally execute a branch based on expression equality.
+
+Each clause that wishes to provide an expression must use the "when" keyword. Multiple expressions can be provided, separated with a comma (",").
+
+There can be one, optional, "else" clause within a `case` statement. The "else" clause is executed if all other clauses failed to run.
+
+The clauses are evaluated in the order they are listed, with the first successful evaluation being executed.
+
+A `case` statement can optionally provide an expression. If no expression is provided, it is the equivalent of writing `case true {`.
+
+```ebnf
+CaseStmt = "case" [ Expression ] "{" { CaseWhenClause } "}" .
+CaseWhenClause = CaseWhenCase ":" StatementList .
+CaseWhenCase = "when" Expression { "," Expression } | "else" .
+```
+
+```sentinel
+case x {
+when y, z:
+ return true
+else:
+ return false
+}
+
+case {
+when x > 42:
+ return true
+else:
+ return false
+}
+```
+
+### For Statements
+
+A `for` statement specifies repeated execution of a block.
+
+The expression must evaluate to a list or a map.
+
+When iterating over a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When iterating over a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+ForStmt = "for" Expressions "as" [ identifier "," ] identifier Block .
+```
+
+```sentinel
+for [1, 2, 3] as v { count += v } // basic sum
+
+for [1, 2, 3] as idx, v {
+ if idx > 1 { count += v }
+}
+
+data = { "a": 12, "b": 32 }
+for data as k { count += data[k] } // sum map values
+for data as k, v { count += v }
+```
+
+### Break Statements
+
+A `break` statement terminates execution of the innermost `for` statement
+within the same function.
+
+```ebnf
+BreakStmt = "break" .
+```
+
+```sentinel
+// Will only print "1"
+for [1, 2, 3] as v {
+ print(v)
+ break
+}
+```
+
+### Continue Statements
+
+A `continue` statement begins the next iteration of the innermost `for` loop.
+The `for` loop must be within the same function.
+
+```ebnf
+ContinueStmt = "continue" .
+```
+
+```sentinel
+// Will print 1, 3
+for [1, 2, 3] as v {
+ if v == 2 {
+ continue
+ }
+
+ print(v)
+ break
+}
+```
+
+### Return Statements
+
+A return statement in a function F terminates the execution of F and provides the result value.
+
+```ebnf
+ReturnStmt = "return" Expression .
+```
+
+A function must terminate with a return statement.
+
+The return statement can be invoked at any time during a function to terminate execution.
+
+## Imports
+
+An import adds an assignment to the global scope to external definitions.
+
+The import declarations must only appear at the top of the source file, before any other statements. Comments may appear before imports.
+
+```ebnf
+ImportDecl = "import" Name ["as" identifier] .
+Name = string_lit .
+```
+
+An import name and identifier must be distinct. Two names may not repeated even if they're declared with different identifiers.
+
+If an identifier is not specified, the identifier is the name of the import itself.
+
+An import is not a valid value. The identifier representing the import may not be used as an expression, such as in assignment statements or call expressions.
+
+Values in imports are not assignable directly via their selectors. Function calls may be used to alter the state of the external data represented by the import. Whether or not this is supported is specific to the import implementation. The exception to this assignment restriction is the memoized data returned by a call to an import (see below).
+
+Data returned by imports can be memoized, meaning that data is returned by the import in the form of a map which is then used as much as possible without having to return to the import for more data. For example, `t = time.now` returns a map so that `t.hour` will be able to be looked up without having to go back to the import for that data.
+
+Data returned by imports may be callable, meaning that methods may be called on the memoized data returned by an import. For example, given `t = time.now`, `t.after(some_previous_time)` is valid, with the receiver data being set to the local memoized data stored in `t`. It is a valid case to accept modifications to the receiver data (example: assignment to the memoized data via index expressions), as long as all data in in the receiver continues to be string-keyed. It is an invalid case to accept receiver data with non-string-typed keys. Methods may also alter the contents of receiver data so that it is different after the call is finished.
+
+As with methods, imports may also have callable keys where the data may not be memoized. Example: in the event of `v = foo.bar`, if `v.baz` is not included in the memoized data, a call will be made to the import to fetch it. The same rules and restrictions to receiver data apply to callable keys as do to method calls.
+
+The support for callable methods and non-memoized keys on data returned by imports is specific to the import implementation.
+
+The handling of import loading is specific to the runtime implementation.
+
+Implicit imports may be added by the runtime, for example for standard library definitions. An explicit import of the same name should override a matching implicit import. For example, if "time" is an implicit import and the program specifies `import "time" as cal`, then only `cal` is defined.
+
+```sentinel
+import "time"
+import "math" as m
+```
+
+## Parameters
+
+```ebnf
+ParamDecl = "param" identifier [ "default" Expression ]
+```
+
+A parameter is a way for a policy to describe variables that are expected to be supplied by the calling application, in addition to any default value.
+
+Parameter declarations must appear at the top of the source file, following imports.
+
+Like imports, comments may appear before parameters; this is mainly pertinent when the policy is not importing anything, which would make parameters the first declarations that appear in a policy.
+
+A parameter declaration describes a valid variable that is added to the scope of a policy at runtime. This variable has the same properties and rules as other variables assigned during the course of policy evaluation as defined by the specification. This includes having a dynamic type and being able to be re-assigned during execution.
+
+Parameters may only be strings, integers, floating point numbers, booleans, lists, or maps. More complex types such as rules or functions are not allowed.
+
+A parameter can specify a default value by adding one in the declaration via use of the "default" keyword, and supplying a literal for the default value. The literal for a default is a very limited expression, comprising of:
+
+- For strings, a simple string literal;
+- For integers and floating-point numbers, either a literal denoting the value, or a unary expression with the literal, and the operators - or +;
+- For booleans, the pre-declared identifiers true or false;
+- For lists and maps, their respective literals composed of values and keys (for maps) of the above values only.
+
+Any other type of expression or identifier is not allowed.
+
+A parameter that does not have a default value defined is required by the policy. Evaluating a policy with a required parameter that has not been supplied at execution results in a runtime error.
+
+Parameters must not conflict with imports or any other identifiers currently defined in the global scope, including any reserved or pre-declared identifiers. Attempting to assign a parameter to an existing value results in a runtime error.
+
+```sentinel
+import "time"
+
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+param time default "11:00" // error, conflicts with "time" import
+param undefined // error, reserved identifier
+```
+
+## Built-in Functions
+
+Built-in functions are predeclared. They are called like any other function.
+
+### Length
+
+The built-in function `length` returns the length of a collection of string.
+
+The length of a string is the number of bytes in the string. It is not necessarilly the number of characters.
+
+The length of a collection is the number of elements in that collection.
+
+The length of `undefined` is `undefined`.
+
+### Collections
+
+#### List Append
+
+The built-in function `append` appends a value to the end of a list in-place.
+
+Appending to a non-list value results in an immediate `fail`.
+
+The return value of `append` is always the `undefined` value.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+#### Map Delete
+
+The built-in function `delete` deletes elements from a map by key. The map is modified in-place.
+
+Deleting a key that does not exist does nothing.
+
+Calling delete for a non-map value results in an immediate `fail`.
+
+The return value of `delete` is always the `undefined` value.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
+
+#### Keys and Values
+
+The built-in function `keys` and `values` return the keys and values of a map, respectively. The return value is a list. Both functions return values in an unspecified order.
+
+The `keys` or `values` of `undefined` is `undefined`.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+### Range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it defaults to `1`.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4]
+range(1, 5, 2) // [1,3]
+range(0, -3, -1) // [0,-1,-2]
+```
+
+### Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a value of that type according to the rules below.
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
+
+### Printing
+
+The built-in function `print` can be used to output formatted debug information. The runtime may omit print function calls depending on its execution mode. The runtime may choose where the output of a print function goes.
+
+`print` is a variadic function, accepting one or more values to be printed; values are separated by a single whitespace character (`" "`).
+
+The return value of `print` is always `true` to allow it to be used in boolean expressions.
+
+```sentinel
+print("hello") // "hello"
+print("hello", "world") // "hello world"
+print("The", "number", "is", 42) // "The number is 42"
+print([1, 2, 3]) // "[1, 2, 3]"
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // "false"
+```
+
+### Errors
+
+The built-in function `error` is equivalent to `print` but immediately causes execution to halt and the main rule to evaluate to `false`. The program execution is considered to have failed.
+
+## Program Execution
+
+Program execution begins by evaluating the source top to bottom. If evaluation is successful, the `main` rule is evaluated and its result is returned. Execution can be interrupted at any time by an error, which can be called explicitly or implicitly through illegal or undefined behavior.
+
+---
+
+> The content of this page is licensed under the [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
+>
+> Based on [The Go Programming Language Specification](https://golang.org/ref/spec) licensed under [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/language/undefined.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/language/undefined.mdx
new file mode 100644
index 0000000000..dc51531783
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/language/undefined.mdx
@@ -0,0 +1,97 @@
+---
+page_title: Sentinel Language - Undefined
+sidebar_current: docs-language-undefined
+description: >-
+ The value `undefined` is a special value representing undefined behavior or an
+ unknown value. Any operation on an `undefined` value results in `undefined`.
+layout: docs
+---
+
+# Language: Undefined
+
+The value `undefined` is a special value representing undefined behavior
+or an unknown value.
+
+Undefined is not an error because there are situations where the undefined
+value can be safely ignored and the language also provides construts for
+recovering from an undefined value.
+
+The undefined value can be created manually with `undefined`. In most cases,
+undefined is created by accessing a non-existent list element or value.
+The undefined value is created in a number of other circumstances. The documentation
+will note when an undefined value may be created.
+
+Almost any operation with `undefined` results in `undefined`. For example,
+performing math on undefined, attempting to call a function that is undefined,
+etc.
+
+## Main and Undefined
+
+If the value `undefined` is returned from the top-level `main` rule, then
+the policy is considered `false`. However, the runtime provides mechanisms
+for the host application to determine the policy failure was caused by
+an undefined value and report that to the user.
+
+The `main` rule returning the undefined value should be considered a logical
+error in most cases and should probably be corrected by the policy writer.
+
+## Debugging Undefined
+
+When an `undefined` value is first created (either explicitly or implicitly),
+the runtime will record the position where the undefined was created. This
+location can be used to find logic that could be erroneous.
+
+## Boolean Logic and Undefined
+
+Boolean logic is one way to safely recover from `undefined`. If boolean
+logic can safely determine a result despite an undefined value, that result
+will be returned.
+
+For example: `undefined or true` will result in `true`, because no matter
+what actual value `undefined` could've been if the policy behaved properly,
+the boolean expression would still be true.
+
+Another scenario where `undefined` can be safely ignored is short-circuit
+logic with `and`. `false and undefined` will result in `false`.
+
+The full table of logical operations on `undefined` is below:
+
+```sentinel
+undefined or true = true
+undefined or false = undefined
+undefined or undefined = undefined
+undefined and true = undefined
+undefined and false = undefined
+undefined and undefined = undefined
+undefined xor true = undefined
+undefined xor false = undefined
+undefined xor undefined = undefined
+
+// Short-circuit examples
+false or true or undefined = true
+false or undefined or true = true
+true and false and undefined = false
+true and undefined and false = undefined
+```
+
+## Else Expressions
+
+The `else` expression allows explicit recovery from undefined and is useful
+for setting default values.
+
+`else` is an operator where the left and right hand side are values. If the
+left-hand value is `undefined`, the right-hand value is returned. Otherwise,
+the left-hand value is returned.
+
+Examples:
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else "default"
+
+// In more complex scenarios
+
+foo() else 42 == 12
+a = config.value else "default"
+```
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/language/values.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/language/values.mdx
new file mode 100644
index 0000000000..f932480340
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/language/values.mdx
@@ -0,0 +1,210 @@
+---
+page_title: Sentinel Language - Values
+sidebar_current: docs-language-values
+description: Values are the data that Sentinel policies operate on.
+layout: docs
+---
+
+# Language: Values
+
+Values are the _data_ that Sentinel policies operate on. You can create this
+data yourself, for example by just typing the number `42`, or you may access
+this data from an external source to make policy decisions.
+
+Values have a _type_, which determines what kind of operations can be performed
+on the data. Example types are booleans (true and false), numbers,
+and strings (text).
+
+This page documents all the available value types. You can also
+[convert between types](#type-conversion).
+
+## Boolean
+
+A boolean is a value that is either true or false.
+
+A boolean value is created literally with `true` or `false`.
+
+As a policy language, booleans are central to the behavior of Sentinel.
+Booleans are used as conditions in [if statements](/sentinel/language/conditionals),
+are the result of [rules](/sentinel/language/rules), and more. The ultimate
+result of a Sentinel policy is true or false.
+
+## Integer
+
+An integer is a whole number.
+
+An integer is a 64-bit value. This means it can represent numbers from
+-9,223,372,036,854,775,808 to 9,223,372,036,854,775,808.
+
+Integers are created by typing them out literally with no
+separators (such as a comma). An optional prefix can set a non-decimal base:
+`0` for octal, and `0x` for hexadecimal. In hexadecimal literals, letters
+`a-f` and `A-F` represent values 10 to 15.
+
+Example integers are shown below:
+
+```sentinel
+42
+0600
+0xBadFace
+170141183460469
+```
+
+Integers are used for math and numerical comparison.
+
+## Float
+
+A float is a number with a decimal point. It has an integer part, a decimal
+point, a fractional part, and optionally an exponent part. Example
+floats are shown below:
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+Floats are IEEE-754 64-bit floating point numbers.
+
+## String
+
+Strings are text values.
+
+Strings are created by wrapping text in double quotes, such as `"hello"`.
+Within the quotes, any character may appear except newline and an unescaped
+double quote. The text between the quotes is the value.
+
+Because Sentinel policies must be UTF-8 encoded text, strings themselves are
+UTF-8 encoded text. This means you can put any value UTF-8 character into
+a string, such as `"日本語"`.
+
+You can have a string with a literal double-quote by _escaping_ it with
+a backslash. For example: `"they said \"hello\""` turns into the value
+`they said "hello"`.
+
+Backslash escapes can be used for much more than only escaping a double quote.
+Newlines are `\n`, a literal backslash is `\\`, and many more. The full list
+of available backslash escapes can be found
+[in the language specification](/sentinel/language/spec#string-literals).
+
+Example strings:
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+Strings do not support indexing, however it is possible to achieve a similar
+result through the combination of the [strings](/sentinel/imports/strings)
+standard import and list indexing.
+
+```sentinel playground
+import "strings"
+
+asciistr = "Hello, world!"
+utf8str = "日本語"
+
+utf8split = rule {
+ strings.split(utf8str, "")[2] == "語"
+}
+
+asciisplit = rule {
+ strings.split(asciistr, "")[5] == ","
+}
+
+main = rule { utf8split and asciisplit }
+```
+
+Strings support [slicing](/sentinel/language/slices) to efficiently create
+substrings.
+
+## List
+
+See [Lists](/sentinel/language/lists).
+
+## Map
+
+See [Maps](/sentinel/language/maps).
+
+## Rule
+
+See [Rules](/sentinel/language/rules).
+
+## Function
+
+See [Functions](/sentinel/language/functions).
+
+## Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a
+value of that type. Some examples are shown below followed by a list of exact
+rules for type conversion.
+
+```sentinel
+int(42) // 42
+int("42") // 42
+int(42.8) // 42
+int(true) // 1
+
+float(1.2) // 1.2
+float(1) // 1.0
+float("4.2") // 4.2
+float(true) // 1.0
+
+string("foo") // "foo"
+string(88) // "88"
+string(0xF) // "15"
+string(true) // "true"
+
+bool("true") // true
+bool(1) // true
+bool(-1) // true
+bool(0.1) // true
+bool("false") // false
+bool(0) // false
+```
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/language/variables.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/language/variables.mdx
new file mode 100644
index 0000000000..7516890212
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/language/variables.mdx
@@ -0,0 +1,99 @@
+---
+page_title: Sentinel Language - Variables
+sidebar_current: docs-language-vars
+description: Variables store values that can be used later.
+layout: docs
+---
+
+# Language: Variables
+
+Variables store values that can be used later.
+
+Most notably, a variable assignment of `main` is required for all
+Sentinel policies. This is the main [rule](/sentinel/language/rules) that
+is executed to determine the result of a policy. The `main` rule may
+use other variables that are assigned in the policy.
+
+Example:
+
+```sentinel
+a = 1
+b = a + 1
+```
+
+In this example, two variables are assigned: `a` and `b`. `a` is given
+the value of "1" and `b` uses the `a` to calculate its own value.
+
+## Syntax
+
+The syntax for assigning a variable is:
+
+```sentinel
+IDENTIFIER = VALUE
+```
+
+On the left of the equal sign is an _identifier_. This is a name for your
+variable and will be how you reference it later. An identifier is any
+combination of letters and digits, but must start with a letter. An
+underscore `_` is also a valid letter.
+
+On the right is any valid Sentinel value or expression. A value is a
+literal value such as a number or string. An expression is some computed
+value such as doing math, calling a function, etc.
+
+## Assignment and Reassignment
+
+A variable is _assigned_ when it is given a value. You can also _reassign_
+variables at any time by setting it to a new value. The new value takes
+effect for any subsequent use of that variable. A variable can be reassigned
+to a different type.
+
+For example:
+
+```sentinel
+a = 1 // a = 1
+b = a // b = 1
+a = "value" // a = "value", b = 1
+c = a // c = "value", b = 1
+```
+
+In the above example you can see that the variables are set and reassigned.
+Notice that the value of a variable is the _current_ value, and that reassigning
+a variable only affects _future_ uses of that variable. You can see this with
+`c` and `b` being different values.
+
+## Unassigned Variables
+
+Using a variable that is _unassigned_ is an error.
+
+In the example below, the first line would result in the policy erroring.
+Sentinel is executed top-down and the value of `c` is not available yet
+on the first line.
+
+```sentinel
+a = c // Error!
+c = 1
+```
+
+## Type Conversion
+
+While a topic more related to [values][lang-values], variables can be assigned
+(or-reassigned) a different value type via type conversion.
+
+[lang-values]: /sentinel/language/values
+
+```sentinel
+s = "1.1"
+a = int(s) // a = 1
+a = float(s) // a = 1.1
+
+a = 1
+s = string(a) // s = "1"
+```
+
+For more details, see the type conversion sections in the
+[values][lang-values-type-conversion] page, or the [Sentinel Language
+Specification][lang-spec-type-conversion].
+
+[lang-values-type-conversion]: /sentinel/language/values#type-conversion
+[lang-spec-type-conversion]: /sentinel/language/spec#type-conversion
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/nomad.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/nomad.mdx
new file mode 100644
index 0000000000..a105b0337d
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/nomad.mdx
@@ -0,0 +1,51 @@
+---
+page_title: Nomad and Sentinel
+sidebar_current: docs-app-nomad
+description: >-
+ Nomad Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies can currently execute on job
+ submission (creation, update).
+layout: docs
+---
+
+# Nomad
+
+Nomad is a simple, flexible scheduler and workload orchestrator.
+[Nomad Enterprise](https://www.hashicorp.com/products/nomad/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies can currently execute on job submission (creation, update).
+
+Sentinel policies have full access to the job structure. This allows the
+Sentinel policy to control behavior based on any attribute within a job,
+such as the driver, resource requests, network configuration, volume
+configuration, and more. The information that Sentinel policies have access
+to will expand over time.
+
+Nomad fully supports all [enforcement levels](/sentinel/concepts/enforcement-levels).
+For soft mandatory policies, the `sentinel-override` capability must be
+available on the user's ACL policy to allow override. Overrides are always
+logged.
+
+The Nomad integration with Sentinel is documented in depth in the
+[Nomad Enterprise documentation](https://www.nomadproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Only allow Docker-based jobs.**
+
+```sentinel
+# Test policy only allows Docker based tasks
+main = rule { all_drivers_docker }
+
+# all_drivers_docker checks that all the drivers in use are Docker
+
+all_drivers_docker = rule {
+ all job.task_groups as tg {
+ all tg.tasks as task {
+ task.driver is "docker"
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/terraform.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/terraform.mdx
new file mode 100644
index 0000000000..5922fd0cfb
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/terraform.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Terraform and Sentinel
+sidebar_current: docs-app-terraform
+description: >-
+ Sentinel can be used with Terraform and Terraform Enterprise to enforce policy
+ on Terraform configurations, states, and plans.
+layout: docs
+---
+
+# Terraform
+
+[Terraform Enterprise](https://www.hashicorp.com/products/terraform/) uses Sentinel to enforce
+policy on [Terraform](https://www.terraform.io) configurations, states, and plans.
+
+The Sentinel integration with Terraform runs within
+[Terraform Enterprise](https://www.hashicorp.com/products/terraform/)
+after a `terraform plan` and before a `terraform apply`. The policies
+have access to the created plan, the state at the time of the plan,
+and the configuration at the time of the plan.
+
+The Terraform integration with Sentinel is documented in depth in the [Terraform Enterprise documentation](https://www.terraform.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show basic examples.
+
+### Examples
+
+**Example: All AWS instances must have a tag**
+
+```sentinel
+import "tfplan"
+
+main = rule {
+ all tfplan.resources.aws*instance as *, instances {
+ all instances as \_, r {
+ (length(r.applied.tags) else 0) > 0
+ }
+ }
+}
+```
+
+**Example: Only allow GCP instance sizes smaller than `n1-standard-16`**
+
+```sentinel
+import "tfplan"
+
+allowed_machine_types = [
+ "n1-standard-1",
+ "n1-standard-2",
+ "n1-standard-4",
+ "n1-standard-8",
+]
+
+main = rule {
+ all tfplan.resources.google_compute_instance as _, instances {
+ all instances as _, r {
+ r.applied.machine_type in allowed_machine_types
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/vault.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/vault.mdx
new file mode 100644
index 0000000000..6db2f8af22
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/vault.mdx
@@ -0,0 +1,64 @@
+---
+page_title: Vault and Sentinel
+sidebar_current: docs-app-vault
+description: >-
+ Vault Enterprise uses Sentinel to augment the built-in policy system to
+ provide Role Governing Policies (RGPs) and Endpoint Governing Policies (EGPs)
+ to enable complex, flexible policies across identities and endpoints.
+layout: docs
+---
+
+# Vault
+
+[Vault Enterprise](https://www.hashicorp.com/products/vault/) uses Sentinel
+to augment the built-in policy system to provide Role Governing Policies (RGPs)
+and Endpoint Governing Policies (EGPs) to enable complex, flexible policies
+across identities and endpoints.
+
+**Role Governing Policies (RGPs)** are Sentinel policies that are tied to
+particular tokens, Identity entities, or Identity groups. They have access to
+a rich set of controls across various aspects of Vault. These are evaluated
+whenever a token they're attached to is used.
+
+**Endpoint Governing Policies (EGPs)** are Sentinel policies that are tied to
+particular paths instead of tokens. They have access to as much request
+information as possible, but they can take effect even on unauthenticated
+paths, such as login paths.
+
+The Vault integration with Sentinel is documented in depth in the
+[Vault Enterprise documentation](https://www.vaultproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Endpoint policy that requires MFA authentication from a corporate network.**
+
+```sentinel
+import "sockaddr"
+
+# We expect logins to come only from our private IP range
+cidrcheck = rule {
+ sockaddr.is_contained(request.connection.remote_addr, "10.20.0.0/16")
+}
+
+# Require Ping MFA validation to succeed
+ping_valid = rule {
+ mfa.methods.ping.valid
+}
+
+main = rule when request.path is "auth/ldap/login" {
+ ping_valid and cidrcheck
+}
+```
+
+**Example: Endpoint policy that disallows tokens created before a certain time.**
+
+```sentinel
+import "time"
+import "strings"
+
+main = rule when not strings.has_prefix(request.path, "auth/ldap/login") {
+ time.load(token.creation_time).unix > time.load("2017-09-17T13:25:29Z").unix
+}
+```
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/writing/basic.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/writing/basic.mdx
new file mode 100644
index 0000000000..b62533466e
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/writing/basic.mdx
@@ -0,0 +1,113 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-basic
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Policy Basics
+
+Sentinel policies are easy to write while still supporting advanced constructs
+for creating complex policies. This page will explain the basics of writing
+Sentinel policies to get started. You don't need any prior Sentinel knowledge,
+but we do recommend reading the
+[getting started guide](/sentinel/intro/getting-started/first-policy) and
+[language guide](/sentinel/language) after this.
+
+Sentinel policies are text files written using the [Sentinel language](/sentinel/language).
+The policies are evaluated top-to-bottom. The value of `main` after execution
+determines whether a policy passes or fails.
+
+## The Simplest Policy
+
+Sentinel only requires that a policy have a `main` variable that evaluates to
+a boolean value.
+
+A valid example is shown below:
+
+```sentinel playground
+main = 10 > 5
+```
+
+This type of minimal policy is not purely academic. In practice, simple
+policies can often be reduced to a single line logical statement resulting
+in `true` or `false`. However, the expression is usually wrapped in a
+[rule](/sentinel/language/rules) for testing reasons.
+
+You can verify Sentinel will execute this minimal policy using the CLI:
+
+```
+$ sentinel apply minimal.sentinel
+Pass
+```
+
+## Logical Expressions
+
+Policy is at its core a set of logic: you can or can not perform some action
+under a certain set of circumstances. Those circumstances are logical
+expressions. Therefore, Sentinel policies primarily translate into logical
+expressions.
+
+Detailed documentation on boolean expressions is
+[available in the language guide](/sentinel/language/boolexpr).
+
+A simple numerical comparison was seen in the first example on this page.
+Sentinel also provides inclusion operators such as `contains`, `any`, `all`, and
+more. Sentinel allows some operators to have aliases to promote readability
+while remaining programmer-familiar, such as `==` which can equivalently be `is`.
+
+The example below verifies that all numbers in a list are even:
+
+```sentinel playground
+numbers = [6, 22, 8, 4, 12]
+main = all numbers as n { n % 2 is 0 }
+```
+
+## Variables
+
+A policy will very often use variables. Applications such as
+[Nomad](https://www.nomadproject.io) inject variables into the global
+scope of a policy for making policy decisions. For example, Nomad injects
+the job that is being run into the policy scope. Knowing how to use
+variables is critical to effectively using Sentinel.
+
+Detailed documentation on how to define and access variables is
+[available in the language guide](/sentinel/language/variables).
+
+Variables can be defined and used explicitly. For example:
+
+```sentinel playground
+value = 10
+main = value > 5
+```
+
+But they may also be introduced implictly by the host system. Nomad
+injects `job` into policies to describe the job that is being run. The
+policy below is a valid policy that requires a job have two task groups.
+Notice that `job` is not defined anywhere. It is implicitly inserted by
+the host application (in this case Nomad). Refer to the application you're
+writing policy for to determine if it implicitly inserts values.
+
+```json sentinel
+{
+ "policy": "main = length(job.task_groups) is 2",
+ "globals": {
+ "job": {
+ "task_groups": ["foo", "bar"]
+ }
+ }
+}
+```
+
+## And more!
+
+The Sentinel language supports many more features such as functions,
+loops, and more. You can learn about all of this in the
+[complete language guide](/sentinel/language).
+
+The other pages in the [writing policy](/sentinel/writing)
+will cover other information you need to know about writing Sentinel
+policies that isn't simply a language reference.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/writing/debugging.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/writing/debugging.mdx
new file mode 100644
index 0000000000..7dc284d222
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/writing/debugging.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-debugging
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Debugging
+
+As policies become more complex, it becomes harder to understand exactly why
+a policy may be behaving differently than you expect.
+
+Prior to actively debugging, there are multiple best practices we recommend
+following. We recommend breaking your policy down into a set of
+[rules](/sentinel/writing/rules). This will make it easier to understand
+[traces](/sentinel/writing/tracing) and make it easier to
+[test](/sentinel/writing/testing) your policies.
+This should help to debug policies up to a significant complexity.
+
+## Print Logging
+
+The built-in [print function](/sentinel/language/logging-errors#print)
+can be used to log data. The print output is only shown during failures
+or when [tracing is explicitly enabled](/sentinel/writing/tracing).
+
+The `print` function outputs each argument, which can be of any type, converted
+to a human-friendly string format. Arguments are separated with a single space.
+
+Example:
+
+```sentinel playground
+value = 42
+print("the number is", value) // the number is 42
+
+object = { "foo": false }
+print(object) // { "foo": false }
+
+main = rule { true }
+```
+
+The `print` function returns the value `true` so that it can also be
+used within rule expressions. Note that the `print` function should be
+used at the beginning of statements otherwise they may never be
+reached. This is due to internal performance techniques within Sentinel
+like [short-circuiting](https://en.wikipedia.org/wiki/Short-circuit_evaluation).
+
+Example:
+
+```sentinel playground
+// rule_a and rule_b will evaluate to false
+rule_a = rule { print("rule_a is printed") and false } // "rule_a is printed"
+rule_b = rule { false and print("rule_b is printed") } // Nothing is printed
+
+// rule_c and rule_d will evaluate to true
+rule_c = rule { print("rule_c is printed") or true } // "rule_c is printed"
+rule_d = rule { true or print("rule_d is printed") } // Nothing is printed
+
+main = rule { rule_a or rule_b or rule_c and rule_d }
+```
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/writing/imports.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/writing/imports.mdx
new file mode 100644
index 0000000000..74ac751ee4
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/writing/imports.mdx
@@ -0,0 +1,117 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom import](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only local
+context for making policy decisions.
+
+Sentinel also comes with a set of [standard imports](/sentinel/imports).
+Standard imports are available to every Sentinel policy to help policy writers
+with common tasks such as working with the time, network addresses, and more.
+
+-> **This page is about writing policies that _use_ imports.** If you're
+interested in _creating_ a new import, please see the section on [extending
+Sentinel](/sentinel/extending) for information on how to write modules and
+import plugins.
+
+## Using Imports
+
+To use an import, you use the `import` keyword at the top of your policy. This
+specifies the name of the import you want to use. The application you're writing
+the policy for must already be
+[configured](/sentinel/configuration#imports) to provide that
+import.
+
+Details on imports can be found in the
+[import section in the language reference](/sentinel/language/imports).
+
+In the example below, we use the [time](/sentinel/imports/time) import:
+
+```sentinel playground
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+There are two options to develop a policy locally when using imports:
+configure Sentinel to launch the import on `apply`, or mock the import
+using `mock`. The former requires access to the import while the
+latter is faster (doesn't have to launch a process for plugins) and
+doesn't require the import.
+
+## Mocking Imports
+
+The first option to developing policies locally is to mock the import values.
+When mocking an import, you don't need the import to be available. This can be
+useful since some imports may not be available as a plugin and may only be
+available to the application the policy runs in.
+
+Mocks are specified via the [configuration
+file](/sentinel/configuration). Mocks can also be used for
+[testing](/sentinel/writing/testing).
+
+You can supply mock configuration one of two ways, depending on your use case:
+
+- **[Using static data](/sentinel/configuration#mocking-static-data):**
+ Use this method when you can accurately represent your mock data in JSON and
+ do not need to mock complex Sentinel features such as functions.
+- **[Using Sentinel code](/sentinel/configuration#mocking-with-sentinel-code):**
+ Use this method when using a static JSON object is insufficient, such as when
+ you need to mock functions or other complex Sentinel features.
+
+Our example does not require complex data to be mocked, so a static object
+is sufficient:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ hour = 12
+ weekday_name = "Tuesday"
+ }
+ }
+}
+```
+
+This can be used via the CLI:
+
+```
+$ sentinel apply -config=config.hcl policy.sentinel
+Pass
+```
+
+## Launching Import Plugins
+
+If you have access to the plugin binary, you can launch the import. The
+benefit of this is that it is really using the import to test your
+policy. If the import changes, your policies may start failing. If you
+only use mock data and the import changes, your policies will still
+appear to work.
+
+Imports are configured in the configuration file:
+
+```hcl
+import "plugin" "custom_time" {
+ source = "/path/to/sentinel-time-import"
+}
+```
+
+This would require the `sentinel-time-import` binary. For this example
+this doesn't currently exist. We plan on writing one to provide for this
+section of the documentation.
+
+## Custom Imports
+
+You can also [create your own imports](/sentinel/extending).
+
+If your policy decisions could benefit from accessing external information,
+then you can use custom imports as a way to do this.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/writing/index.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/writing/index.mdx
new file mode 100644
index 0000000000..064069d389
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/writing/index.mdx
@@ -0,0 +1,35 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing
+description: >-
+ Sentinel provides a language and workflow for building policy across any
+ system that embeds Sentinel. By learning Sentinel once, you are able to
+ effectively control access to many systems using Sentinel's powerful features.
+ Basic concepts that are important to understand for Vault usage.
+layout: docs
+---
+
+# Writing Sentinel Policy
+
+This section covers how to write Sentinel policies.
+
+It is recommended that you complete the
+[getting started guide](/sentinel/intro/getting-started/first-policy) prior to
+reading this section of the documentation. The getting started guide will
+explain all the basics of writing and testing policies. This section of the
+documentation will serve as more of a reference guide to all available
+features of Sentinel.
+
+Sentinel provides a language and workflow for building policy across any system
+that embeds Sentinel. By learning Sentinel once, you are able to effectively
+control access to many systems using Sentinel's powerful features.
+
+Sentinel also provides a [local CLI](/sentinel/commands) for developing and testing
+Sentinel policies. This CLI can be integrated into a daily developer's workflow
+along with CI to treat [policy as code](/sentinel/concepts/policy-as-code).
+
+Sentinel uses its own [language](/sentinel/language) for writing
+policies. You can view a [language reference](/sentinel/language)
+as well as the [specification](/sentinel/language/spec) for details. You
+don't have to read those documents immediately, since the language should
+be easy enough to pick up throughout this section.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/writing/rules.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/writing/rules.mdx
new file mode 100644
index 0000000000..ab4fc69d08
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/writing/rules.mdx
@@ -0,0 +1,65 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-rules
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Rules
+
+Rules form the basis of a policy by representing behavior that is either passing
+or failing. Rules are a first class language construct in Sentinel. A policy can
+and should be broken down into rules to aid with readability, testability, and
+performance.
+
+Rules provide:
+
+- **Readability:** A policy that is broken down into rules can be read
+ more easily. The logic becomes clearer to see for policy writers when
+ they come back to it.
+
+- **Reportability:** When [tracing](/sentinel/writing/tracing) is enabled,
+ rules form the foundation of the data returned. All evaluated rules are added
+ to the trace with the data the rule evaluated to, and their position within
+ policy or module source files.
+
+- **Testability:** [Policy testing](/sentinel/writing/testing) is
+ built on asserting the values of rules. By breaking logic down into
+ rules, you're able to more effectively test your policies.
+
+- **Performance:** Rules are only evaluated on demand, and are also only
+ evaluated once. This means that a rule referenced multiple times only has a
+ one-time performance cost. For complex logic, this could result in improved
+ performance.
+
+An example usage of rules is shown below:
+
+```json sentinel
+{
+ "policy": "is_sunny = rule { weather is \"sunny\" }\nis_wednesday = rule { day is \"wednesday\" }\nmain = rule { is_sunny and is_wednesday }",
+ "globals": {
+ "weather": "sunny",
+ "day": "wednesday"
+ }
+}
+```
+
+Rules can also contain non-boolean data. This can be useful for passing more
+detail to the caller than an opaque boolean value would be able to communicate.
+
+```sentinel playground
+days = [{"weather": "sunny"}]
+main = rule {
+ filter days as d {
+ d.weather is not "sunny"
+ }
+}
+```
+
+Details about rules and their behavior can be found in
+[the language reference](/sentinel/language/rules). Rules have important
+behavior so we recommend reading the language reference on rules.
+Rules will be used throughout the remainder of the documentation.
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/writing/testing.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/writing/testing.mdx
new file mode 100644
index 0000000000..61ab0ea46c
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/writing/testing.mdx
@@ -0,0 +1,491 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-testing
+description: >-
+ Sentinel has a built-in test framework to validate a policy behaves as
+ expected.
+layout: docs
+---
+
+# Testing
+
+Sentinel has a built-in test framework to validate a policy behaves as expected.
+
+With an ever-increasing amount of automation surrounding technology,
+the guardrails provided by policies are a critical piece towards ensuring
+expected behavior. As a reliance on correct policy increases, it is important
+to test and verify policies.
+
+Testing is a necessary step to fully realize
+[policy as code](/sentinel/concepts/policy-as-code). Just as good software
+is well tested, a good set of policies should be equally well tested.
+
+## Test Folder Structure
+
+Policies are tested by asserting that [rules](/sentinel/writing/rules)
+are the expected values given a pre-configured input. Tests are run
+by executing the [test command](/sentinel/commands/test).
+
+Sentinel is opinionated about the folder structure required for tests.
+This opinionated structure allows testing to be as simple as running
+`sentinel test` with no arguments. Additionally, it becomes simple to test
+in a CI or add new policies.
+
+The structure Sentinel expects is `test//*.[hcl|json]` where ``
+is the name of your policy file without the file extension. Within that folder
+is a list of HCL or JSON files. Each file represents a single test case.
+Therefore, each policy can have multiple tests associated with it.
+
+-> Note that the primary configuration file format for Sentinel is HCL. While
+you can write configuration files in a HCL-equivalent JSON, we only discuss the
+use of HCL on this page.
+
+## Test Case Format
+
+Each HCL file within the test folder for a policy is a single test case.
+
+The file is the same configuration format as the [CLI configuration
+file](/sentinel/configuration). The format lets you define mock data, imports to
+use, and more. This mock data is the key piece in being able to test policies:
+you craft a specific scenario and assert your policy behaves as you expect.
+
+Test cases also use the `test` block within the configuration file to assert the
+value of [rules](/sentinel/writing/rules). If the `test` key is omitted, the
+policy is expected to pass. If the test key is specified, only the rules
+specified in the map will be asserted. This means if you omit `main`, then the
+final policy result is not asserted.
+
+Example with assertions:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+
+test {
+ rules = {
+ main = false
+ is_open_hours = false
+ is_weekday = true
+ }
+}
+```
+
+The configuration above specifies some parameter data, and asserts the result of
+some rules. This is the same configuration used in the example section below.
+
+## Example
+
+Lets use the following file as an example. Save this file to a directory
+and name it `policy.sentinel`. It can be named anything with the `sentinel`
+extension, but by naming it `policy.sentinel` your output should match
+the example output on this page.
+
+```sentinel
+// The day of the week.
+param day
+
+// The hour of the day.
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+### A Passing Test
+
+Next, let's define a single test case. Relative to where you saved
+the policy, create a file at the path `test/policy/good.hcl`.
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 14
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/good.hcl
+```
+
+This passed because the policy passed. We didn't assert any specific
+rules. By not specifying any assertions, `test` expects the policy itself
+to fully pass.
+
+### A Failing Test
+
+Define another test case to fail. We want to verify our policy fails when expected, too.
+
+Save the following as `test/policy/7-am.hcl`:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+FAIL - policy.sentinel
+ FAIL - test/policy/7-am.hcl
+ expected "main" to be true, got: false
+
+ trace:
+ policy.sentinel:9:1 - Rule "main"
+ bool: false
+
+ policy.sentinel:8:1 - Rule "is_open_hours"
+ bool: false
+
+ PASS - test/policy/good.hcl
+```
+
+As you can see, the test fails because "main" is false. This is good
+because the policy _should_ have failed since we specified an invalid
+hour. But, we expect main to be false and don't want our test to fail!
+Update `7-am.hcl` to add test assertions:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+
+test {
+ rules = {
+ main = false
+ is_open_hours = false
+ }
+}
+```
+
+And when we run the tests:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/7-am.hcl
+ PASS - test/policy/good.hcl
+```
+
+The test passes. We asserted that we expect the `main` rule to be false,
+the `is_open_hours` rule to be false, and the `is_weekday` rule to be
+true. By asserting some rules are true, we can verify that our policy
+is failing for reasons we expect.
+
+## Mocking
+
+The above example demonstrates how to test by supplying different
+[parameters](/sentinel/language/parameters). Parameters in a policy can be
+specifically useful when you want to control user-defined input values to a
+policy.
+
+However, generally, when testing, you will need mimic the conditions you will
+see in production. Production implementations of Sentinel will supply data using
+one of two methods:
+
+- **Global data:** Data is injected directly into the policy's scope and is
+ accessible using normal identifiers, similar to variables.
+- **Imports:** Data is stored behind an [import](/sentinel/language/imports)
+ and loaded on demand as needed by the policy author.
+
+Proper testing of a policy requires that these values be able to be _mocked_ -
+or, in other words, simulated in a way that allows the accurate testing of the
+scenarios that a policy could reasonably pass or fail under.
+
+Mocking both globals and imports can be done by setting various parts of the
+configuration file.
+
+### Mocking Globals
+
+Demonstrating the mocking of globals can be seen by making a few modifications to
+our example policy, removing the `param` declarations:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Then, change the `param` section in the configuration file to
+[`global`](/sentinel/configuration#global).
+
+```json
+global "day" {
+ value = "monday"
+}
+
+global "hour" {
+ value = 14
+}
+```
+
+This test should still pass, as if nothing had happened, although what we've
+done is shifted our parameters to globals, simulating an environment where `day`
+and `hour` are already defined for us.
+
+### Mocking Imports
+
+To mock imports, we need to use the
+[`mock`](/sentinel/configuration#mock-imports) section of the
+configuration file.
+
+Let's say the above example is behind an import named `time`.
+
+-> **NOTE:** [`time`](/sentinel/imports/time) is a valid standard import.
+This example may not be accurate to the
+import's syntax.
+
+The code now looks like this:
+
+```sentinel
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To mock this import, we can [mock it as static
+data](/sentinel/configuration#mocking-static-data). The configuration
+file now looks like, without assertions:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ weekday_name = "Monday"
+ hour = 14
+ }
+ }
+}
+```
+
+The policy will now pass, with the `time` import mocked.
+
+Data can also be [mocked as Sentinel
+code](/sentinel/configuration#mocking-with-sentinel-code). In this case,
+the above configuration file would look like:
+
+```hcl
+mock "time" {
+ module {
+ source = "mock-time.sentinel"
+ }
+}
+```
+
+And a file named `mock-time.sentinel` would now hold your mock values:
+
+```sentinel
+day = "monday"
+hour = 14
+```
+
+Mocking as Sentinel code allows more complex details to be mocked as well, such
+as functions. Say we wanted to mock the `time.load()` function. To mock this,
+just add it to the `mock-time.sentinel` file:
+
+```sentinel
+load = func(_) {
+ return {
+ "weekday_name": "Monday",
+ "hour": 14,
+ }
+}
+```
+
+Your code can now be written as:
+
+```sentinel
+import "time"
+
+t = time.load("a_mock_timestamp")
+
+is_weekday = rule { t.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { t.hour > 8 and t.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To see more details, see the [Mock
+Imports](/sentinel/configuration#mock-imports) section in the
+configuration file.
+
+## Asserting Non-Boolean Rules
+
+Non-boolean rules can also be asserted by `sentinel test`.
+
+To assert non-boolean values, simply enter the expected value into the rule
+contents:
+
+```hcl
+param "maintenance_days" {
+ value = [
+ {
+ day = "wednesday"
+ hour = 9
+ },
+ {
+ day = "friday"
+ hour = 1
+ },
+ {
+ day = "sunday"
+ hour = 1
+ },
+ ]
+}
+
+test {
+ rules = {
+ main = [
+ {
+ day = "wednesday"
+ hour = 9
+ },
+ ]
+ }
+}
+```
+
+This would assert a policy checking for violations of a maintenance policy,
+saying that maintenance hours should happen before 6AM on any given day:
+
+```sentinel
+param maintenance_days
+
+main = rule {
+ filter maintenance_days as d {
+ d.hour >= 6
+ }
+}
+```
+
+## Test JSON Output
+
+Running `sentinel test` with the `-json` flag will give you the test results in
+a JSON output format, suitable for parsing by reporting software.
+
+-> **Note:** The JSON output format is currently under development and the
+format may change at a later time. Additionally, support for other well-known
+formats, such as JUnit, may become available in the future.
+
+The current format is an object with the only top-level key being `policies`,
+with each test grouped up by policy being run.
+
+The policy result fields are:
+
+- `path`: The path of the policy and the index of the test result in the
+ `policies` field in the root object.
+- `status`: A string representation of the policy's test status as a whole. Can
+ be one of `PASS`, `FAIL`, `ERROR`, or `?`. The final status, `?`, represents a
+ policy that has no tests to process, and acts like a passing test.
+- `errors`: An array of any error messages encountered during processing the
+ policy for testing. Usually reserved for policy file or parser-related errors.
+ For case-specific errors, see the error field for the particular case.
+- `cases`: A map of case results, indexed by case path.
+
+The case result fields are:
+
+- `path`: The path of the test case and the result's index in the `cases` field
+ in the policy object.
+- `status`: A string representation of the case's test status. Can be one of
+ `PASS`, `FAIL` or `ERROR`.
+- `errors`: An array of any error messages encountered during running this test
+ case.
+- `trace`: The trace for this policy in JSON format. See the
+ [tracing](/sentinel/writing/tracing) page for more details.
+- `rule_detail`: When the `status` of this test case is `FAIL`, contains an
+ object of assertion failure detail, indexed by rule. Only failures are counted
+ here; any rules not found here can be assumed to have passed or not asserted.
+- `config_warnings`: An array of strings denoting any configuration warnings
+ found while processing the configuration for this test case.
+- `config_legacy`: Denotes whether or not the configuration is a legacy JSON
+ configuration and needs to be modernized. This field may be removed in future
+ releases.
+
+A passing example is shown below:
+
+```json
+{
+ "policies": {
+ "policy.sentinel": {
+ "path": "policy.sentinel",
+ "status": "PASS",
+ "errors": null,
+ "cases": {
+ "test/policy/pass.hcl": {
+ "path": "test/policy/pass.hcl",
+ "status": "PASS",
+ "errors": null,
+ "trace": {
+ "description": "A very basic policy to determine if a run is within working hours.",
+ "error": null,
+ "print": "",
+ "result": true,
+ "rules": {
+ "is_open_hours": {
+ "desc": "Passes if run during business hours.",
+ "ident": "is_open_hours",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 290,
+ "line": 13,
+ "column": 1
+ },
+ "value": true
+ },
+ "is_weekday": {
+ "desc": "Passes if the day does not fall on the weekend.",
+ "ident": "is_weekday",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 193,
+ "line": 10,
+ "column": 1
+ },
+ "value": true
+ },
+ "main": {
+ "desc": "",
+ "ident": "main",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 338,
+ "line": 14,
+ "column": 1
+ },
+ "value": true
+ }
+ }
+ },
+ "rule_detail": {},
+ "config_warnings": null,
+ "config_legacy": false
+ }
+ }
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.19.x/content/sentinel/docs/writing/tracing.mdx b/content/sentinel/v0.19.x/content/sentinel/docs/writing/tracing.mdx
new file mode 100644
index 0000000000..f2069bd682
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/docs/writing/tracing.mdx
@@ -0,0 +1,401 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-tracing
+description: >-
+ Sentinel provides execution traces to better understand the execution of a
+ policy.
+layout: docs
+---
+
+# Traces
+
+Sentinel provides execution traces to better understand the execution of a policy.
+
+When using the Sentinel [CLI](/sentinel/commands), traces are shown
+on policy failure during `apply` or `test`, or when the `-trace` flag is
+explicitly specified. For Sentinel-enabled applications, traces are optional
+and may require special configuration to enable.
+
+The availability of the trace may vary from application to application. While
+some applications may only log traces on failure, others, such as [Terraform
+Cloud](https://www.terraform.io/cloud), log the trace on every execution. For
+more details, consult your implementation's documentation.
+
+-> **Note:** We're actively improving Sentinel tracing in each release
+to provide better data and improve readability. The exact format of a
+trace may not match the Sentinel version embedded in the application you're
+using, but the data should be similar. The canonical format for a trace should
+get more stable as we approach a 1.0 release.
+
+## Analyzing a Trace
+
+To show traces, let's use the example policy below with the CLI:
+
+```sentinel
+param day
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+
+main = rule { is_open_hours and is_weekday }
+```
+
+Let's cause the policy to fail so the trace is more interesting.
+If you're on a terminal that supports it, the trace output will be colored
+so you can more clearly see passes, failures, and rules.
+
+
+
+ $ sentinel apply main.sentinel{'\n'}
+
+ main.sentinel:1:7: requires value for parameter day
+
+ {'\n'}
+ {'\n'}
+ {'\n'}
+ {' '}Values can be strings, floats, or JSON array or object values. To force
+ {'\n'}
+ {' '}strings, use quotes.{'\n'}
+ {'\n'}
+ {' '}Enter a value: sunday{'\n'}
+ {'\n'}
+
+ main.sentinel:2:7: requires value for parameter hour
+
+ {'\n'}
+ {'\n'}
+ {'\n'}
+ {' '}Values can be strings, floats, or JSON array or object values. To force
+ {'\n'}
+ {' '}strings, use quotes.{'\n'}
+ {'\n'}
+ {' '}Enter a value: 14{'\n'}
+ {'\n'}
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+
+ Fail - main.sentinel
+
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:7:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}
+ false
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:5:1 - Rule "is_open_hours"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}true{'\n'}
+ {'\n'}
+
+ main.sentinel:4:1 - Rule "is_weekday"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}false{'\n'}
+
+
+
+We can see all of our rules neatly and clearly displayed along with the result
+of the policy. If you are getting the trace due to a failed policy and not
+because you explicitly used `-trace`, an informational message is displayed.
+
+As we can see from our basic example, the `main` rule has failed and its result
+is highlighted in red. All peripheral rules that were also evaluated as part of
+the policy are also displayed below the main rule. Source positions are also
+displayed so that you can find the references to the rules in your code.
+
+Via the trace, we can clearly see that while `is_open_hours` returned a true
+result, `is_weekday` did not, and per the logic in our code, the policy is
+rejected.
+
+## Non-Boolean Rules and the Trace
+
+The trace is particularly important when working with rules that return
+non-boolean data, such as rules where you want to see violations instead of a
+simple opaque boolean value. Let's take our example from the [rules](./rules)
+section, and write a small static policy for it:
+
+```sentinel playground
+days = [
+ {"weekday": "Sunday", "weather": "clouds"},
+ {"weekday": "Monday", "weather": "rain"},
+ {"weekday": "Tuesday", "weather": "sunny"},
+ {"weekday": "Wednesday", "weather": "sunny"},
+ {"weekday": "Thursday", "weather": "clouds"},
+ {"weekday": "Friday", "weather": "rain"},
+ {"weekday": "Saturday", "weather": "sunny"},
+]
+
+main = rule {
+ filter days as d {
+ d.weather is not "sunny"
+ }
+}
+```
+
+Here is the output of our policy:
+
+
+
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+ {'\n'}
+
+ Fail - weather.sentinel
+
+ {'\n'}
+ {'\n'}
+
+ weather.sentinel:11:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+
+ {' [\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Sunday"'}
+ {'\n'}
+ {' "weather": "clouds"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Monday"'}
+ {'\n'}
+ {' "weather": "rain"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Thursday"'}
+ {'\n'}
+ {' "weather": "clouds"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Friday"'}
+ {'\n'}
+ {' "weather": "rain"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' ]'}
+
+
+
+
+We can now see all of the weekdays with non-sunny weather.
+
+Note that to prevent formatting issues and excessive output, the human-readable
+trace is limited in the volume of output it will display for collections.
+
+## JSON Output
+
+The trace can also be displayed in JSON by adding `-json` to `sentinel apply`
+and `sentinel test`, and will display the trace in its entirety, unlike the
+standard CLI output.
+
+Here is the result of running `sentinel apply -json` against our weather policy:
+
+```json
+{
+ "can_override": false,
+ "error": null,
+ "policies": [
+ {
+ "allowed_failure": false,
+ "error": null,
+ "policy": "weather.sentinel",
+ "result": false,
+ "trace": {
+ "description": "",
+ "error": null,
+ "print": "",
+ "result": false,
+ "rules": {
+ "main": {
+ "desc": "",
+ "ident": "main",
+ "position": {
+ "filename": "weather.sentinel",
+ "offset": 328,
+ "line": 11,
+ "column": 1
+ },
+ "value": [
+ {
+ "weather": "clouds",
+ "weekday": "Sunday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Monday"
+ },
+ {
+ "weather": "clouds",
+ "weekday": "Thursday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Friday"
+ }
+ ]
+ }
+ }
+ }
+ }
+ ],
+ "result": false
+}
+```
+
+Note that the trace is always included with this output, regardless of whether
+or not the policy passes or fails; using `-trace` is unnecessary.
+
+Additionally, with `sentinel apply`, you can display the value of a single rule
+in JSON, instead of the entire trace. This is done with the `-json-rule` flag.
+
+-> **Note:** `sentinel apply -json-rule` needs to be run either against a single
+on-disk policy, or against a single policy within a policy set. It is ignored
+in full policy set executions and functions exactly like `-json` in these
+scenarios.
+
+Here is the result of executing `sentinel apply -json-rule main weather.sentinel`:
+
+```json
+[
+ {
+ "weather": "clouds",
+ "weekday": "Sunday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Monday"
+ },
+ {
+ "weather": "clouds",
+ "weekday": "Thursday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Friday"
+ }
+]
+```
+
+## Other Trace Details
+
+There are some other details that are good to know when working with the trace:
+
+- Only rules that get executed are added to the trace. Considering our first
+ example where the `main` rule is the expression `is_open_hours and is_weekday`,
+ if our expression was using an `or` operator instead of `and`, in addition to
+ the policy passing, `is_open_hours` would be present in the trace, but
+ `is_weekday` would not be, as the policy would have never evaluated that rule.
+- Descriptions for both rules and policies will show up in the trace as well if
+ present. Here is the output to our first policy with the appropriate
+ annotations:
+
+
+
+ ...{'\n\n'}
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+
+ Fail - main.sentinel
+
+ {'\n'}
+ {'\n'}
+ Description:{'\n'}
+ {' A very basic policy to determine if a run is within working\n'}
+ {' hours.\n'}
+ {'\n'}
+
+ main.sentinel:7:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}
+ false
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:5:1 - Rule "is_open_hours"
+
+ {'\n'}
+ {' '}
+ Description:
+ {'\n'}
+ {' Passes if run during business hours.\n'}
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}true{'\n'}
+ {'\n'}
+
+ main.sentinel:4:1 - Rule "is_weekday"
+
+ {'\n'}
+ {' '}
+ Description:
+ {'\n'}
+ {' Passes if the day does not fall on the weekend.\n'}
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}false{'\n'}
+
+
diff --git a/content/sentinel/v0.19.x/content/sentinel/intro/getting-started/first-policy.mdx b/content/sentinel/v0.19.x/content/sentinel/intro/getting-started/first-policy.mdx
new file mode 100644
index 0000000000..38d7b22c9a
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/intro/getting-started/first-policy.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Your First Sentinel Policy
+sidebar_current: intro-getting-started-first
+description: Let's begin by writing a working Sentinel policy.
+layout: intro
+---
+
+# Your First Sentinel Policy
+
+Sentinel is a system to enforce complex policies on an integrated application.
+
+Writing Sentinel policy requires minimal programming experience. The Sentinel
+language is designed to be approachable and learned quickly and easily. Whether
+you're a professional programmer or someone who uses SQL and Excel, you can
+learn to write Sentinel policies.
+
+Let's begin by writing a simple, working Sentinel policy:
+
+```sentinel playground
+hour = 4
+main = rule { hour >= 0 and hour < 12 }
+```
+
+This is a valid Sentinel policy. It will pass since we hardcoded the
+`hour` to be 4. In a real system, `hour` may be something that is provided
+to us and actually set to the current hour. We'll learn more about that later.
+
+For now, try running this policy locally. Save the above example to a file
+named `policy.sentinel` and execute it. Then, modify the policy to make it
+fail. Play around more if you'd like.
+
+```
+$ sentinel apply policy.sentinel
+Pass
+```
+
+## Main
+
+Every Sentinel policy must have a `main` rule. This is the rule that
+is evaluated to determine the result of a policy.
+
+A rule describes an expression that generally means one of two things:
+
+- Does a policy _pass a condition_ that would authorize an operation? In our
+ above example, describe a policy that checks the supplied hour (4) is within an
+ authorized time window (between 0 - midnight, and 12 noon).
+- Conversely, can a policy find any _violations_ that would block authorization
+ of the operation? Building on the above, consider a policy that takes a
+ schedule, and finds all time blocks that fall outside of the example time window
+ supplied in the above policy.
+
+It is easy to imagine that such a rule might be used in a system such
+as [Nomad](https://www.nomadproject.io) to restrict the times when a
+deploy can occur. The power of arbitrary logical statements within Sentinel
+allows Sentinel policies to restrict almost any behavior.
+
+Next, we'll
+[introduce and explain rules](/sentinel/intro/getting-started/rules)
+so we can use them in our policies.
diff --git a/content/sentinel/v0.19.x/content/sentinel/intro/getting-started/imports.mdx b/content/sentinel/v0.19.x/content/sentinel/intro/getting-started/imports.mdx
new file mode 100644
index 0000000000..dbbb146ecf
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/intro/getting-started/imports.mdx
@@ -0,0 +1,58 @@
+---
+page_title: Imports
+sidebar_current: intro-getting-started-imports
+description: Imports enable Sentinel to access external data and functions.
+layout: intro
+---
+
+# Imports
+
+Imports enable Sentinel to access external data and functions.
+
+Sentinel ships with a set of [standard imports](/sentinel/imports).
+Standard imports are available by default to every Sentinel policy. Note that
+the application embedding Sentinel may allow or deny the available
+set of imports.
+
+To use an import, use the `import` statement. Then, access data and
+functions on the import using the import name followed by a period and
+the field you want to access. The example below checks whether the request
+path starts with a prefix. Assume that `request_path` is available.
+
+```sentinel
+import "strings"
+
+main = rule { strings.has_prefix(request_path, "/admin") }
+```
+
+## External Data
+
+The true power in imports is their ability to reference external data.
+Imports can be added to a Sentinel-enabled application through
+[plugins](/sentinel/extending). This enables any Sentinel-enabled
+application to make policy decision based on any source of data.
+
+This ability allows the policy system to enforce almost any necessary
+organizational policy, since the ability of a policy isn't restricted
+purely to the embedding application's data model.
+
+For example, policies in [Nomad](https://www.nomadproject.io) can
+access data in [Consul](https://www.consul.io) to determine attributes
+of a policy. In the example below, we use a hypothetical Consul import:
+
+```sentinel
+import "consul"
+
+main = rule {
+ job.tasks[0].resources.memory <= int(consul.get("policy/nomad/max-memory"))
+}
+```
+
+In this example, a Nomad job's memory usage is limited to the value of
+a Consul key/value item. By simply changing a Consul KV entry, policy
+can be changed. Imports can do anything, you can [write your own import plugins](/sentinel/extending)
+to extend Sentinel.
+
+Next, we'll learn how to
+[test policies](/sentinel/intro/getting-started/testing)
+to ensure our policy logic is correct.
diff --git a/content/sentinel/v0.19.x/content/sentinel/intro/getting-started/index.mdx b/content/sentinel/v0.19.x/content/sentinel/intro/getting-started/index.mdx
new file mode 100644
index 0000000000..5c9b6b43c5
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/intro/getting-started/index.mdx
@@ -0,0 +1,16 @@
+---
+page_title: Install Sentinel CLI - Getting Started
+sidebar_current: intro-getting-started-overview
+description: The first step to using Sentinel is to get the CLI installed.
+layout: intro
+---
+
+# Getting Started With Sentinel
+
+This section covers getting started with Sentinel. Here, we will take you
+through the first steps that you will need to do to get started with installing
+the Sentinel CLI, writing your first policy, and getting familiar with rules,
+boolean logic, imports, and testing.
+
+You can select a topic on the menu to get started. The first step is to [install
+the Sentinel CLI](/sentinel/intro/getting-started/install).
diff --git a/content/sentinel/v0.19.x/content/sentinel/intro/getting-started/install.mdx b/content/sentinel/v0.19.x/content/sentinel/intro/getting-started/install.mdx
new file mode 100644
index 0000000000..e478fd532f
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/intro/getting-started/install.mdx
@@ -0,0 +1,56 @@
+---
+page_title: Install Sentinel CLI - Getting Started
+sidebar_current: intro-getting-started-install
+description: The first step to using Sentinel is to get the CLI installed.
+layout: intro
+---
+
+# Install Sentinel CLI
+
+Sentinel is a policy framework that is embedded in the enterprise versions of
+HashiCorp tools. The policies you write are deployed to these applications and
+enforced there.
+
+The Sentinel CLI is a command-line interface for local development and testing.
+For the getting started guide, we'll use the CLI to learn how to write policies
+for Sentinel-enabled applications. The Sentinel CLI is distributed as a [binary
+package](/sentinel/downloads) for all supported platforms and architectures.
+
+## Installation Instructions
+
+To install the Sentinel CLI, find the [appropriate package](/sentinel/downloads) for your
+system and download it. The CLI is packaged as a zip archive.
+
+After downloading Sentinel, unzip the package. The CLI runs as a single binary
+named `sentinel`. Any other files in the package can be safely removed and
+Sentinel will still function.
+
+The final step is to make sure that the `sentinel` binary is available on the `PATH`.
+See [this page](https://stackoverflow.com/questions/14637979/how-to-permanently-set-path-on-linux)
+for instructions on setting the PATH on Linux and Mac.
+[This page](https://stackoverflow.com/questions/1618280/where-can-i-set-path-to-make-exe-on-windows)
+contains instructions for setting the PATH on Windows.
+
+## Verifying the Installation
+
+After installing the Sentinel CLI, verify the installation worked by opening a
+new terminal session and checking that the `sentinel` binary is available. By
+executing `sentinel`, you should see help output similar to the following:
+
+```
+$ sentinel
+Usage: sentinel [--version] [--help] []
+
+Available commands are:
+ apply Execute a policy and output the result
+ fmt Format Sentinel policy to a canonical format
+ test Test policies
+ version Prints the Sentinel version
+```
+
+If you get an error that the binary could not be found, then your `PATH`
+environment variable was not setup properly. Please go back and ensure that your
+`PATH` variable contains the directory where Sentinel was installed.
+
+Otherwise, the CLI is installed and ready to go. Let's celebrate by [writing our
+first policy!](/sentinel/intro/getting-started/first-policy)
diff --git a/content/sentinel/v0.19.x/content/sentinel/intro/getting-started/logic.mdx b/content/sentinel/v0.19.x/content/sentinel/intro/getting-started/logic.mdx
new file mode 100644
index 0000000000..d3716a64ea
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/intro/getting-started/logic.mdx
@@ -0,0 +1,54 @@
+---
+page_title: Logic
+sidebar_current: intro-getting-started-logic
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Logic
+
+The core of a policy is a set of logical expressions to determine whether
+a behavior is allowed or not.
+Sentinel makes it easy to write readable logical expressions to express
+the intended policy.
+
+The logical expression syntax will be familiar to anyone who has programmed
+before. Sentinel also supports a couple more unique constructs to assist
+with common policy requirements. Detailed documentation on how to write
+logical expressions and the supported operators is available in
+[the boolean expression reference](/sentinel/language/boolexpr).
+
+Example logic:
+
+```sentinel
+a is b // Equality, you can also use ==
+a is not b // Inequality, you can also use !=
+
+a > b // Relational operators, comparison
+a < b
+a >= b
+a <= b
+
+a contains b // Inclusion check, substrings, etc.
+a not contains b // Negated version of above
+```
+
+The full list of available operators can be found in
+[the boolean expression reference](/sentinel/language/boolexpr).
+
+Logic can be combined with `and` or `or`. When combined with `and`, both
+sides must result in `true`. When combined with `or`, only one side needs
+to be true to result in `true`.
+
+With these building blocks, basic rules can be built up:
+
+```sentinel
+main = rule {
+ hour >= 8 and
+ hour < 17
+}
+```
+
+Next, we'll introduce
+[imports](/sentinel/intro/getting-started/imports)
+so that we can write rules that interact with external data.
diff --git a/content/sentinel/v0.19.x/content/sentinel/intro/getting-started/next-steps.mdx b/content/sentinel/v0.19.x/content/sentinel/intro/getting-started/next-steps.mdx
new file mode 100644
index 0000000000..23b470be59
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/intro/getting-started/next-steps.mdx
@@ -0,0 +1,21 @@
+---
+page_title: Next Steps
+sidebar_current: intro-getting-started-nextsteps
+description: That concludes the getting started guide for Sentinel.
+layout: intro
+---
+
+# Next Steps
+
+That concludes the getting started guide for Sentinel. Hopefully you're excited
+about the possibilities of Sentinel and ready to put this knowledge to use to
+improve the safety of your environments.
+
+We've covered the basics of the core features of Sentinel in this guide.
+We recommend the following as next steps:
+
+- [Documentation](/sentinel) - The documentation is an in-depth
+ reference guide of all the features of Sentinel.
+
+- [Language Reference](/sentinel/language) - The language reference
+ is a complete reference to the features of the Sentine policy language.
diff --git a/content/sentinel/v0.19.x/content/sentinel/intro/getting-started/rules.mdx b/content/sentinel/v0.19.x/content/sentinel/intro/getting-started/rules.mdx
new file mode 100644
index 0000000000..f12a61fe75
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/intro/getting-started/rules.mdx
@@ -0,0 +1,86 @@
+---
+page_title: Rules
+sidebar_current: intro-getting-started-rules
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Rules
+
+Rules in Sentinel are a first-class concept. Within a policy, rules serve a few
+purposes:
+
+- They make complex logic more understandable by allowing said logic to be
+ broken down.
+- They allow assertion of this logic through
+ [testing](/sentinel/intro/getting-started/testing) of the rule's contents.
+- They facilitate reporting of data as rules get published as part of the [policy
+ trace](/sentinel/writing/tracing).
+
+A rule functions in ways similar to both a variable and a function: they hold a
+value, but are lazily evaluated; a rule's value is not assigned until the first
+time it's referenced in a policy. Additionally, while the value of evaluated
+rules will be available within a policy's trace after it's evaluated, values of
+variables - and the return value of functions - are not. Finally, a rule value
+is memoized - further references to the rule will not change its result.
+
+Rules can hold more than just boolean data. For more advanced rule patterns, see
+[the language reference](/sentinel/language/rules).
+
+## Writing Rules
+
+Let's look at the Sentinel policy we wrote in the previous section:
+
+```sentinel playground
+hour = 4
+main = rule { hour >= 0 and hour < 12 }
+```
+
+The logic in this policy is straightforward and easy to understand. However,
+it is common for policy logic to become much more complicated. Rules are the
+key abstraction for making complex logic understandable. We can turn the
+policy above into a set of rules:
+
+```sentinel playground
+hour = 4
+
+past_midnight = rule { hour >= 0 }
+before_noon = rule { hour < 12 }
+
+main = rule {
+ past_midnight and
+ before_noon
+}
+```
+
+This policy is easier to read. Our original policy was already quite
+simple, but it is easy to imagine a more complex policy becoming much
+more readable by extracting logical expressions into a set of rules.
+
+Rules don't just exist to make a policy more readable. They're also a
+testing and debugging point. For testing, you can assert that certain rules
+are certain values given a specific input to verify that your policy works
+as you expect. Testing and debugging are covered in later sections.
+
+## Conditional Rules
+
+In many cases, a rule is only valid when something else is also true.
+
+Consider the human language statement "before you eat, you must wash your hands."
+The rule "you must wash your hands" is only valid "before you eat". In any
+other scenario, the rule is meaningless.
+This is also true in a technical context. Imagine the rule "if the driver
+is Docker, you must not expose any ports." For this example, assume
+rules `is_docker` and `has_no_exposed_ports` exist. You can write this rule
+like this:
+
+```sentinel
+main = rule when is_docker { has_no_exposed_ports }
+```
+
+When `is_docker` is true, the rule is evaluated as normal. However,
+when `is_docker` is false, the rule always returns `true`, because the
+logic of the rule is meaningless.
+
+Let's learn how to create more complex rules with
+[logical expressions](/sentinel/intro/getting-started/logic).
diff --git a/content/sentinel/v0.19.x/content/sentinel/intro/getting-started/testing.mdx b/content/sentinel/v0.19.x/content/sentinel/intro/getting-started/testing.mdx
new file mode 100644
index 0000000000..7149a7966f
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/intro/getting-started/testing.mdx
@@ -0,0 +1,72 @@
+---
+page_title: Testing
+sidebar_current: intro-getting-started-testing
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Testing
+
+It is important to ensure the policies you write are correct. Sentinel
+includes a built-in test framework that can be run locally and in CI
+environments to test that policies behave as expected.
+
+A common pitfall with many simple ACL systems is that they provide no easy
+way to verify their correctness. You basically have to set the ACL and try
+the behaviors against a real system to verify it is working as expected.
+This requires a lot of setup and is unique to each system.
+
+[Sentinel's built-in test framework](/sentinel/writing/testing) has zero
+dependencies. Contained within the [Sentinel CLI](/sentinel/commands), it can
+mock the data that real systems are exposing to the policy. It is designed to be
+CI-friendly and enables continuous testing of your policies. This is necessary
+for [policy as code](/sentinel/concepts/policy-as-code).
+
+Detailed documentation on testing policies is available in [the Sentinel Testing
+reference](/sentinel/writing/testing).
+
+## Writing Tests
+
+For this example, save the following policy as `policy.sentinel`:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Next, let's write a passing test case. This will test that the policy tests
+when we expect it to pass. Save the following in `test/policy/good.hcl`:
+
+```hcl
+global "day" {
+ value = "monday"
+}
+
+global "hour" {
+ value = 14
+}
+```
+
+And run `sentinel test`:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/good.json
+```
+
+The `sentinel test` command will automatically find all Sentinel policies
+and their associated test cases and run them all. The test framework will
+run all HCL files as individual test cases, allowing you to test a variety
+of scenarios for your policies.
+
+Try adding another test case to force the policy to fail. This test case can
+be saved at `test/policy/fail.hcl`. For test cases with intentional
+failures, you'll need to use the [test assertions described in the testing
+reference](/sentinel/writing/testing#a-failing-test).
+
+Now that you have a good grasp of what Sentinel is and how to use it, please feel
+free to look through the
+[next steps](/sentinel/intro/getting-started/next-steps)
+you can take to further your Sentinel knowledge.
diff --git a/content/sentinel/v0.19.x/content/sentinel/intro/index.mdx b/content/sentinel/v0.19.x/content/sentinel/intro/index.mdx
new file mode 100644
index 0000000000..785a442fb8
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/intro/index.mdx
@@ -0,0 +1,32 @@
+---
+layout: intro
+page_title: Documentation
+sidebar_current: docs-home
+description: >-
+ Sentinel is a language and framework for policy built to be embedded in
+ existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+[introduction](/sentinel/intro) to understand what Sentinel is, how it
+compares to other software, and more.
+
+
diff --git a/content/sentinel/v0.19.x/content/sentinel/intro/what.mdx b/content/sentinel/v0.19.x/content/sentinel/intro/what.mdx
new file mode 100644
index 0000000000..36853d366c
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/intro/what.mdx
@@ -0,0 +1,64 @@
+---
+page_title: Introduction
+sidebar_current: intro-what
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: intro
+---
+
+# Introduction to Sentinel
+
+Welcome to the intro guide to Sentinel! This guide is the best
+place to start with Sentinel.
+
+If you are already familiar with the basics of Sentinel, the
+[documentation](/sentinel) provides a better reference
+guide for all available features as well as internals.
+
+## What is Sentinel?
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+Sentinel provides a language for writing policy and a workflow for developing
+and testing policies independent of the software they'll be written to. We
+also provide a framework for developers to build plugins that allow a
+Sentinel-enabled system to access external information to make policy
+decisions.
+
+Most systems today have some degree of access control. You are able to define
+identities and what they have access to. These ACL systems solve an immediate
+and necessary problem of locking down a system in very broad strokes. Sentinel
+is a reusable system for more advanced software policy decisions. Sentinel
+enables:
+
+- **Fine-Grained Policy**: Most ACL systems only enable coarse-grained
+ behaviors: "read", "write", etc. Sentinel enables fine-grained behavior such
+ as disallowing a certain API call when specific parameters are present.
+
+- **Logic-Based Policy**: You can write policy using full
+ conditional logic. For example, you may only allow a certain application behavior
+ on Monday to Thursday unless there is a manager override.
+
+- **Accessing External Information**: Sentinel can source external information
+ to be used in policy decisions. For example, a policy that restricts the size
+ of a payload may read data from [Consul](https://www.consul.io) to determine
+ the payload size limit.
+
+- **Enforcement levels**: Sentinel allows policies to be defined along
+ with an "enforcement level" that dictates the pass/fail behavior of a policy.
+ Advisory policies warn if they fail, soft mandatory policies can have their
+ failures overridden, and hard mandatory policies must pass under all
+ circumstances. Having this as a built-in concept enables you to model
+ policy more accurately and completely for your organization.
+
+## Next Steps
+
+See the page on [Why Sentinel?](/sentinel/intro/why) to learn more
+about the origins of the product. Then, continue onwards with
+the [getting started guide](/sentinel/intro/getting-started/install) to write
+your first policies and see how Sentinel works in practice.
diff --git a/content/sentinel/v0.19.x/content/sentinel/intro/why.mdx b/content/sentinel/v0.19.x/content/sentinel/intro/why.mdx
new file mode 100644
index 0000000000..98b58aab92
--- /dev/null
+++ b/content/sentinel/v0.19.x/content/sentinel/intro/why.mdx
@@ -0,0 +1,49 @@
+---
+page_title: Why Sentinel?
+sidebar_current: intro-why
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: intro
+---
+
+# Why Sentinel?
+
+The growth of infrastructure and applications has been enabled in part
+by an increasing trend towards automation everywhere.
+Configuration as code (such as Chef or Puppet with [Packer](https://www.packer.io))
+has enabled automated machine configuration. Infrastructure
+as code (such as [Terraform](https://www.terraform.io)) has enabled
+automatic infrastructure creation. And schedulers (such as
+[Nomad](https://www.nomadproject.io) and Kubernetes) have enabled
+automatic application deployment.
+Sentinel enables guardrails to be put in place
+on automation while allowing the codification and automatic enforcement
+of business requirements in critical areas of your infrastructure.
+
+Meanwhile, businesses have business requirements and sometimes legal
+requirements which must be expressed in policies. Traditionally, these
+policies are enforced by humans. But in a highly automated world, the
+automation is only as fast as its slowest component. In many cases, this
+is the human verification step.
+
+As an example: before infrastructure as code and autoscaling, if an order
+came through for 5,000 new machines, a human would likely respond to the
+ticket verifying that the user really intended to order 5,000 new machines.
+Today, automation can almost always freely order 5,000 new compute instances
+without any hesitation, which can result in unintended expense or system
+instability.
+
+Sentinel introduces [policy as code](/sentinel/concepts/policy-as-code)
+and a powerful framework built-in to HashiCorp tooling to allow automation
+guardrails, business requirements, legal compliance, and more to be
+actively enforced by the running systems in realtime.
+
+With Sentinel, you can require an override to create certain numbers of
+infrastructure resources. You can disallow unsafe deployment configurations
+with Nomad. You can enforce certain key/value formats in Consul. You
+can restrict secret access by time in Vault. And more.
+
+Sentinel is available today in HashiCorp enterprise products. You can
+try Sentinel immediately with the [getting started guide](/sentinel/intro/getting-started/install) or learn more about the integrations in the
+[documentation](/sentinel).
diff --git a/content/sentinel/v0.19.x/data/docs-nav-data.json b/content/sentinel/v0.19.x/data/docs-nav-data.json
new file mode 100644
index 0000000000..1e281f044a
--- /dev/null
+++ b/content/sentinel/v0.19.x/data/docs-nav-data.json
@@ -0,0 +1,324 @@
+[
+ {
+ "title": "Release Notes",
+ "path": "changelog"
+ },
+ {
+ "title": "Basic Concepts",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "concepts"
+ },
+ {
+ "title": "Policy as Code",
+ "path": "concepts/policy-as-code"
+ },
+ {
+ "title": "Policy Language",
+ "path": "concepts/language"
+ },
+ {
+ "title": "Imports",
+ "path": "concepts/imports"
+ },
+ {
+ "title": "Enforcement Levels",
+ "path": "concepts/enforcement-levels"
+ }
+ ]
+ },
+ {
+ "title": "Configuration File Syntax",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "configuration"
+ },
+ {
+ "title": "Override Files",
+ "path": "configuration/overrides"
+ },
+ {
+ "title": "Remote Sources",
+ "path": "configuration/remote-sources"
+ }
+ ]
+ },
+ {
+ "title": "Commands (CLI)",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "commands"
+ },
+ {
+ "title": "apply",
+ "path": "commands/apply"
+ },
+ {
+ "title": "fmt",
+ "path": "commands/fmt"
+ },
+ {
+ "title": "test",
+ "path": "commands/test"
+ }
+ ]
+ },
+ {
+ "title": "Writing Policy",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "writing"
+ },
+ {
+ "title": "Basics",
+ "path": "writing/basic"
+ },
+ {
+ "title": "Rules",
+ "path": "writing/rules"
+ },
+ {
+ "title": "Traces",
+ "path": "writing/tracing"
+ },
+ {
+ "title": "Testing",
+ "path": "writing/testing"
+ },
+ {
+ "title": "Imports",
+ "path": "writing/imports"
+ },
+ {
+ "title": "Debugging",
+ "path": "writing/debugging"
+ }
+ ]
+ },
+ {
+ "title": "Extending Sentinel",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "extending"
+ },
+ {
+ "title": "Modules",
+ "path": "extending/modules"
+ },
+ {
+ "title": "Plugins",
+ "path": "extending/plugins"
+ },
+ {
+ "title": "Static Imports",
+ "path": "extending/static-imports"
+ },
+ {
+ "title": "Internals",
+ "path": "extending/internals"
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Language",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "language"
+ },
+ {
+ "title": "Variables",
+ "path": "language/variables"
+ },
+ {
+ "title": "Values",
+ "path": "language/values"
+ },
+ {
+ "title": "Lists",
+ "path": "language/lists"
+ },
+ {
+ "title": "Maps",
+ "path": "language/maps"
+ },
+ {
+ "title": "Rules",
+ "path": "language/rules"
+ },
+ {
+ "title": "Imports",
+ "path": "language/imports"
+ },
+ {
+ "title": "Parameters",
+ "path": "language/parameters"
+ },
+ {
+ "title": "Boolean Expressions",
+ "path": "language/boolexpr"
+ },
+ {
+ "title": "Arithmetic",
+ "path": "language/arithmetic"
+ },
+ {
+ "title": "Slices",
+ "path": "language/slices"
+ },
+ {
+ "title": "Conditionals",
+ "path": "language/conditionals"
+ },
+ {
+ "title": "Loops",
+ "path": "language/loops"
+ },
+ {
+ "title": "Collection Operations",
+ "path": "language/collection-operations"
+ },
+ {
+ "title": "Functions",
+ "path": "language/functions"
+ },
+ {
+ "title": "Scope",
+ "path": "language/scope"
+ },
+ {
+ "title": "Undefined",
+ "path": "language/undefined"
+ },
+ {
+ "title": "Logging and Errors",
+ "path": "language/logging-errors"
+ },
+ {
+ "title": "Specification",
+ "path": "language/spec"
+ }
+ ]
+ },
+ {
+ "title": "Builtin Functions",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "functions"
+ },
+ {
+ "title": "append",
+ "path": "functions/append"
+ },
+ {
+ "title": "delete",
+ "path": "functions/delete"
+ },
+ {
+ "title": "error",
+ "path": "functions/error"
+ },
+ {
+ "title": "keys",
+ "path": "functions/keys"
+ },
+ {
+ "title": "length",
+ "path": "functions/length"
+ },
+ {
+ "title": "print",
+ "path": "functions/print"
+ },
+ {
+ "title": "range",
+ "path": "functions/range"
+ },
+ {
+ "title": "values",
+ "path": "functions/values"
+ }
+ ]
+ },
+ {
+ "title": "Standard Imports",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "imports"
+ },
+ {
+ "title": "base64",
+ "path": "imports/base64"
+ },
+ {
+ "title": "decimal",
+ "path": "imports/decimal"
+ },
+ {
+ "title": "http",
+ "path": "imports/http"
+ },
+ {
+ "title": "json",
+ "path": "imports/json"
+ },
+ {
+ "title": "runtime",
+ "path": "imports/runtime"
+ },
+ {
+ "title": "sockaddr",
+ "path": "imports/sockaddr"
+ },
+ {
+ "title": "strings",
+ "path": "imports/strings"
+ },
+ {
+ "title": "time",
+ "path": "imports/time"
+ },
+ {
+ "title": "types",
+ "path": "imports/types"
+ },
+ {
+ "title": "units",
+ "path": "imports/units"
+ },
+ {
+ "title": "version",
+ "path": "imports/version"
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Consul",
+ "path": "consul"
+ },
+ {
+ "title": "Nomad",
+ "path": "nomad"
+ },
+ {
+ "title": "Terraform",
+ "path": "terraform"
+ },
+ {
+ "title": "Vault",
+ "path": "vault"
+ }
+]
diff --git a/content/sentinel/v0.19.x/data/intro-nav-data.json b/content/sentinel/v0.19.x/data/intro-nav-data.json
new file mode 100644
index 0000000000..c95181af70
--- /dev/null
+++ b/content/sentinel/v0.19.x/data/intro-nav-data.json
@@ -0,0 +1,47 @@
+[
+ {
+ "title": "What is Sentinel?",
+ "path": "what"
+ },
+ {
+ "title": "Why Sentinel?",
+ "path": "why"
+ },
+ {
+ "title": "Getting Started",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "getting-started"
+ },
+ {
+ "title": "Installing the CLI",
+ "path": "getting-started/install"
+ },
+ {
+ "title": "Your First Policy",
+ "path": "getting-started/first-policy"
+ },
+ {
+ "title": "Rules",
+ "path": "getting-started/rules"
+ },
+ {
+ "title": "Logic",
+ "path": "getting-started/logic"
+ },
+ {
+ "title": "Imports",
+ "path": "getting-started/imports"
+ },
+ {
+ "title": "Testing",
+ "path": "getting-started/testing"
+ },
+ {
+ "title": "Next Steps",
+ "path": "getting-started/next-steps"
+ }
+ ]
+ }
+]
diff --git a/content/sentinel/v0.19.x/img/sentinel-import-topology.svg b/content/sentinel/v0.19.x/img/sentinel-import-topology.svg
new file mode 100644
index 0000000000..36f7c79bc9
--- /dev/null
+++ b/content/sentinel/v0.19.x/img/sentinel-import-topology.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/content/sentinel/v0.19.x/redirects.jsonc b/content/sentinel/v0.19.x/redirects.jsonc
new file mode 100644
index 0000000000..d9e73f4a49
--- /dev/null
+++ b/content/sentinel/v0.19.x/redirects.jsonc
@@ -0,0 +1,15 @@
+[
+ {
+ "source": "/",
+ "destination": "/sentinel",
+ "permanent": true,
+ },
+ {
+ "source": "/sentinel/commands/config",
+ "destination": "/sentinel/configuration",
+ "permanent": true,
+ },
+ // disallow ".html" or "/index.html" in favor of cleaner, simpler paths
+ { "source": "/:path*/index", "destination": "/:path*", "permanent": true },
+ { "source": "/:path*.html", "destination": "/:path*", "permanent": true },
+]
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/changelog.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/changelog.mdx
new file mode 100644
index 0000000000..0b60b032c6
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/changelog.mdx
@@ -0,0 +1,757 @@
+---
+page_title: Sentinel Runtime Release Notes
+sidebar_current: docs-changelog
+description: >-
+ This are the public release notes for the Sentinel runtime. See this document
+ for the latest updates.
+layout: docs
+---
+
+# Sentinel Runtime Release Notes
+
+These are the release notes for the Sentinel runtime.
+
+Each version of the runtime is released with a corresponding version of
+[Sentinel CLI](/sentinel/commands). To download the CLI, see the [downloads
+page](/sentinel/downloads).
+
+Sentinel integrations and embedded runtimes may not always have the latest
+version installed, depending on the product's individual release cycle. For more
+information, contact the support team for your specific integration.
+
+
+## 0.20.0 (February 16, 2023)
+
+ENHANCEMENTS:
+
+- `cmd/testcmd`: Allows sentinel tests to run concurrently by default. Sequential style testing can
+ be enabled by running with the `-maxConcurrency=1` option.
+- `cmd/testcmd`: Allows sentinel test command to timeout after a certain duration. This can be provided
+ by the user or will default to 5 minutes.
+- `cmd/apply`: The policy enforcement level is now included in the JSON output.
+- `lang/ast`: Functions can now be declared as named statements, providing
+ a safer function declaration.
+
+BREAKING CHANGES:
+
+- `cmd/apply`: Policies provided directly to the apply command will now default their enforcement
+ level to `advisory`, aligning with the `policy` configuration block.
+- `sentinel`: JSON results will no longer return `allowed_failure` or `can_override` fields.
+- `sentinel/result`: A new package has been added which provides additional methods to return
+ supplemental data about the evaluation result.
+
+## 0.19.5 (February 9, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `SelectorExpr` will correctly rewrite `X`.
+
+## 0.19.4 (February 8, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `Compile` function will correctly set the `Original`
+ field when rewriting a `CallExpr` as an `ImportExpr`.
+
+## 0.19.3 (February 3, 2023)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.19.2 (January 17, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `Compile` function will no longer modify `ast.CallExpr`
+ arguments in place.
+
+## 0.19.1 (December 14, 2022)
+
+BUG FIXES:
+
+- `lang/ast`: The `Rewrite` function will no longer modify the provided Node
+ in place.
+
+ENHANCEMENTS:
+
+- `lang/semantic`: The Break semantic check uses the AST walker.
+
+## 0.19.0 (December 6, 2022)
+
+BREAKING CHANGES:
+
+- `config`: Attempts to override a standard import with a custom import will
+ now be met with an error.
+- `imports`: All plugin imports now return `sdk.Plugin` instead of `sdk.Import`,
+ as per the SDK update to v0.4.0.
+
+ENHANCEMENTS:
+
+- `runtime/importer`: An `Import` and `ImportInstance` interface have been
+ added to improve the import capabilities.
+- `runtime/importer`: The `Importer` interface now returns a `Import` interface
+ instead of a `sdk.Import`.
+- `lang/semantic`: The Import Assignment semantic check uses the AST walker.
+- `lang/ast`: Added an AST Walker so the AST rewriter does not need to be used for basic AST tasks.
+
+FEATURES:
+
+- `config`: Configuration syntax now allows for configuration of imports within
+ the standard library.
+- `config`: A new configuration syntax for defining modules and plugins is now
+ available.
+- Static data can now be added via `import` configuration.
+- `cmd/apply`: The `apply` command now supports multiple primary configuration
+ files by default, loading all configuration files within the working
+ directory.
+- `cmd/apply`: The `apply` command now accepts applying configuration overrides
+ to primary configuration.
+
+## 0.18.13 (October 31, 2022)
+
+BUG FIXES:
+
+- `cmd/apply`: `sentinel apply` no longer aborts on advisory policy failure.
+
+## 0.18.12 (September 15, 2022)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where nested `CallExpr` were not rewritten.
+
+## 0.18.11 (June 8, 2022)
+
+NOTES:
+
+- `config`: The `enforcement_level` key for policies is now optional, defaulting to `"advisory"` when not supplied.
+
+## 0.18.10 (May 20, 2022)
+
+NOTES:
+
+- `runtime/initwd`: Improvements to the installer, including timeouts and filesize limits, as well as disabling support
+ for symlinks within `git` or `hg` repositories.
+
+## 0.18.9 (March 23, 2022)
+
+NOTES:
+
+- This release fixes an issue with the Docker container build pipeline. There are no changes to the
+Sentinel runtime or CLI.
+
+## 0.18.8 (March 22, 2022)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue in the installer for Windows paths.
+
+## 0.18.7 (February 24, 2022)
+
+NOTES:
+
+- **Darwin arm64**. This release will be our first to include Darwin arm64 architecture.
+
+BUG FIXES:
+
+- `lang/ast`: Fixed issues where `IndexSpec` and `RuleLit` nodes where returning incorrect end positions.
+- `lang/ast`: Fixed an issue where `ParamSpec` nodes where returning incorrect end positions.
+
+## 0.18.6 (February 2, 2022)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.18.5 (January 14, 2022)
+
+IMPROVEMENTS:
+
+- `runtime/initwd`: Changed the sum algorithm used during remote installation from `md5` to `sha256` to reduce chance of collision.
+
+## 0.18.4 (July 20, 2021)
+
+FEATURES:
+
+- `imports/static`: Improved handling of struct tags when performing encoding, allowing `-` to mark as ignored.
+
+## 0.18.3 (June 1, 2021)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue for remote source installation where duplicate sub-directories resulted in incorrect installation.
+- `imports/static`: Fixed an issue where global configuration that contained an empty map could not be accessed without raising selector issues.
+
+## 0.18.2 (May 18, 2021)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where import expressions inside lists and maps were not being evaluated correctly.
+
+## 0.18.1 (May 11, 2021)
+
+BUG FIXES:
+
+- `imports/json`: Fixed an issue where empty maps where failing when passed at any level to `marshal`.
+
+## 0.18.0 (March 25, 2021)
+
+FEATURES:
+
+- `imports/http`: Added support for POST actions within the `http` import
+ through addition of the `post` function. Additionally, support for adding
+ a body to the `request` object has been added via `with_body`.
+
+## 0.17.4 (February 2, 2021)
+
+BUG FIXES:
+
+- `runtime/format`: Fixed a nesting issue with the rule printer where nesting
+ state leaks were leading to all collection values eventually being truncated,
+ regardless of their nesting level.
+
+## 0.17.3 (January 15, 2021)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue where local file installation failed on Windows
+ when using `sentinel test`.
+
+## 0.17.2 (January 13, 2021)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where duplicate log messages were being printed with
+ `sentinel test`.
+
+## 0.17.1 (January 7, 2021)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where hard-coded path separator caused `sentinel
+ test` issues in Windows.
+
+## 0.17.0 (January 5, 2021)
+
+LANGUAGE CHANGES:
+
+- **Map Expression** `map`. A new quantifier expression, `map` has been added.
+ `map` takes a collection and applies an operation to each element in the
+ collection, returning a list of results with the resulting values.
+- **Emptiness checks** `is empty` and `is not empty`. Two new expressions,
+ `is empty` and `is not empty` have been added. As they are aliases to the
+ `length` built-in, only `string`, `map`, `list` or `undefined` is
+ supported.
+- **Comparable maps** Maps (the data type) are now comparable. Maps are equal if
+ they are of equal length and both their corresponding keys and values are
+ comparable and equal.
+- **Rich Return Types** Rules can now process and return non-boolean data.
+ Scalar, list, and map types are supported. When non-boolean values are used,
+ the presence of a non-zero or non-zero-length `main` rule determines policy
+ failure. More details can be found on
+ https://docs.hashicorp.com/sentinel/language#main.
+
+FEATURES:
+
+- **CLI**: `sentinel apply` and `sentinel test` now have options for JSON
+ output. For more details, see the CLI documentation.
+- `imports/base64`: Added a new import, `base64`, to assist with encoding and
+ decoding base64 strings.
+
+## 0.16.1 (October 21, 2020)
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where `contains` to ensure any instance of `undefined` would correctly result in `undefined`.
+- `runtime/eval`: Fixed an issue in `any` and `all` expressions to ensure correct boolean logic when dealing with a collection containing `undefined`.
+- `imports`: Fixed an issue where import processes were not killed, which caused non-graceful closures for the clients.
+- `lang/scanner`: Fixed an issue where inline `#` style comments were not being consumed.
+
+## 0.16.0 (October 14, 2020)
+
+BREAKING CHANGES:
+
+- `cmd/doc`: Removal of the deprecated `sentinel doc` command.
+- `sentinel`: Replace `whitelist` and `blacklist` with `allowlist` and `denylist`, as per inclusive language changes.
+
+FEATURES:
+
+- `imports/version`: Added a new import, `version`, which supplies helpers for dealing with semantic versioning.
+- `cmd/apply`: Added an `HCL` based configuration file, which will eventually deprecate the legacy `JSON` configuration.
+- `cmd/apply`: Added support for download remote policies and modules.
+- `cmd/test`: Added support for downloading remote test modules.
+- `cmd/apply`: Added support for evaluating a policy based on it's key within the configuration.
+- `cmd/apply`: Added support for running all policies in a configuration by default.
+
+## 0.15.6 (July 7, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.15.5 (May 20, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel integrations. There are no user-facing changes.
+
+## 0.15.4 (May 13, 2020)
+
+BUG FIXES:
+
+- `imports`: Fixed an issue with the standard imports that was affecting the handling of null data within lists.
+
+## 0.15.3 (April 16, 2020)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where `IndexExpr` were not rewritten, as well as ensuring `SelectorExpr` uses any nested rewrites.
+- `lang/printer`: Fixed issue printing deeply nested structures and/or loops.
+
+IMPROVEMENTS:
+
+- `imports/decimal`: Added alias methods to improve consistency of method names within the `decimal` import. The alias methods are `is_nan` for `isnan`, as well as both `is_inf` and `is_infinite` for `isinfinite`.
+- `command/apply`: `sentinel apply` will now output the policy description when a trace is forced via `-trace`.
+- `sentinel`: Improved `String` output formatting of Policy docstring for `EvalPolicyResult`.
+
+## 0.15.2 (April 2, 2020)
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue with the `printer` that was causing a panic
+ when a `#` line comment was used with no text following it.
+- `imports/types`: Fixed an issue with the `types` import where calls to
+ `type_of` for undefined types were incorrectly returning as map types. These
+ will now be correctly be identified as undefined as expected.
+
+## 0.15.1 (March 6, 2020)
+
+BUG FIXES:
+
+- `sentinel`: Fixed how modules are managed internally in the runtime to correct
+ race conditions in concurrent scenarios and to allow for per-evaluation module
+ restrictions. This functionality is only of interest to embedded applications
+ with long-lived runtimes and is currently not implemented in any HashiCorp
+ integration.
+
+## 0.15.0 (March 5, 2020)
+
+NOTES:
+
+- **Windows Digital Signature**. Our windows releases for this version and up will be signed
+ and verified according to Microsoft's requirements.
+
+FEATURES:
+
+- `cmd/config`: Modules can now be loaded off of local disk using the Sentinel
+ CLI. For information on how to do so, see the Sentinel documentation.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: Changed modules so that they now have singleton scope when
+ loaded. Importing a module under two different aliases, or within a nested
+ module, will now share scopes - modification of state in one will alter the
+ other.
+- `lang/object`: Introduced an interface to allow the duplication of various
+ types, like collections.
+- `runtime/eval`: Changed how collection data is returned from modules. This
+ data is now duplicated to prevent accidental or deliberate circumvention of
+ the prohibition on assignment to import data, and brings behavior to parity
+ with binary imports.
+
+## 0.14.4 (February 6, 2020)
+
+IMPROVEMENTS:
+
+- `imports/time`: Changed the `add` timespace function in the `time` import to
+ allow it to take float types as durations. These values will be truncated to
+ the appropriate integer-value duration.
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where out-of-place right-hand braces were being
+ parsed as empty statements, instead of raising syntax errors.
+
+## 0.14.3 (January 22, 2020)
+
+IMPROVEMENTS:
+
+- `cmd/test`: Fail when an assertion is not found in a trace.
+- `cmd/test`: Added a message to notify when no rules were evaluated in a test.
+
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue where import aliases (the `as baz` in `import
+ "foo/bar" as baz`) were being removed when formatting with `sentinel fmt`.
+
+- `imports/http`: Fixed an issue with the http import where the client library's
+ debug logs were being printed to standard error.
+
+## 0.14.2 (January 15, 2020)
+
+NOTES:
+
+With this release, we are discontinuing support for MacOS 32-bit. 64-bit builds
+are now the only builds available for MacOS.
+
+IMPROVEMENTS:
+
+- `lang/printer`: A newline is now added to files formatted via `sentinel fmt`.
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue in printing where multi-line expressions would
+ indent infinitely. They will now only indent once at the most. This is mostly
+ seen when using `sentinel fmt`.
+- `runtime/encoding`: Fixed an issue where if a plugin returned a deeply
+ embedded undefined value, the value was instead decoded into a map.
+
+## 0.14.1 (January 6, 2020)
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where reserved words could not be used as
+ selectors when the selector expression was the last lexeme on a line.
+
+## 0.14.0 (December 18, 2019)
+
+LANGUAGE CHANGES:
+
+- **Filter Expression** `filter`. A new quantifier expression, `filter` has been added.
+ `filter` returns a subset of the provided collection based on a boolean condition that
+ is asserted for each element.
+
+NOTES:
+
+- **Apple Notarization**. Our darwin releases for this version and up will be signed
+ and notarized according to Apple's requirements.
+
+## 0.13.1 (November 25, 2019)
+
+BUG FIXES:
+
+- `runtime/eval`: Parameters are no longer allowed in mock files. Adding one to
+ a mock will result in a runtime error.
+- `runtime/eval`: Fixed an issue where import calls from within mocks were
+ failing under certain circumstances.
+- `imports/decimal`: `int` should now correctly provide the truncated integer
+ representation of a decimal number, not the rounded one.
+
+## 0.13.0 (November 15, 2019)
+
+LANGUAGE CHANGES:
+
+- **Policy Parameters**. The new [policy
+ parameters](https://docs.hashicorp.com/sentinel/language/parameters) feature
+ allows authors to use a `param` declaration at the top of a policy to supply
+ values that are expected to come from outside of a policy. See the linked
+ documentation for more details.
+
+FEATURES:
+
+- `imports/http`: The [`http`
+ import](https://docs.hashicorp.com/sentinel/imports/http) is a new addition
+ to the standard library enabling the use of HTTP-accessible data from outside
+ the runtime in policy rules.
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where loading other imports before a mocked
+ import would cause those imports to no longer be visible from within the
+ policy.
+
+## 0.12.0 (October 7, 2019)
+
+LANGUAGE CHANGES:
+
+- **New `case` statement**. This statement is a selection control mechanism to
+ conditionally execute a branch based on expression equality, allowing
+ simplification of complex conditional chains that may otherwise need to be
+ written with `else if`. See the [Case
+ Statements](https://docs.hashicorp.com/sentinel/language/spec#case-statements)
+ section of the Sentinel language specification for more details.
+
+IMPROVEMENTS:
+
+- `lang/semantic`: Added a semantic check to ensure usage of append is not using
+ a return value.
+
+BUG FIXES:
+
+- `runtime/eval`: Corrected an issue where calling a method on an import object
+ value that was the result of a method call on another import object value
+ would have erroneously tried to call an import of the name of the "parent"
+ import object value. Example: in `a = subject.new(); b = a.call(); c =
+ b.call()`, `b.call()` would attempt to call a method named `call` on the root
+ namespace for an import named `a`. This has now been corrected so that
+ `b.call()` will now correctly call the `call` method for the respective object
+ namespace residing in the import named `subject`.
+- `imports`: Some standard imports may have been returning null for some unknown
+ keys in objects when they should have been returning undefined. This was due
+ to an SDK issue which was corrected in SDK version 0.3.2, which has now been
+ corrected.
+- `cmd/test`: `sentinel test` will now correctly fail a policy if it encounters
+ an error.
+- `cmd/test`: `sentinel test` will now correctly display errors and other output
+ that were missing due to a formatting issue.
+- `runtime/eval`: The `append` builtin now correctly returns undefined for all
+ calls, as called for by the Specification. Note that in most cases, the
+ semantic check outlined above will trigger an error if the return value is
+ used.
+
+## 0.11.0 (September 5, 2019)
+
+LANGUAGE CHANGES:
+
+- **New builtin function:** `range()`. This function existed in the spec in
+ earlier versions but was removed as it lacked an implementation. This has now
+ been implemented and re-added to the spec. See the
+ [Range](https://docs.hashicorp.com/sentinel/language/spec#range) section of
+ the Sentinel Specification for more details.
+- Lists are now comparable. Lists are equal if their corresponding elements are
+ comparable and equal.
+- Method calls on values returned by imports are now supported. See the
+ [Imports](https://docs.hashicorp.com/sentinel/language/spec#imports) section
+ of the Sentinel Specification for more details.
+
+FEATURES:
+
+- `imports/decimal`: This is a new import designed to do exact precision
+ mathematical calculations.
+- `runtime/eval`: Compound call expressions that refer to imports (example:
+ foo.bar().baz() when `foo` is a loaded import) will now function as expected.
+ Previously, this was only supported up to the first call (example:
+ `foo.bar()`).
+- `imports/time`: Timespaces returned by calls such as `time.now` are now
+ callable. Example: `t = time.now; t.after(some_previous_time)` will now
+ function.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: The implementation of comparison of non-comparable types has
+ now changed. Rather than triggering a runtime error, non-comparable types will
+ now return false when attempting to compare.
+
+## 0.10.4 (August 15, 2019)
+
+BUG FIXES:
+
+- `runtime/encoding`: Fixed an issue with conversion of null values that could
+ lead to crashes in imports.
+
+## 0.10.3 (July 15, 2019)
+
+BUG FIXES:
+
+- `command/config`: Parsing a configuration file where malformed data is
+ encountered after apparently properly-formed data will now report an error. An
+ example would be a situation where misplaced braces would cause a JSON object
+ to only parse part of the configuration file.
+
+## 0.10.2 (June 25, 2019)
+
+BUG FIXES:
+
+- `lang/parser`: Corrected an issue where parsing certain compound binary
+ expressions where the negation predicate (`not`) was in use would cause the
+ negation to have no effect. Example: `foo else "bar" not in "baz"` would have
+ been parsed and evaluated as `foo else "bar" in "baz"`, effectively producing
+ the opposite result.
+
+## 0.10.1 (May 9, 2019)
+
+BUG FIXES:
+
+- `command/test`: `sentinel test` now displays policies without
+tests as an unknown result with [no test files], instead of the somewhat
+erroneous behavior of displaying it as a PASS. A full test run with a mixture
+of passing tests and no tests still results in an overall successful result.
+
+## 0.10.0 (April 18, 2019)
+
+LANGUAGE CHANGES:
+
+- Mixed-number arithmetic operations are now allowed. Addition (`+`),
+ subtraction (`-`), multiplication (`*`), division (`/`), and remainder
+ operations (`%`) are no longer restricted to number values of the same
+ type, and can mix integer and floating point. The result of these operations
+ is always a floating-point number.
+- Remainder (modulo, `%`) operations are now allowed on floating-point numbers.
+
+
+BUG FIXES:
+
+- `lang/parser`: Fixed a bug that affected the use of `not` with `contains`,
+ `matches`, or `in` in a compound expression.
+- `runtime/eval`: Fixed error messages on evaluation errors with `contains` and
+ `in` to indicate that strings are an allowed type along with lists and maps.
+
+
+## 0.9.2 (March 15, 2019)
+
+This is a dependency update related to the changes mentioned in 0.9.1. No other
+changes have been made.
+
+## 0.9.1 (March 14, 2019)
+
+This is a patch release that is required to integrate with the latest versions
+of the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). No other
+changes have been made.
+
+## 0.9.0 (January 28, 2019)
+
+FEATURES:
+
+- `imports/strings`: Added a `join` function to the `strings` import. This can
+ be used to join a list into a string with a specific separator.
+ Multi-dimensional lists and all Sentinel primitives are supported.
+
+## 0.8.1 (January 17, 2019)
+
+BUG FIXES:
+
+- `lang/eval`: Fixed a bug that prevents the effective use of `return`
+ statements in `for` loops.
+
+## 0.8.0 (January 14, 2019)
+
+FEATURES:
+
+- Mocks can now be represented by Sentinel code. This allows for the mocking of
+ functions and other complex data structures that cannot be represented in
+ JSON. For more information on using this feature via mocks, click
+ [here](https://docs.hashicorp.com/sentinel/commands/config#mocking-with-sentinel-code).
+
+
+IMPROVEMENTS:
+
+- Import validation has now been moved to the semantic checking phase. This
+ should result in better reporting of validation errors. In addition, import
+ validation will now enforce the use of an `as` identifier when an import path
+ is not a valid identifier on its own (example: `import "foo/bar"`).
+
+## 0.7.0 (December 12, 2018)
+
+BUG FIXES:
+
+- There have been changes to the runtime in how scope is handled over multiple
+ policy executions. Scope is now correctly unique per single policy execution,
+ and values set or builtins that are overridden in one policy will no longer
+ affect those values within another.
+
+## 0.6.0 (November 30, 2018)
+
+FEATURES:
+
+- `imports/runtime`: This new import allows for one to check various aspects of
+ the Sentinel runtime as it may be embedded in the simulator or a specific
+ implementation. For now, it allows the version to be checked.
+
+## 0.5.1 (November 28, 2018)
+
+IMPROVEMENTS:
+
+- `imports/time`: Added the `zone` and `zone_string` attributes to assist with
+ validation of a timespace's zone.
+- `command/fmt`: Added a new -check flag. This option does not commit changes,
+ but instead checks to see what files need formatting and outputs them on
+ stdout.
+
+BUG FIXES:
+
+- `command/test`: Ensure that passing test results are correctly output one per
+ line. Tests are also now run in a deterministic fashion based on
+ lexicographical (alphabetical) order.
+- `imports/time`: `month_name` and `weekday_name` will now show up correctly in
+ a returned timespace result.
+
+
+## 0.5.0 (November 5, 2018)
+
+IMPROVEMENTS:
+
+- `spec`: Selectors can now contain any reserved word (example: `rule`) or
+ keyword operator (example: `any`, `all`, `is`, `not`). This only works for the
+ _selector_ part of the expression (after the first period) - the first primary
+ expression (before the first period) still needs to be an identifier that does
+ not conflict with reserved words.
+
+BUG FIXES:
+
+- The simulator should now display import function call names correctly in
+ import errors.
+
+## 0.4.0 (October 1, 2018)
+
+FEATURES:
+
+- `builtin`: Added the `bool` built-in type conversion function. Booleans will
+ also now accepted as conversion into other values as well, with the full list
+ of behaviors available in the spec.
+
+## 0.3.2 (September 27, 2018)
+
+FEATURES:
+
+- `command/apply`: `sentinel apply` now prints out messages output by the
+ `print()` function when a trace is output on policy failure, or when a trace
+ is forced with `-trace`.
+- `imports/time`: Added the `month_name` and `weekday_name` keys to the
+ timespace, which return full-English names for the month and day of the week.
+
+
+BUG FIXES:
+
+- `command/fmt`: `sentinel fmt -` Will no longer print out the filter status
+ message on the output stream when `-write=false` Is not explicitly stated.
+ This brings the behavior of the command in line with the help text.
+- `runtime`: Index operations on the right-hand-side that have negative indexes
+ that go out of range (example: `length(list) * -1 - 1`) now correctly return
+ `undefined`. left-hand-side index assignments with a out-of-range negative
+ index still return runtime errors.
+
+## 0.3.1 (August 3, 2018)
+
+BUG FIXES:
+
+- `runtime`: Basic index assignment has been implemented as per the spec.
+- `runtime`: Index expressions for lists with negative indexes will no longer panic if the
+ list index is less than `length(list) * -1`.
+
+## 0.3.0 (July 20, 2018)
+
+FEATURES:
+
+- **New standard import: `types`.** This can be used to dynamicaly detect the
+ type of some value.
+
+## 0.2.0 (April 11, 2018)
+
+FEATURES:
+
+- **New standard import: `json`.** Marshal and unmarshal JSON documents and
+ access their contents as native Sentinel values.
+- **`break` and `continue`.** These are now both specified and implemented.
+ `break` allows loop exiting and `continue` allows immediate execution of the
+ next iteration.
+
+IMPROVEMENTS:
+
+- runtime: `print()` map values are now ordered alphabetically by keys.
+
+BUG FIXES:
+
+- command/test: If no `test` block exists, test behaves like it is asserting
+ `main: true`.
+- runtime: default maximum stack depth to 500
+- runtime: `print()` map values now appear like more typical maps.
+- runtime: division by zero is an error, not a crash
+- runtime: plugins that send map values with `null` values now decode properly
+ into native Sentinel values.
+
+## 0.1.0 (September 19, 2017)
+
+Initial release.
+
+
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/commands/apply.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/commands/apply.mdx
new file mode 100644
index 0000000000..c5e81d6d65
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/commands/apply.mdx
@@ -0,0 +1,63 @@
+---
+page_title: 'Command: apply'
+sidebar_current: docs-commands-apply
+description: >-
+ The `sentinel apply` command is used to execute a policy locally for
+ development purposes.
+layout: docs
+---
+
+# Command: `apply`
+
+The `sentinel apply` command is used to execute a policy locally for development
+purposes.
+
+## Usage
+
+Usage: `sentinel apply [options] POLICY`
+
+This command executes the policy file at the path specified by POLICY. In
+addition to a path to a policy, POLICY can reference a label of a
+[policy](/sentinel/configuration#policies) entry in the configuration.
+
+Use the exit code of this command to determine the exact status of the policy
+evaluation. `0` is pass, `1` is fail, `2` is undefined (fail, but because the
+result was undefined), and `3` is a runtime error. Errors unrelated to the
+policy status itself are returned with an exit status of `9`.
+
+To control the behavior of the `apply` command, create a [configuration
+file](/sentinel/configuration). With this, you can define available
+[import plugins](/sentinel/concepts/imports), mock data, and global values.
+This can help you simulate a policy embedded within an application.
+
+As of the 0.16 release, POLICY is optional. When it is not supplied,
+`sentinel apply` will run **all** policies in the supplied configuration.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-config=path` - Path to a configuration file specifying available imports,
+ mock data, globals, etc. The default is `sentinel.[hcl|json]`.
+
+- `-json` Enable JSON output. Using this mode, all other output will be
+ suppressed and the full detail of the apply will be output in a
+ machine-readable format. Enabling this implies `-trace`. See the section on
+ [tracing JSON output](/sentinel/writing/tracing#json-output) for more details.
+
+- `-json-rule=RULE` Enable JSON output, outputting only the value of the
+ specified rule. When running against a policy set, the policy must be supplied
+ to enable per-rule filtering. Implies `-json`.
+
+- `-global key=value` - Set global values. This is the same as setting `global`
+ in the configuration file, and will override any of these respective values
+ set in the configuration. The value is either a string, or a JSON number,
+ array, or object. To force strings, use quotes.
+
+- `-param key=value` - Set parameters, the same as setting `param` in the
+ configuration file. Values are handled in the same way they are with the
+ `-global` flag.
+
+- `-trace` - Always show the execution trace. This shows intermediate
+ boolean expression values. This always shows for failed policies.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/commands/fmt.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/commands/fmt.mdx
new file mode 100644
index 0000000000..8297316d8b
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/commands/fmt.mdx
@@ -0,0 +1,33 @@
+---
+page_title: 'Command: fmt'
+sidebar_current: docs-commands-fmt
+description: The `sentinel fmt` command formats a policy source to a canonical format.
+layout: docs
+---
+
+# Command: `fmt`
+
+The `sentinel fmt` command formats a policy source to a canonical format.
+
+## Usage
+
+Usage: `sentinel fmt [options] FILE ...`
+
+This command formats all the specified policy files to a canonical format.
+
+By default, policy files are overwritten in place. This behavior can be
+changed with the `-write` flag. If a specified FILE is `-` then stdin is
+read and the output is always written to stdout.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-write=true` - Write formatted policy to the named source file. If false,
+ output will go to stdout. If multiple files are specified, the output will
+ be concatenated directly.
+
+- `-check=false` - Don't format, only check if formatting is necessary. Files
+ that require formatting are printed, and a non-zero exit code is returned if
+ changes are required.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/commands/index.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/commands/index.mdx
new file mode 100644
index 0000000000..7177093cec
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/commands/index.mdx
@@ -0,0 +1,23 @@
+---
+page_title: Commands (CLI)
+sidebar_current: docs-commands
+description: >-
+ Sentinel provides a `sentinel` command-line interface (CLI) for developing and
+ testing policies.
+layout: docs
+---
+
+# Sentinel CLI Commands
+
+The Sentinel command-line interface (CLI) allows for the developing and testing
+of policies outside of a particular Sentinel implementation. Having a standard
+workflow to develop policies is critical for our mission of [policy as
+code](/sentinel/concepts/policy-as-code).
+
+The CLI takes a subcommand to execute. The complete list of subcommands is in
+the navigation to the left.
+
+The Sentinel CLI is a well-behaved command line application. In erroneous cases,
+a non-zero exit status will be returned. It also responds to -h and --help as
+you'd expect. To view a list of the available commands at any time, just run
+`sentinel` with no arguments.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/commands/test.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/commands/test.mdx
new file mode 100644
index 0000000000..bc785c3725
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/commands/test.mdx
@@ -0,0 +1,52 @@
+---
+page_title: 'Command: test'
+sidebar_current: docs-commands-test
+description: The `sentinel test` command runs policies against the Sentinel test framework.
+layout: docs
+---
+
+# Command: `test`
+
+The `sentinel test` command runs policies against the Sentinel test framework.
+
+To learn more about testing, see the [testing
+policies](/sentinel/writing/testing) page.
+
+## Usage
+
+Usage: `sentinel test [options] [PATH] ...`
+
+This command runs the defined test cases against a set of policies.
+
+The test cases are expected to live at the path `test//*.[hcl|json]` relative
+to the policy being tested. `` should be the name of the policy
+excluding the file extension. The files should be in the [configuration
+file structure](/sentinel/configuration). The `test` key is used for
+assertions.
+
+The PATH arguments specified may be a list of zero or more files or directories.
+When no arguments are given, it defaults to the current directory. When a
+directory is given, all policies ending with the extension `.sentinel` are
+tested.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-json` - Suppress normal output and output test results as a JSON document.
+ See the [testing JSON output](/sentinel/writing/testing#test-json-output)
+ section for more details.
+
+- `-run=regexp` - Run only tests matching the regular expression pattern.
+ A `/` splits policy name and test case name.
+
+- `-verbose` - Show tracing and log output for tests that succeed in addition
+ to tests that fail. By default, traces and logs are only shown for tests that
+ fail.
+
+- `-maxConcurrency` - Allows users to specify the number of tests to be run concurrently.
+ Defaults to the number of logical CPUs if not provided. To run tests sequentially, use `-maxConcurrency=1`
+
+- `-timeout` - Allows users to specify a timeout after which the test command will stop running.
+Defaults to 5 minutes if not provided. The timeout needs to be specified as a Duration type, eg `100ms, 5s etc`
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/concepts/enforcement-levels.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/concepts/enforcement-levels.mdx
new file mode 100644
index 0000000000..fc4b8062d0
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/concepts/enforcement-levels.mdx
@@ -0,0 +1,43 @@
+---
+page_title: Enforcement Levels
+sidebar_current: docs-concepts-levels
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Enforcement Levels
+
+Enforcement levels are a first class concept in Sentinel allowing pass/fail
+behavior to be associated separately from the policy logic. This enables
+any policy to be a warning, allow overrides, or be absolutely mandatory.
+Because this level is not part of the policy body itself, different uses of
+the same policy can have different enforcement levels.
+
+Sentinel has three enforcement levels:
+
+- **Advisory:** The policy is allowed to fail. However, a warning should be
+ shown to the user or logged. Advisory is the default enforcement level.
+
+- **Soft Mandatory:** The policy must pass unless an override is specified.
+ The semantics of "override" are specific to each Sentinel-enabled application.
+ The purpose of this level is to provide a level of privilege separation
+ for a behavior. Additionally, the override provides non-repudiation since
+ at least the primary actor was explicitly overriding a failed policy.
+
+- **Hard Mandatory:** The policy must pass no matter what. The only way to
+ override a hard mandatory policy is to explicitly remove the policy.
+ It should be used in situations where an override is not possible.
+
+## Configuring Enforcement Levels
+
+Enforcement levels are configured when a policy is deployed to a
+Sentinel-enabled application. The exact mechanism that the level is specified
+is determined by each application. Please reference the documentation for
+your Sentinel-enabled application for more information.
+
+Enforcement levels are not configured and are not known by the policy body
+itself. All policies should be written to describe exactly the behavior
+they're attempting to control. For example, a policy that restricts deploys
+to business hours should be written exactly like so. When that policy is
+configured on an application, the operator may specify that it is advisory,
+soft, or hard mandatory.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/concepts/imports.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/concepts/imports.mdx
new file mode 100644
index 0000000000..1306e1512e
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/concepts/imports.mdx
@@ -0,0 +1,32 @@
+---
+page_title: Imports
+sidebar_current: docs-concepts-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom imports](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only
+local context for making policy decisions.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that the
+import calendar allows access to engineer calendars. This import only exists
+for the purpose of this example and at the time of writing is not a real
+available import.
+
+```json sentinel
+{
+ "policy": "import \"calendar\"\n// Get the calendar for Bob for today\nbob_calendar = calendar.of(\"bob\").today\n\n// Allow this policy to pass if Bob is not on vacation.\nmain = rule { not bob_calendar.has_event(\"vacation\") }",
+ "mocks": {
+ "calendar": "has_event = func(event) {\n\treturn true\n}\nof = func(name) {\n\treturn { \"today\": { \"has_event\": has_event } }\n}"
+ }
+}
+```
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/concepts/index.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/concepts/index.mdx
new file mode 100644
index 0000000000..ce7ac51454
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/concepts/index.mdx
@@ -0,0 +1,15 @@
+---
+page_title: Basic Concepts
+sidebar_current: docs-concepts
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Basic Concepts
+
+This section covers some high level basic concepts that are important
+to understand for day to day Sentinel usage. Every page in
+this section is recommended reading for anyone consuming
+or extending Sentinel.
+
+Please use the navigation to the left to learn more about a topic.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/concepts/language.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/concepts/language.mdx
new file mode 100644
index 0000000000..ce91f3eb8f
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/concepts/language.mdx
@@ -0,0 +1,91 @@
+---
+page_title: Policy Language
+sidebar_current: docs-concepts-language
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Policy Language
+
+Sentinel defines and uses its own [policy language](/sentinel/language).
+
+The language was designed to be approachable by non-programmers, since
+there are many use cases where the individual defining policy may not
+be a developer. However, the language includes constructs that
+are familiar to developers to enable powerful policies.
+
+To learn more about the language, please see the
+[writing policy section](/sentinel/writing)
+or the [language reference](/sentinel/language).
+
+An example of the policy language is shown below:
+
+```sentinel playground
+import "time"
+
+# Validate time is between 8 AM and 4 PM
+valid_time = rule { time.now.hour >= 8 and time.now.hour < 16 }
+
+# Validate day is M - Th
+valid_day = rule {
+ time.now.weekday_name in ["Monday", "Tuesday", "Wednesday", "Thursday"]
+}
+
+main = rule { valid_time and valid_day }
+```
+
+## Why?
+
+To explain why Sentinel defines and uses its own language, the question
+can be more easily split into roughly two types of languages: _configuration_ languages
+and _programming_ languages.
+
+### Configuration Languages
+
+Configuration languages are formats such as JSON, YAML, XML, etc. Many applications
+use configuration languages as their ACL format. Configuration languages
+are good for static, declarative information. ACL systems are a good fit
+for this. ACL systems are focused, providing the limiting options necessary
+to restrict access to a system.
+
+Configuration languages are not good for dynamic or logical rules. They
+typically only contain limited conditions, loops, or functions. Further
+configuration is often declarative, which can introduce complexities to
+logical statements that depend on ordering.
+
+The use cases that Sentinel was built for require the ability to perform
+complex behavior that didn't model well into a configuration format or a
+declarative form.
+
+### Programming Languages
+
+The Sentinel language was designed with the following goals:
+
+- **Non-programmer friendly.** Sentinel was built to be used by non-programmers.
+ For the use cases we discovered, non-programmers needed the ability to
+ enforce certain rules within a system. For example, a person responsible for
+ compliance may need to insert rules into a system.
+
+- **Programmer friendly.** At the same time, the language needed to support
+ programmer-friendly constructs such as conditionals, loops, and functions
+ for complex policies that a programmer may be writing.
+
+- **Embeddable.** Sentinel is embedded in existing software. The language
+ itself needs to be easily embeddedable. Further, Sentinel was designed
+ to be embedded in [HashiCorp](https://www.hashicorp.com) software
+ written in Go, so it needed to be easily embeddable in Go.
+
+- **Safe.** The language is used in highly security-sensitive environments.
+ It must not be able to crash its host system or access system resources
+ without explicit approval.
+
+Prior to building our own language, Sentinel evaluated other languages.
+Some languages worked quite well. As we continued to develop Sentinel, we
+found that the flexibility and control over designing our own language
+outweighed the costs.
+
+Because the language itself doesn't need to be general purpose, building
+a language focused on policy proved to be the best route for Sentinel.
+It allows us to build powerful tracing capabilities for debugging, make
+interesting performance choices, and extend the language as needed in the
+future.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/concepts/policy-as-code.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/concepts/policy-as-code.mdx
new file mode 100644
index 0000000000..6dac593e18
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/concepts/policy-as-code.mdx
@@ -0,0 +1,72 @@
+---
+page_title: Policy as Code
+sidebar_current: docs-concepts-pac
+description: >-
+ Policy as code is the idea of writing code in a high-level language to manage
+ and automate policies. By representing policies as code in text files, proven
+ software development best practices can be adopted such as version control,
+ automated testing, and automated deployment.
+layout: docs
+---
+
+# Policy as Code
+
+Policy as code is the idea of writing code in a high-level language to
+manage and automate policies. By representing policies as code in text files,
+proven software development best practices can be adopted such as version
+control, automated testing, and automated deployment.
+
+Many existing policy or ACL systems do not practice policy as code. Many
+policies are set by clicking in a GUI, which isn't easily repeatable nor
+versionable. They usually don't provide any system for testing policies
+other than testing an action that would violate the policy. This makes it
+difficult for automated testing. And the policy language itself varies by
+product.
+
+Sentinel is built around the idea and provides all the benefits of policy as code.
+
+## Benefits
+
+Policy as code provides a number of benefits:
+
+- **Sandboxing.** Policies provide the guardrails for other automated systems.
+ As the number of automated systems grow, there is also a growing need to
+ protect those automated systems from performing dangerous actions. Manual
+ verification is too slow; policies need to be represented as code to
+ keep up with other automated systems.
+
+- **Codification.** By representing policy logic as code, the information
+ and logic about a policy is directly represented in code and can be augmented
+ with comments rather than relying on oral tradition to learn about the
+ reason for policies.
+
+- **Version Control.** Policies are encouraged to be stored as simple text
+ files managed by a version control system. This lets you gain all the
+ benefits of a modern VCS such as history, diffs, pull requests, and more.
+
+- **Testing.** Policies are just code. Their syntax and behavior can be
+ [easily validated with Sentinel](/sentinel/commands/test). This also encourages
+ automated testing such as through a CI. Paired with a VCS system, this
+ allows a pull request workflow to verify that a policy keeps the system
+ behavior as expected before merging.
+
+- **Automation.** With all policies as code in simple text files, various
+ automation tools can be used. For example, it is trivial to create tools
+ to automatically deploy the policies into a system.
+
+## Sentinel and Policy as Code
+
+Sentinel fully embraces policy as code in a number of ways:
+
+- **Language.** All Sentinel policies are written using the
+ [Sentinel language](/sentinel/concepts/language). This language is
+ made to be inputted directly to text files. As an additional benefit,
+ all Sentinel-enabled applications share the same policy language.
+
+- **Development.** Sentinel provides a [CLI](/sentinel/commands)
+ for development and testing. This local CLI can be used to verify policies
+ before deploying them to a system.
+
+- **Testing.** Sentinel provides a [test framework](/sentinel/commands/test)
+ designed specifically for automation. This allows developers and CI systems
+ to further verify policies.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/configuration/index.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/configuration/index.mdx
new file mode 100644
index 0000000000..712fffcbed
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/configuration/index.mdx
@@ -0,0 +1,400 @@
+---
+page_title: Sentinel CLI Configuration File Syntax
+sidebar_current: docs-configuration
+description: >-
+ The Sentinel CLI's configuration file can be used to control the
+ behavior of the simulator during apply and test operations.
+layout: docs
+---
+
+# Sentinel CLI Configuration File Syntax
+
+The Sentinel CLI's configuration file can be used to control the behavior
+of the simulator during [`apply`](/sentinel/commands/apply) and
+[`test`](/sentinel/commands/test) operations.
+
+## Usage
+
+The configuration file is used in different ways depending on what operation you
+are trying to execute.
+
+### Apply
+
+When using `sentinel apply`, configuration files that have the `.hcl`
+extension are loaded into a single configuration. This allows for configuration
+to be split across multiple files, which is useful for large policy sets. To
+supply a single configuration file, the `-config=FILE` flag can be used, where
+`FILE` is the path to the configuration file. This argument also allows for a
+`.json` configuration file to be supplied. The default is `sentinel.[hcl|json]`.
+
+See the [apply command](/sentinel/commands/apply) reference for more details.
+
+### Test
+
+When using `sentinel test`, each file matching `test//*.[hcl|json]` is a
+configuration file representing a single test case, where `` is the name
+of the policy being tested. Configure assertions for each [test
+case](#test-cases) using the test section.
+
+See the [test command](/sentinel/commands/test) reference for more details.
+
+#### Remote sources
+
+When declaring a `policy` or `module` block within the configuration, a
+`source` attribute must be supplied. This `source` should declare the location
+of the resource, which can be either a local file or a URL pointing to a remote
+file. Examples of local `source` attributes are detailed both in the
+[Policies](#policies) and [Modules](#modules) sections of this page. Below
+are a few examples of remote `source` attributes.
+
+Example:
+
+```hcl
+policy "foo" {
+ source = "git::https://github.com/hashicorp/sentinel-example.git//main.sentinel"
+ enforcement_level = "hard-mandatory"
+}
+```
+
+The above example will fetch the policy from the provided URL and place it into
+the cache ready for use. Be sure to read the
+[Remote Sources](/sentinel/configuration/remote-sources) page for an
+in-depth overview.
+
+## Configuration File Reference
+
+The format of the configuration file is either JSON or HCL. The available
+blocks are:
+
+- [`mock`](#mock-imports) - A mock import specification.
+- [`policy`](#policies) - Configuration for a [policy](/sentinel/writing).
+- [`import`](#imports) - Configuration for a [module](/sentinel/extending/modules), [standard import](/sentinel/imports), [plugin](/sentinel/extending/plugins) or
+ [static import](/sentinel/extending/static-imports).
+- [`global`](#globals) - Data that is inserted into the global scope.
+- [`param`](#parameters) - Values for [parameters](/sentinel/language/parameters) defined in the policy.
+- [`test`](#test-cases) - Test cases for the [test command](/sentinel/commands/test).
+
+### Mock Imports
+
+Mock imports allow running a policy with an
+[import](/sentinel/concepts/imports) that you may not have access to or is
+too difficult to run. For example, it can be easier to mock data for [Terraform
+Enterprise](https://www.terraform.io/docs/enterprise/sentinel/index.html)
+locally instead of having to run a workspace through a plan/policy check cycle.
+
+Mock imports are specified using the `mock` block, labelled by the import
+name that you want to mock. For example, if you wanted to mock the `time`
+import, you could create an entry with the `time` label pointing to the data
+you want to mock.
+
+The data can take one of two forms:
+
+#### Mocking static data
+
+Static data can be mocked directly via HCl using the `data` attribute on the
+`mock` block.
+
+Example:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ hour = 9
+ minute = 42
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"time\"\n\nmain = rule { time.now.hour is 9 }",
+ "mocks": {
+ "time": "now = { \"hour\": 9, \"minute\": 42 }"
+ }
+}
+```
+
+#### Mocking with Sentinel code
+
+There are some Sentinel types that raw data cannot mock, such as functions,
+and maps that don't have string keys. To mock this data, you can use
+a Sentinel file itself. In this case, you can set the `module` attribute to
+a file with the Sentinel code in it, relative to the current working directory in
+the event of `apply`, or relative to the configuration file location in the
+event of `test`.
+
+Note that `main` is not required in a mock data file.
+
+Example:
+
+```hcl
+mock "foo" {
+ module {
+ source = "mock-foo.sentinel"
+ }
+}
+```
+
+`mock-foo.sentinel` would contain:
+
+```sentinel
+bar = func() {
+ return "baz"
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"foo\"\n\nmain = rule { foo.bar() is \"baz\" }",
+ "mocks": {
+ "foo": "bar = func() { return \"baz\" }"
+ }
+}
+```
+
+### Policies
+
+The `policy` block allows for the the configuration of policies to assist
+with integrating into other commands.
+
+Each `policy` block has a label to identify the policy, with the block
+providing configuration of the policy. The available configuration options for
+policy are `source` and `enforcement_level`. The `source` key provides the
+location of the policy, while `enforcement_level` is currently used by
+integrations such as [Terraform Cloud](https://www.terraform.io/docs/cloud/sentinel/manage-policies.html#enforcement-levels). For more information on the `source`
+value, see [Policy and Module Sources](#policy-and-module-sources).
+
+```hcl
+policy "foo" {
+ source = "foo.sentinel"
+ enforcement_level = "hard-mandatory"
+}
+```
+
+If `enforcement_level` is not supplied, it will fallback to the `advisory` level.
+
+### Imports
+
+Import configuration allows you to configure [modules](/sentinel/extending/modules),
+[standard imports](/sentinel/imports) and [import plugins](/sentinel/extending/plugins).
+
+The `import` is a multi-label block, with the first label determining the kind of import
+being configured. Currently the supported values for this label are:
+
+* [`"module"`](#import-modules) for configuring import modules
+* [`"plugin"`](#import-plugins) for configuring standard imports and import plugins
+* [`"static"`](#static-imports) for configuring static data files as imports
+
+#### Import Modules
+
+The `import "module"` block allows you to map a Sentinel import to a
+[module](/sentinel/extending/modules). The Sentinel CLI will parse the module
+source and make it available for evaluation with the policy.
+
+Each block has a label aligning to the name of the import, with
+the block set to the configuration object for the module. Currently, the only
+value within the configuration object is `source`, which points to the
+location of the module. For more information on the `source` value, see
+[Policy and Module Sources](#policy-and-module-sources).
+
+```hcl
+import "module" "foo" {
+ source = "modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "modules/bar.sentinel"
+}
+```
+
+Note when using [`sentinel test`](/sentinel/commands/test), module paths must be
+specified relative to the location of the configuration file.
+
+```hcl
+import "module" "foo" {
+ source = "../../modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "../../modules/bar/sentinel"
+}
+```
+
+See [Writing and Using a
+Module](/sentinel/extending/modules#writing-and-using-a-module) for more details
+on using a module with this configuration.
+
+### Import Plugins
+
+Import `"plugin"` configuration allows you to configure both [standard imports](/sentinel/imports)
+as well as [import plugins](/sentinel/extending/plugins) that can be used within
+a policy. If defining a custom import plugin, the Sentinel CLI will launch the
+plugin, connect to it, configure it, and execute it as needed by the policy.
+
+The available configuration attributes for an `import "plugin"` are:
+
+- `source` (string, required) - Path to the import plugin executable.
+- `args` (list of string, optional) - A list of arguments to pass to the
+ executable when starting it.
+- `env` (map of string to string, optional) - A set of environmental variables
+ to set when launching the plugin.
+- `config` (map, optional) - Configuration for the plugin itself. This is
+ specific to each individual plugin. Please reference the documentation for
+ the plugin for more information.
+
+Standard import example:
+
+```hcl
+import "plugin" "time" {
+ config = { "timezone": "Australia/Brisbane" }
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"time\"\n\nmain = time.now.zone_string is \"+10:00\"",
+ "mocks": {
+ "time": "now = { \"zone_string\": \"+10:00\" }"
+ }
+}
+```
+
+Custom plugin example:
+
+```hcl
+# flipper receives a boolean and returns the opposite
+import "plugin" "flipper" {
+ source = "/path/to/flipper"
+}
+```
+
+```json sentinel
+{
+ "policy": "import \"flipper\"\n\nmain = flipper.flip(false)",
+ "mocks": {
+ "flipper": "flip = func(input) { return not input }"
+ }
+}
+```
+
+#### Static Imports
+
+Static import configuration allows you to supply a data file and make
+it available to a policy via an import statement.
+
+The available configuration attributes for an `import "static"` are:
+
+- `source` (string, required) - Path to the static data file.
+- `format` (string, required) - The format of the static data. Currently, only
+ the "json" value is supported.
+
+Static import example:
+
+```hcl
+import "static" "people" {
+ source = "./data/people.json"
+ format = "json"
+}
+```
+
+```json sentinel
+{
+ "policy": "import \"people\"\n\nmain = length(people.names) > 0",
+ "mocks": {
+ "people": "names = [\"John Smith\", \"Jane Smith\"]"
+ }
+}
+```
+
+### Globals
+
+Global data is injected directly into the global scope of the policy.
+This can be used to simulate global data that may be injected by a
+Sentinel-enabled application.
+
+Global data is specified by the `global` key. The value is a map of
+variables to inject directly into the running policy.
+
+Example:
+
+```hcl
+global "time" {
+ value = {
+ now = {
+ day = 31
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass. Notice
+that we don't have to do any `import`. The values of `global` are
+injected directly into the global scope.
+
+```json sentinel
+{
+ "policy": "main = time.now.day == 31",
+ "globals": {
+ "time": {
+ "now": {
+ "day": 31
+ }
+ }
+ }
+}
+```
+
+### Parameters
+
+The `param` section allows you to supply values for the
+[parameters](/sentinel/language/parameters) found in a policy. Values
+entered here will satisfy required parameters in a policy, in addition to
+override any defaults. Note that any of the manual parameter value methods
+supported by `sentinel apply` will override the values set here.
+
+```hcl
+param "foo" {
+ value = "bar"
+}
+```
+
+### Test Cases
+
+Test cases specify the test cases for the [test command](/sentinel/commands/test).
+
+Tests are specified by the `test` key. The value of this is a map of
+string to boolean. The key is the name of a rule and the value is the
+expected value of that rule. If a rule is not specified, than any value is
+allowed.
+
+Example:
+
+```hcl
+test {
+ rules = {
+ main = true
+ days = []
+ }
+}
+```
+
+For the policy:
+
+```json sentinel
+{
+ "policy": "valid_days = rule {\n\tfilter days as d {\n\td is \"monday\"\n\t}\n}\n\nmain = rule { valid_days is empty }",
+ "globals": {
+ "days": []
+ }
+}
+```
+
+For more information, read about the [test command](/sentinel/commands/test).
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/configuration/overrides.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/configuration/overrides.mdx
new file mode 100644
index 0000000000..ca1a6842b1
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/configuration/overrides.mdx
@@ -0,0 +1,80 @@
+---
+page_title: Override Files
+sidebar_current: docs-configuration-overrides
+description: >-
+ The Sentinel CLI's configuration can be overriden using override files.
+ Override files merge additional settings into existing configuration.
+layout: docs
+---
+
+# Override Files
+
+As outlined in the configuration [overview](/sentinel/configuration#Apply),
+the normal behavior of Sentinel is to load all `.hcl` files into a single
+configuration object.
+
+In addition to appending multiple configuration files, Sentinel allows for the
+rare case of overriding configuration through override files. Override files
+are any files that match either `_override.{hcl,json}` or `override.{hcl,json}`.
+
+Sentinel will parse override files after it has loaded the primary
+configuration, and then process each override file in turn (in lexicographical
+order). All top level blocks other than [test](/sentinel/configuration#test-cases)
+require the block to already be defined in the primary configuration. It will
+then attempt to merge the override block into the existing configuration.
+
+## Example
+
+If you had a Sentinel configuration `sentinel.hcl` that contained the following:
+
+```hcl
+policy "main" {
+ source = "./main.sentinel"
+ enforcement_level = "advisory"
+}
+```
+
+And you created a file `override.hcl` that contained the following:
+
+```hcl
+policy "main" {
+ enforcement_level = "soft-mandatory"
+}
+```
+
+Sentinel will merge the contents of the override into the former, providing the
+following configuration:
+
+```hcl
+policy "main" {
+ source = "./main.sentinel"
+ enforcement_level = "soft-mandatory"
+}
+```
+
+## Merging Behavior
+
+The general rule, which applies in most cases, is:
+
+- A top-level block in an override file merges with a block in a normal
+ configuration file that has the same block header. The block header is the
+ block type and any quoted labels that follow it.
+- Within a top-level block, an attribute argument within an override block
+ replaces any argument of the same name in the original block.
+- Within a top-level block, any nested blocks within an override block replace
+ all blocks of the same type in the original block. Any block types that do not
+ appear in the override block remain from the original block.
+- The contents of nested configuration blocks are not merged.
+- The resulting merged block must still comply with any validation rules that
+ apply to the given block type.
+
+If more than one override file defines the same top-level block, the overriding
+effect is compounded, with later blocks taking precedence over earlier blocks.
+Overrides are processed in order first by filename (in lexicographical order)
+and then by position in each file.
+
+## Required Attributes
+
+It is important to note that all attributes inside an override file are
+considered to be optional, meaning that an override file can itself fail
+validation rules for block types.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/configuration/remote-sources.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/configuration/remote-sources.mdx
new file mode 100644
index 0000000000..b64e4d3abc
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/configuration/remote-sources.mdx
@@ -0,0 +1,165 @@
+---
+page_title: Remote Sources
+sidebar_current: docs-configuration-remote-sources
+description: >-
+ There are a number of options for formatting the URL provided to the
+ source attribute on policy and module blocks.
+layout: docs
+---
+
+# Remote Sources
+
+There are a number of options for formatting the URL provided to the
+`source` attribute on `policy` and `import "module"` blocks. This flexibility
+should solve most use cases and allow for reusable policies and modules.
+
+### Supported Protocols
+
+-> **NOTE:** All protocols are supported on the CLI, however each production
+Sentinel integration may not. Be sure to read the appropriate integration
+documentation to check the supported protocols.
+
+- Local filesystem
+- Git
+- Mercurial
+- HTTP
+- Amazon S3
+- Google GCP
+
+### Forced Protocols
+
+Due to the ambiguous nature of URLs, it is possible to force a particular
+protocol to be used during the fetch. To achieve this, simply prefix the url
+with the appropriate protocol as listed below:
+
+- `file` - Local filesystem
+- `git` - Git
+- `hg` - Mercurial
+- `http` or `https` - HTTP
+- `s3` - Amazon S3
+- `gcp` - Google GCP
+
+Example:
+
+```
+git::http://github.com/hashicorp/sentinel.git
+```
+
+The above would download the provided HTTP URL using the Git protocol.
+
+### Protocol Options
+
+In addition to some global options, there are options available to specific
+protocols to perform features such as authentication.
+
+#### Subdirectories / File Pathing
+
+When supplying URLs that point to a directory (eg. `git`), a subdirectory and
+file can be provided by suffixing the URL with `//` and the path. For example,
+if we have a git repository that has a policy, `main.sentinel` in the root, we
+could directly fetch the file by using the following:
+
+```
+git::https://github.com/hashicorp/sentinel-docs-example.git//main.sentinel
+```
+
+Or, if the file was nested in the `policies` folder:
+
+```
+git::https://github.com/hashicorp/sentinel-docs-example.git//policies/main.sentinel
+```
+
+#### Checksumming
+
+For downloads of any protocol, you can automatically verify a checksum. To
+checksum a file, append a `checksum` query parameter to the URL. The parameter
+value can be in the format of type:value or just value, where type is "md5",
+"sha1", "sha256", "sha512" or "file" . The "value" should be the actual
+checksum value or download URL for "file". When type part is omitted, type will
+be guessed based on the length of the checksum string.
+
+```
+./foo.sentinel?checksum=md5:b7d96c89d09d9e204f5fedc4d5d55b21
+
+./foo.sentinel?checksum=b7d96c89d09d9e204f5fedc4d5d55b21
+
+./foo.sentinel?checksum=file:./foo.txt.sha256sum
+```
+
+#### Unarchiving
+
+An `archive` query parameter can be supplied to explicitly state what format
+to attempt to extract, if required. The supported formats are:
+
+- `tar.gz` and `tgz`
+- `tar.bz2` and `tbz2`
+- `tar.xz` and `txz`
+- `zip`
+- `gz`
+- `bz2`
+- `xz`
+
+If a URL has an extension that matches one of the supported formats, it will
+use that format to unarchive.
+
+```
+./file.zip
+```
+
+The above would use the `zip` format to unarchive.
+
+```
+./path/to/file?archive=zip
+```
+
+Would force the `zip` format. If for some reason you required archiving to be
+disabled, this can also be achieved by using the `false` value.
+
+```
+./path/to/file?archive=false
+```
+
+#### Git (`git`)
+
+- `ref` - The Git ref to checkout. This is a ref, so it can point to a commit
+ SHA, a branch name, etc. If it is a named ref such as a branch name, it will
+ be updated to the latest on each get.
+- `sshkey` - An SSH private key to use during clones. The provided key must be
+ a base64-encoded string. For example, to generate a suitable sshkey from a
+ private key file on disk, you would run base64 -w0 \.
+ **Note:** Git 2.3+ is required to use this feature.
+- `depth` - The Git clone depth. The provided number specifies the last `n`
+ revisions to clone from the repository.
+
+The git getter accepts both URL-style SSH addresses like
+`git::ssh://git@example.com/foo/bar`, and "scp-style" addresses like
+`git::git@example.com/foo/bar`. In the latter case, omitting the `git::` forced
+prefix is allowed if the username prefix is exactly git@.
+
+The "scp-style" addresses cannot be used in conjunction with the `ssh://`
+scheme prefix, because in that case the colon is used to mark an optional port
+number to connect on, rather than to delimit the path from the host.
+
+#### Mercurial (`hg`)
+
+- `rev` - The Mercurial revision to checkout.
+
+#### HTTP (`http` or `https`)
+
+Basic Authentication
+
+To use HTTP basic authentication, simply prepend `username:password@` to the
+hostname in the URL such as `https://Aladdin:OpenSesame@www.example.com/index.html`.
+All special characters, including the username and password, must be URL
+encoded.
+
+#### S3 (`s3`)
+
+The following query parameters are available and will take priority when
+authenticating against an S3 bucket.
+
+- `aws_access_key_id` - AWS access key.
+- `aws_access_key_secret` - AWS access key secret.
+- `aws_access_token` - AWS access token if this is being used.
+- `aws_profile` - Use this profile from local ~/.aws/ config. Takes priority
+ over the other three.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/consul.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/consul.mdx
new file mode 100644
index 0000000000..7a3aaaa697
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/consul.mdx
@@ -0,0 +1,47 @@
+---
+page_title: Consul and Sentinel
+sidebar_current: docs-app-consul
+description: >-
+ Consul Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies are applied during writes to
+ the KV Store.
+layout: docs
+---
+
+# Consul
+
+[Consul Enterprise](https://www.hashicorp.com/products/consul/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies are applied during writes to the KV Store.
+
+Sentinel policies have access to the key/value being written. They can be used to
+allow or deny the modification. The information that Sentinel policies have access
+to will expand over time.
+
+The Consul integration with Sentinel is documented in depth in the
+[Consul Enterprise documentation](https://www.consul.io/docs/guides/sentinel.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Input validation depending on the name of the key.**
+
+```sentinel
+main = rule { valid_key() }
+
+required = [
+ ["port", "\\d+"], # ports must be integers
+ ["name", "\\w+"], # name must be a word
+]
+
+valid_key = func() {
+ for required as v {
+ if key is v[0] {
+ return value matches v[1]
+ }
+ }
+
+ return false
+}
+```
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/extending/index.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/extending/index.mdx
new file mode 100644
index 0000000000..a39cb0384d
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/extending/index.mdx
@@ -0,0 +1,52 @@
+---
+page_title: Extending Sentinel
+sidebar_current: docs-extending
+description: Learn how to create your own Sentinel imports to extend Sentinel's functionality.
+layout: docs
+---
+
+# Extending Sentinel
+
+-> **NOTE:** Sentinel's extensibility is currently undergoing large
+improvements - watch this space for future updates!
+
+Sentinel can be extended by writing additional
+[imports](/sentinel/language/imports). These imports can bring new functionality
+to Sentinel by allowing access to new data or by the addition of new functions.
+This section is designed to show you how to accomplish this extensibility and
+describe how it works.
+
+Currently, the only usable way to add additional imports is via
+[modules](#modules). While [plugins](#plugins) currently work under the Sentinel
+CLI, no Sentinel integration currently supports their use.
+
+-> You may also be interested in advanced details on [import
+internals](/sentinel/extending/internals).
+
+## Modules
+
+Modules allow you to re-use Sentinel code as an import. Modules are loaded
+external of policies and mapped to imports. This is usually configured via a
+file such as the [CLI configuration file](/sentinel/configuration).
+
+See the [modules](/sentinel/extending/modules) section for more details.
+
+## Plugins
+
+-> **NOTE:** Plugins are currently only supported on the CLI and not in any
+production Sentinel integration, with the exception of existing configuration
+in [Nomad](https://www.nomadproject.io/docs/configuration/sentinel).
+
+Imports can also be implemented as plugins when Sentinel code itself is too
+restrictive to represent the import, or if you need access to features not
+normally available to the Sentinel runtime.
+
+See the [plugins](/sentinel/extending/plugins) section for more details.
+
+## Static Imports
+
+Static imports provide support for loading data files into the Sentinel runtime,
+making them available as an import.
+
+See the [static imports](/sentinel/extending/static-imports) section for more
+details.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/extending/internals.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/extending/internals.mdx
new file mode 100644
index 0000000000..11be9d3d67
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/extending/internals.mdx
@@ -0,0 +1,252 @@
+---
+page_title: >-
+ Extending Sentinel: Internals
+sidebar_current: docs-extending-internals
+description: This Section covers some details about Sentinel's import internals.
+layout: docs
+---
+
+# Extending Sentinel: Internals
+
+-> **Advanced Topic!** This page covers low-level technical details of Sentinel.
+You don't need to understand these details to effectively use Sentinel. The
+details are documented here for those who wish to learn about them.
+
+This section covers some of the internal details of Sentinel's import system.
+The goal of this section is to help remove notion of "magic" from Sentinel, to
+allow you to trust and understand what Sentinel is doing with imports, and also
+to help practitioners and developers alike understand the differences between
+the ways that imports can be loaded into Sentinel.
+
+## Language and Runtime
+
+Understanding the import system in Sentinel generally requires understanding of
+two key concepts within Sentinel: the _language_, and the _runtime
+implementation_ of the language.
+
+The Sentinel language and the rules governing it are detailed in the [Sentinel
+Language Specification](/sentinel/language/spec). A runtime implementation must
+conform to the rules laid out in the specification at a minimum. However, to
+meet the design goals of a particular implementation, the runtime may implement
+features on top of the language. The subtle implementation details within the
+runtime may also vary while still being compliant with the specification.
+
+The core runtime included within the [Sentinel CLI](/sentinel/commands) is
+sometimes referred to as the _reference implementation_ of the Sentinel
+language. This runtime is also the main runtime embedded within each
+Sentinel-enabled HashiCorp product such as Terraform Cloud and Enterprise, Vault
+Enterprise, Nomad Enterprise, and Consul Enterprise. The runtime includes an API
+to allow these integrations implement Sentinel in as robust or as restrictive of
+a way as possible, so depending on the implementation, your experience may vary.
+
+## Sentinel's Import Model
+
+The concept of Imports within Sentinel is a _language_ feature. You can see the
+exact rules governing imports within the
+[Imports](/sentinel/language/spec#imports) section of the specification.
+
+Within the runtime, there are currently two major classifications of imports:
+
+- **Modules:** The mapping of Sentinel code to an import, essentially mapping a
+ scope of values to a particular package.
+- **Binary Imports:** A grouping of embedded and external plugins written using
+ the Sentinel SDK. These are comprised of internal or embedded imports (usually
+ the [standard imports](/sentinel/imports) and imports included by an
+ integration) and import plugins.
+
+Below is a diagram explaining in brief the relationship between the langauge
+and runtime features.
+
+
+
+This kind of topology ultimately minimizes the visibility of implementation
+internals to the actual policy language. This allows us to keep the Sentinel
+language itself simple and keep concerns such as module or plugin management,
+validation, and configuration out of the language. These kinds of things would
+impact the readability of a policy, possibly present security challenges, and
+would ultimately be of no use to more minimal embedded applications.
+
+### Implementation Differences Between Import Types
+
+As one would imagine, both modules and binary imports are loaded in much
+different ways, and the methods that they expose data to Sentinel differ as
+well. The effect is generally the same however across both, and we take care to
+ensure that both modules and binary imports behave as close as possible to each
+other, however there are some subtle differences:
+
+- Modules currently support the exporting of rules, but do not support function
+ calls with object receiver data (as seen in some standard imports such as
+ [`decimal`](/sentinel/imports/decimal), [`http`](/sentinel/imports/http), and
+ [`time`](/sentinel/imports/time)). This will be coming in a later release.
+- Binary imports cannot export rules. However, they do support object receiver
+ data (see [`framework.New`](/sentinel/extending/plugins#framework-new) in
+ [Extending Sentinel: Plugins](/sentinel/extending/plugins)).
+
+## Modules
+
+_Modules_ are essentially Sentinel code that is intended to be re-used in multiple
+policies as an import.
+
+The mechanism of module loading is fairly straightforward: during initialization
+of the Sentinel runtime, modules are parsed along with policies. The parsed
+_abstract syntax tree_ (AST) for each module is loaded into the runtime under
+the specified _import path_. These are then passed to the lower-level
+interpreter during the evaluation of each policy.
+
+As with policy code, during evaluation, a module's AST is walked to execute the
+code within that specific module. The module data is then stored within an
+specific scope mapped to the import name. The module data can then be accessed
+through the import via selector expressions and function calls (e.g.,
+`foo.some_map` or `foo.some_func()` for a module loaded via `import "foo"`).
+
+The AST for a module is only walked if a policy imports it, and it is _only
+walked once_. That means if a policy and a module import the same module, or a
+policy imports the same module twice (using [import
+aliases](/sentinel/language/imports#aliases)), those instances will share state.
+If you call a function that alters a singleton variable within the module, that
+singleton will be modified for all instances of the import.
+
+-> **Fun fact!** When you [mock an import with Sentinel
+code](/sentinel/configuration#mocking-with-sentinel-code), you are actually
+using a module. The module is loaded with a flag that allows it to override an
+existing import, which would normally give a runtime error.
+
+### Map and List Cloning for Module Calls
+
+It's also worthwhile noting that map and list values are cloned for modules.
+
+Consider the case where you have a module with a map singleton.
+
+```sentinel
+// modules/foo.sentinel
+a_map = {"a": "b"}
+```
+
+Normally, assignment to the singleton, even when using an index expression, is
+blocked, so `foo.a_map["c"] = "d"` would not pass semantic checking.
+
+However, even when you try to do assignment via an intermediary, you will still
+be only modifying the copy, and the original will not be affected:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+a_map_copy = foo.a_map
+a_map_copy["c"] = "d" // Only modifies copy, does not modify module singleton
+print(foo.a_map) // Still only prints {"a": "b"}
+```
+
+This is to ensure that modules are consistent with binary imports in how return
+data is handled, and the expectation that most non-object data within an import
+is read-only, with the exception of modification of singleton data via
+functions. As return of complex object and collection data in a binary import is
+always via copy, it's not possible to modify any value elements within the
+import in a similar fashion.
+
+There are also some other implications of this cloning that are of note:
+
+- Rules are not cloned, either within a list or map, or by themselves. This is
+ to ensure that [memoization](/sentinel/language/rules#lazy-and-memoized)
+ functions correctly. As only a module can export rules at this time, there is no
+ parity for this in binary imports.
+- Functions are _not_ cloned. This mainly has implications for scope - for
+ example, if you assign a function to another value, or assign an object to a
+ value that has a method that utilizes a global module variable, those calls will
+ reference and/or modify those values.
+
+Functions, while represented differently in binary imports, generally follow the
+same logic here - as they would be invoked in the same package within the binary
+import, any singleton values they accessed there would be affected in a similar
+fashion. As rules are pseudo-functions, this generally makes sense here too.
+
+## Binary Imports
+
+_Binary imports_ is a grouping referring to all imports that are designed with
+the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). These can be
+either internally embedded, or executed via a plugin.
+
+All imports found in the [standard library](/sentinel/imports) are binary
+imports, embedded internally.
+
+The main difference between internally embedded imports and external plugins is
+whether or not the runtime needs to execute a plugin binary as part of
+[lifecycle management](#lifecycle), and whether or not it needs to
+[communicate](#communication) over RPC. Internal binary imports do not require
+these steps, so their execution model is somewhat simpler; however, technically,
+they still are the same as external plugins in terms of design model and value
+conversion. Most standard imports are even tested using the [SDK test
+suite](/sentinel/extending/plugins#testing), which builds the import as an
+external plugin.
+
+The remainder of this section discusses topics mostly relevant to external
+plugins. A large amount of technical detail regarding binary import development
+(also applicable to embedded binary imports) can be seen on the
+[Plugins](/sentinel/extending/plugins) page.
+
+-> **NOTE:** At this time, plugins can only be written for the Sentinel CLI, and
+cannot be used with any of Sentinel's integrations.
+
+### Plugin Basics
+
+Sentinel plugins are built on top of the HashiCorp [go-plugin
+system](https://github.com/hashicorp/go-plugin). This is the same plugin system
+powering all pluggable HashiCorp tools such as
+[Terraform](https://www.terraform.io), [Vault](https://www.vaultproject.io), and
+more. go-plugin is a system that has been used in production for millions of
+users for over 5 years.
+
+Plugins are executable binaries. Sentinel is responsible for launching and
+managing the lifecycle of plugins. Once a plugin is running, Sentinel
+communicates with the plugin via [gRPC](https://grpc.io/). The [protocol is open
+source](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/import.proto)
+to allow anyone to write a Sentinel plugin.
+
+### Lifecycle
+
+Sentinel launches plugins and is responsible for plugin lifecycle.
+
+The runtime optimizes for latency by launching the plugin as soon as it is
+configured, rather than when a policy requires it. This ensures that the plugin
+is ready to be used immediately. A plugin is only closed when Sentinel is
+reconfigured to no longer allow that plugin or if the Sentinel-enabled
+application is closing.
+
+When a plugin is removed from the configuration, Sentinel may wait to shut down
+the plugin until all currently executing policies that are using that plugin
+complete. New policy executions will not be allowed to use the old plugins.
+
+Plugins are automatically restarted if they shut down unexpectedly. Policies
+that were executing while this happens may fail. The Sentinel system is
+improving to more gracefully understand locations where it is safe to retry an
+execution.
+
+### Communication
+
+An [initial
+handshake](https://github.com/hashicorp/go-plugin/blob/master/docs/internals.md)
+is done via stdout to determine the main communication location. Following the
+handshake, communication will either occur on a local Unix domain socket or via
+a local-only TCP socket. This communication is done mostly over the low-level
+[`Get`](https://github.com/hashicorp/sentinel-sdk/blob/2b4db07dd12b55142f0c016f301af80aa4422520/proto/import.proto#L12)
+RPC call.
+
+#### Value Conversion
+
+As can generally be seen in [Developing an Import
+Plugin](/sentinel/extending/plugins#developing-an-import-plugin), the Sentinel
+type system is not exposed to binary imports. While explicit values for null and
+undefined exist, values are mostly converted over the wire from their Go values
+to their Sentinel equivalents.
+
+This has a few implications:
+
+- As discussed in [Map and List Cloning for Module
+ Calls](#map-and-list-cloning-for-module-calls), Map and list data returned from
+ binary import value lookups or function calls is always cloned. These can be
+ freely modified without affecting anything within the binary import.
+- It's currently impossible to represent certain Sentinel types such as
+ [rules](/sentinel/language/rules) within a binary import. This is not a major
+ issue at this point in time as practitioners generally do not look to binary
+ imports for rules; we may examine this as a later time if this situation
+ changes.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/extending/modules.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/extending/modules.mdx
new file mode 100644
index 0000000000..9846d263e5
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/extending/modules.mdx
@@ -0,0 +1,135 @@
+---
+page_title: >-
+ Extending Sentinel: Modules
+sidebar_current: docs-extending-modules
+description: >-
+ Modules allow you to re-use Sentinel code as an import.
+layout: docs
+---
+
+# Extending Sentinel: Modules
+
+Modules allow you to re-use Sentinel code as an import. This allows for the
+export of any general construct that Sentinel supports, including values,
+functions, and even rules. As such, it's a great way to package code that is
+useful across multiple policies, reducing boilerplate and making final policy
+code simpler.
+
+You may want to use modules for:
+
+- **Writing a simple re-usable helper library.** If you mostly know Sentinel or
+ have helpers that you have written in Sentinel, moving these helpers to a module
+ will offer a low-friction way of facilitating their re-use.
+- **Writing re-usable rules.** If you have a set of
+ [rules](/sentinel/language/rules) you know you want to use across multiple
+ policies, bundling them into a module is a great way of abstracting the logic
+ away.
+- **Abstracting existing Sentinel imports.** Using a module is a great way to
+ extend existing Sentinel imports by abstracting the common functionality that
+ you use within an import to your own workflow.
+
+This page describes how you can use modules within the context of the Sentinel
+CLI. For instructions for a particular integration, see the documentation for
+that product.
+
+-> **Not all Sentinel-enabled applications support modules.** Please refer
+to the documentation of the specific Sentinel-enabled application. Some
+applications disable modules for security reasons.
+
+## Configuring a Module
+
+A module is configured within the Sentinel CLI by adding an entry for it in
+within the `imports` section of the [CLI configuration
+file](/sentinel/configuration). The key for each entry specifies the path that
+the module is imported as.
+
+```hcl
+import "module" "foo" {
+ source = "modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "modules/bar.sentinel"
+}
+```
+
+In this example, we have two modules:
+
+- Import path `foo`, being loaded from the code within `modules/foo.sentinel`.
+- Import path `bar`, being loaded from the code within `modules/bar.sentinel`.
+
+See the [Import Modules](/sentinel/configuration#import-modules) section within
+the CLI configuration file syntax page for more details.
+
+### Remote Modules
+
+In addition to loading from a local source, modules can be loaded from a remote
+location. This can increase reusability and allow for sharing modules across
+multiple configurations.
+
+### Testing Using Modules
+
+As with [mocks](/sentinel/configuration#mocking-with-sentinel-code), modules
+are loaded relative to the configuration file location when using `sentinel test`. As such, you need to account for this in your test configuration files:
+
+```hcl
+import "module" "foo" {
+ source = "../../modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "../../modules/bar.sentinel"
+}
+```
+
+This could be a test file for a policy (example: `policy.sentinel`), residing
+under the correct test file directory for this policy (example:
+`test/policy/pass.json`).
+
+## Writing and Using a Module
+
+Writing a module is nearly the same as writing a Sentinel policy, except for the
+following two exceptions:
+
+- Modules do not require [`main`](/sentinel/writing/basic#the-simplest-policy)
+ to be present. It can be, but it will not carry any extra significance as it
+ does within a policy.
+- [`param`](/sentinel/language/parameters) declarations cannot be present in a module. If they are encountered,
+ a runtime error is given.
+
+Once you have a complete module ready for use and configured, using the module
+is as simple as importing it.
+
+Building on the above [configuration example](#configuring-a-module), we can
+export a simple test function in the module `foo` by adding this code to
+`modules/foo.sentinel`:
+
+```sentinel
+// modules/foo.sentinel
+hello = func() {
+ print("hello world!")
+ return undefined
+}
+```
+
+We can then import it within our policy and use it:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+// prints "hello world" in the trace
+foo.hello()
+
+main = true
+```
+
+Note that modules are considered [imports](/sentinel/language/spec#imports) by
+the Sentinel runtime and as such conform to the specification. This means that
+you cannot directly assign values to anything behind a module scope. You can,
+however, use functions to change state.
+
+-> **NOTE:** At this time, modules do not support object receiver data in the
+way that some imports do, like [`time`](/sentinel/imports/time),
+[`decimal`](/sentinel/imports/decimal), and [`http`](/sentinel/imports/http).
+Support for this will be coming in later versions of Sentinel.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/extending/plugins.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/extending/plugins.mdx
new file mode 100644
index 0000000000..e80de5d4b1
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/extending/plugins.mdx
@@ -0,0 +1,521 @@
+---
+page_title: >-
+ Extending Sentinel: Plugins
+sidebar_current: docs-extending-plugins
+description: >-
+ Plugins allow you to write imports as standalone executables, allowing you to
+ extend Sentinel in ways not normally possible with policy code alone.
+layout: docs
+---
+
+# Extending Sentinel: Plugins
+
+-> **NOTE:** Plugins are currently only supported on the CLI and not in any
+production Sentinel integration, with the exception of existing configuration
+in [Nomad](https://www.nomadproject.io/docs/configuration/sentinel).
+
+Plugins allow you to write imports as standalone executables, allowing you to
+extend Sentinel in ways not normally possible with policy code alone.
+
+You might want to write or use a plugin when you need to:
+
+- **Use a provider or API integration for Sentinel.** In this case, you will
+ probably be working with a provider SDK, or other libraries that will be making
+ complex network requests to external services. Sentinel only provides limited
+ capabilities for these kinds of operations in the standard library, so writing
+ this kind of import as a module is not optimal.
+- **Introduce novel functionality not currently supported by Sentinel.**
+ Sentinel's policy language is limited by design to encourage simple policies,
+ reduce its runtime footprint, and to keep it efficient and safe. This will
+ mean that some functionality may be difficult or impossible to express as
+ Sentinel code, and would require a plugin to write.
+- **Extend a complex module.** Additionally, modules are restricted to a single
+ file of Sentinel code, so these will naturally become more and more unwieldy as
+ you continue to add to them. Eventually, there might be a time where a plugin is
+ better suited for the functionality, especially if it's a general-purpose
+ library.
+
+This section details how import plugins are currently configured in the Sentinel
+CLI, and some detail on how they are written. As we continue to build on the
+ability to use import plugins, we will extend this section with more details.
+
+-> **Not all Sentinel-enabled applications will support plugins.** Some
+applications will disable plugins for security reasons. For those that do enable
+plugins, the method to configure plugins will vary.
+
+## Installing Plugins
+
+Sentinel plugins are standalone executables. Sentinel-enabled applications such
+as the CLI launch these plugins and communicate with them via
+[RPC](https://en.wikipedia.org/wiki/Remote_procedure_call). To install a plugin,
+the first step is to download the plugin executable.
+
+After downloading the plugin, you must add it in the CLI configuration file,
+setting its name, path, and any arguments, environment variables, or
+configuration. An example is below:
+
+```hcl
+import "plugin" "custom_time" {
+ source = "/path/to/sentinel-import-time"
+ config = { "fixed_time": 1504155600 }
+}
+```
+
+-> **NOTE:** We are using "custom_time" as standard import paths cannot be
+overriden. See [Configuration File Syntax](/sentinel/configuration#imports) for
+more details.
+
+After configuring the plugin, it will be available on the next `sentinel apply`
+or `sentinel test`.
+
+For more details on configuration, see the
+[plugins](/sentinel/configuration#plugins) section of the [CLI configuration
+file syntax](/sentinel/configuration).
+
+## Debugging Plugins
+
+The Sentinel CLI logs when it launches, configures, and closes a plugin. The
+plugin may also log additional information when it is running.
+
+To debug issues with a plugin, you can usually refer to these logs. Depending on
+the plugin configuration, the logs you see may vary - refer to the plugin
+documentation on how to enable logs for a particular plugin.
+
+## Developing an Import Plugin
+
+Anyone can develop a Sentinel import plugin using the [Sentinel
+SDK](https://github.com/hashicorp/sentinel-sdk). The SDK contains a high-level
+framework for writing plugins in [Go](https://golang.org) including a test
+framework. Additionally, the SDK contains the low-level [gRPC protocol buffers
+definition](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/plugin.proto)
+for writing plugins in other languages.
+
+The rest of this page will document how to write a new Sentinel plugin in Go
+using the Sentinel SDK and the high-level framework provided. You are expected
+to already know the basics of Go and have a properly configured Go installation.
+
+Throughout this page, we'll be developing a simple plugin to access time values.
+
+-> **NOTE:** The example here is not reflective of the actual implementation of
+the [`time`](/sentinel/imports/time) standard import, so make sure to not
+get the two confused.
+
+### Plugin Framework
+
+The Sentinel SDK provides a high-level
+[framework](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework)
+for writing plugins. We recommend using this framework.
+
+#### Basic Concepts
+
+This framework works by implementing the correct Go interfaces.
+
+As a general overview:
+[`framework.Plugin`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Plugin)
+implements the
+[`sdk.Plugin`](https://godoc.org/github.com/hashicorp/sentinel-sdk#Plugin)
+interface and therefore is a valid import plugin. You must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface to configure the plugin. The root may then delegate nested access to
+various
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+implementations. Other interfaces are implemented to provide functionality past
+what is provided by the basic `framework.Namespace` implementation - these are
+documented below.
+
+This all may sound like a lot of interfaces, but each interface typically only
+requires a single function implementation and you're only required to implement
+a single namespace (`framework.Namespace`).
+
+As we move on, we'll use real examples to make this clear.
+
+#### Implementing framework.Root
+
+To begin, you must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface. This is the interface representing the root of your plugin. The root
+itself must be implement
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace),
+which allows values to be accessed. Note that the root can optionally implement
+other interfaces, but they're more advanced and omitted here for simplicity.
+
+For our custom time example, our root may look like this:
+
+```sentinel
+type root struct {
+ time time.Time
+}
+
+// framework.Root impl.
+func (m *root) Configure(raw map[string]interface{}) error {
+ if _, ok := raw["timestamp"]; !ok {
+ raw["timestamp"] = time.Now().Unix()
+ }
+
+ v := raw["timestamp"]
+ timestamp, ok := v.(int)
+ if !ok {
+ return fmt.Errorf("invalid timestamp type %T", v)
+ }
+
+ m.time = time.Unix(timestamp, 0).UTC()
+ return nil
+}
+
+// framework.Namespace impl.
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "minute":
+ return m.time.Minute(), nil
+ }
+
+ return nil, nil
+}
+```
+
+This example implements `framework.Root` and `framework.Namespace` and would
+enable the following within a Sentinel policy once the completed plugin is
+installed.
+
+```sentinel
+import "custom_time"
+
+print(custom_time.minute)
+main = true
+```
+
+#### framework.Namespace
+
+The
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+interface implements a namespace of values. You saw above that
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+must itself be a namespace. However, a namespace `Get` implementation may
+further return namespaces to access nested values.
+
+This enables behavior such as `custom_time.month.string` vs. `custom_time.month.index`.
+Notice each of these examples accesses a nested value within `month`. This can
+be modeled as namespaces within the framework.
+
+In the example below, we return a new namespace for `month` to do just this:
+
+```sentinel
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "month":
+ return &namespaceMonth{Month: m.time.Month()}, nil
+ }
+
+ // ...
+}
+
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil nil
+}
+```
+
+#### Primitive Types and Structs
+
+A namespace may also return any primitive Go type as well as structs. The
+framework automatically exposes primitive values as you would expect. For
+structs, exported fields are lowercased and exposed to the policies. The
+`sentinel` struct tag can be used to control how Sentinel can access the field.
+
+In the example below, we expose a struct directly from the root namespace:
+
+```sentinel
+type Location struct {
+ Name string
+ TimeZone string `sentinel:"time_zone"`
+}
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "location":
+ return &Location{Name: "somewhere", TimeZone: "some zone"}, nil
+ }
+
+ // ...
+}
+```
+
+This makes the following values work within a Sentinel policy:
+`time.location.name`, `time.location.time_zone`.
+
+If a field has an empty `sentinel` struct tag (example: `sentinel:""`),
+then that field will not be accessible from a policy.
+
+#### Optional Interfaces
+
+The following are optional interfaces that can be implemented on top of
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace).
+All of these interfaces can be implemented at any level, except for
+[`framework.New`](#framework-new), which only works at the root level.
+
+##### `framework.Call`
+
+The
+[`framework.Call`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Call)
+interface can be implemented to support function calls.
+
+Implementing this interface is very similar to attribute access, except instead
+of returning the attribute value, you return a function. The framework uses Go
+reflection to determine the argument and result types and calls it. If the
+argument types do not match or the signature is otherwise invalid, an error is
+returned.
+
+For example, let's implement a function to add months to the current month:
+
+```sentinel
+func (m *root) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *root) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.time.AddDate(0, n, 0).Month()}
+}
+```
+
+You can now call the function. If today was in the month of September,
+the policy below would pass:
+
+```sentinel
+import "custom_time"
+
+main = custom_time.add_month(4).string == "January"
+```
+
+##### `framework.Map`
+
+The optional
+[`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+interface allows an alternative method to to present a namespace back to a
+Sentinel policy as a map.
+
+`framework.Map` is best paired with
+[`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys),
+which allows you to quickly fetch the necessary data via `Get` calls on the
+namespace:
+
+```
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil, nil
+}
+
+func (m *namespaceMonth) Map() (map[string]interface{}, error) {
+ return framework.MapFromKeys(m, []string{"string", "index"})
+}
+```
+
+##### `framework.New`
+
+The optional
+[`framework.New`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#New)
+interface can be implemented on a root namespace to add _methods_ to your
+namespaces.
+
+Data returned from a namespace is memoized and returned as a map, and is
+normally not callable - to call a function to operate on the data, you would
+need to create a new namespace from the top-level of the plugin, and then make a
+function call on the result within the same expression.
+
+Let's consider the [root example](#implementing-framework-root). Let's say,
+instead of hard-coding the time value at configuration, we wanted to load it
+on-demand using a `time.now` key, and allow `add_month` to be callable on that
+value. Our root and de-coupled time namespace would now look like:
+
+```
+type root struct{}
+
+func (m *root) Configure(raw map[string]interface{}) error { return nil }
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "now":
+ return &namespaceTime{Time: time.Now()}
+ }
+
+ return nil
+}
+
+func (m *root) New(data map[string]interface{}) (framework.Namespace, error) {
+ if v, ok := data["unix"]; ok {
+ if t, ok := v.(int64); !ok {
+ return &namespaceTime{Time: time.Unix(t, 0)}
+ }
+
+ return nil, fmt.Errorf("expected timestamp to be int64, got %T", v)
+ }
+
+ return nil, nil
+}
+
+type namespaceTime struct {
+ Time time.Time
+}
+
+func (m *namespaceTime) Get(key string) interface{} {
+ switch key {
+ case "unix":
+ return m.Time.Unix()
+
+ // case ...
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) Get(key string) (interface{}, error) {
+ return framework.MapFromKeys(m, []string{"unix", "..."})
+}
+
+func (m *namespaceTime) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.Time.AddDate(0, n, 0).Month()}
+}
+```
+
+Via this example, we can now create and assign a `namespaceTime` using `t = custom_time.now`, and then call `t.add_month(months_to_add)` to return a
+`namespaceMonth` for the corresponding month.
+
+Methods on a namespace can also _modfiy_ the receiver data. So you can, for
+example, add a method named `increment_month` that takes no arguments, but
+increments the time stored in `namespaceTime.Time` by a month. Subsequent
+statements using the receiver would see the new value.
+
+Note that there are some restrictions and guidelines that you should take into
+account when using `framework.New`:
+
+- Only data that makes it back to a policy can be used as receiver data to
+ instantiate new namespaces. As such it's recommended to make use of
+ [`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+ and
+ [`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys)
+ to return all of the attribute data you need to construct new objects.
+- `framework.New` is only supported on the root namespace and has no effect on
+ namespaces below the root. Check all possible cases of receiver data within
+ the top-level `New` function and return the appropriate namespace based on the
+ input data.
+- Do not return general errors from your `New` method. When an unknown lookup is
+ made off of memoized data, it will hit your `New` method for possible
+ instantiation and key calls. This will ensure `undefined` is correctly
+ returned for these cases.
+- `framework.New` is designed to add utility to plugins where calling methods on
+ a value is more intuitive than just making a function call, or just returning
+ all of a data set as a memoized map. Use it sparingly and avoid using it on
+ recursively complex data sets.
+
+### Testing
+
+The SDK exposes a testing framework to verify your plugin works as
+expected. This test framework integrates directly into `go test` so it
+is part of a familiar workflow.
+
+The test framework works by dynamically building your plugin and
+running it against the `sentinel` CLI to verify it behaves as expected.
+This ensures that your plugin builds, your plugin communicates via RPC
+correctly, and that the behavior within a policy is also correct.
+
+The example below shows a complete ready table-driven test for our time plugin.
+You can run this with a normal `go test`.
+
+```sentinel
+package main
+
+import (
+ "os"
+ "testing"
+
+ "github.com/hashicorp/sentinel-sdk"
+ plugintesting "github.com/hashicorp/sentinel-sdk/testing"
+)
+
+func TestMain(m *testing.M) {
+ exitCode := m.Run()
+ plugintesting.Clean()
+ os.Exit(exitCode)
+}
+
+func TestPlugin(t *testing.T) {
+ cases := []struct {
+ Name string
+ Data map[string]interface{}
+ Source string
+ }{
+ {
+ "month",
+ map[string]interface{}{"timestamp": 1495483674},
+ `main = subject.month.string == "September"`,
+ },
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.Name, func(t *testing.T) {
+ plugintesting.TestPlugin(t, plugintesting.TestPluginCase{
+ Config: tc.Data,
+ Source: tc.Source,
+ })
+ })
+ }
+}
+```
+
+### Building Your Plugin
+
+To build your plugin, you must first implement the `main` function.
+The main function should just use the `rpc.Serve` method to serve the
+plugin over the Sentinel RPC layer:
+
+```sentinel
+package main
+
+import (
+ "github.com/hashicorp/sentinel-sdk"
+ "github.com/hashicorp/sentinel-sdk/rpc"
+)
+
+func main() {
+ rpc.Serve(&rpc.ServeOpts{
+ PluginFunc: func() sdk.Plugin {
+ return &framework.Plugin{Root: &root{}}
+ },
+ })
+}
+```
+
+You can then build this using `go build`. The output can be named
+anything. Once your plugin is built, [install it](#installing-plugins)
+and try it out!
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/extending/static-imports.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/extending/static-imports.mdx
new file mode 100644
index 0000000000..cb51beb5d8
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/extending/static-imports.mdx
@@ -0,0 +1,48 @@
+---
+page_title: >-
+ Extending Sentinel: Static Imports
+sidebar_current: docs-extending-static-imports
+description: >-
+ Static imports allow you to write imports that use static data files.
+layout: docs
+---
+
+# Extending Sentinel: Static Imports
+
+Static imports allow you to write imports that use static data files. This can
+provide methods of consuming data in ways that are not available via
+[modules](./modules) or [plugins](./plugins).
+
+### Configuring a Static Import
+
+The [configuration page](/sentinel/configuration#static-imports) details how to
+configure static imports.
+
+### Usage
+
+Using a static import is not that different to using a plugin or module. The
+import must first be introduced to the policy via an import statement.
+
+```sentinel
+import "people"
+```
+
+From here, the static data can be used throughout the policy as you would any
+normal Sentinel value.
+
+```sentinel
+admins = filter people as person {
+ person.role is "Admin"
+}
+```
+
+One differentiator for static imports is that you can directly reference the
+import without the use of selectors. For example, to print the value;
+
+```sentinel
+print(people)
+```
+
+This is due to the static data being directly loaded and converted into a
+Sentinel value, ready for use. Static imports, like modules and plugins, cannot
+be assigned a new value.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/functions/append.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/functions/append.mdx
new file mode 100644
index 0000000000..81395154f6
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/functions/append.mdx
@@ -0,0 +1,46 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Append'
+sidebar_current: docs-funcs-append
+description: The built-in function `append` appends a value to the end of a list.
+layout: docs
+---
+
+# Builtin Function: append
+
+The built-in function `append` appends a value to the end of a list. The list
+is modified in-place. The return value will always be [undefined](/sentinel/language/undefined).
+
+`append` called on any value other than a list or undefined will result
+in an immediate error.
+
+Examples:
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+### Why does this function return undefined?
+
+This function returns `undefined` to avoid unintended side effects of the function in the context
+of a [rule](/sentinel/language/rules).
+
+For example, if `append` returned the list value as a result, the following policy would depend
+on the order in which rules are referenced:
+
+```sentinel
+list = []
+a = rule { append(list, 1) contains 1 }
+b = rule { append(list, 2) contains 2 }
+
+// Scenario 1: list becomes [1, 2]
+main = rule { a and b }
+
+// Scenario 2: list becomes [2, 1]
+main = rule { b and a }
+```
+
+This sort of side effect behavior introduces complex and difficult to track logical errors in programs.
+As a result, functions like this return `undefined` and modify values in-place.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/functions/delete.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/functions/delete.mdx
new file mode 100644
index 0000000000..3b60803997
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/functions/delete.mdx
@@ -0,0 +1,25 @@
+---
+page_title: 'Sentinel Language - Builtin Function: delete'
+sidebar_current: docs-funcs-delete
+description: The built-in function `delete` deletes an element from a map.
+layout: docs
+---
+
+# Builtin Function: delete
+
+The built-in function `delete` deletes an element from a map.
+
+If the element to delete does not exist, the map is left unchanged.
+Calling `delete` on a value that is not a map or
+[undefined](/sentinel/language/undefined)
+is an immediate error. The result of `delete` is always `undefined`.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/functions/error.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/functions/error.mdx
new file mode 100644
index 0000000000..96d253b662
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/functions/error.mdx
@@ -0,0 +1,15 @@
+---
+page_title: 'Sentinel Language - Builtin Function: error'
+sidebar_current: docs-funcs-error
+description: >-
+ The built-in function `error` is used to exit immediately with an error
+ message.
+layout: docs
+---
+
+# Builtin Function: error
+
+The built-in function `error` is used to exit immediately with an error message.
+
+Please see the [page on errors](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/functions/index.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/functions/index.mdx
new file mode 100644
index 0000000000..4e12143ad0
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/functions/index.mdx
@@ -0,0 +1,13 @@
+---
+page_title: Sentinel Language - Builtin Functions
+sidebar_current: docs-funcs
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Language: Builtin Functions
+
+Builtin functions are
+[functions](/sentinel/language/functions)
+that are available in all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in function.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/functions/keys.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/functions/keys.mdx
new file mode 100644
index 0000000000..a96cafe897
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/functions/keys.mdx
@@ -0,0 +1,22 @@
+---
+page_title: 'Sentinel Language - Builtin Function: keys'
+sidebar_current: docs-funcs-keys
+description: The built-in function `keys` returns the keys of a map.
+layout: docs
+---
+
+# Builtin Function: keys
+
+The built-in function `keys` returns the keys of a map.
+
+`keys` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned keys are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+```
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/functions/length.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/functions/length.mdx
new file mode 100644
index 0000000000..8b874b14c4
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/functions/length.mdx
@@ -0,0 +1,23 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Length'
+sidebar_current: docs-funcs-length
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Builtin Function: length
+
+The built-in function `length` returns the length of a collection or string.
+
+The length of a string is the number of bytes in the string. It is not
+necessarilly the number of characters. The length of a collection is the
+number of elements in that collection. The length of
+[undefined](/sentinel/language/undefined) is `undefined`.
+
+Examples:
+
+```sentinel
+length("foo") // 3
+length([1, 2, 3, 4]) // 4
+length({ "key": "value" }) // 1
+```
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/functions/print.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/functions/print.mdx
new file mode 100644
index 0000000000..bcf441d09d
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/functions/print.mdx
@@ -0,0 +1,13 @@
+---
+page_title: 'Sentinel Language - Builtin Function: print'
+sidebar_current: docs-funcs-print
+description: The built-in function `print` is used for logging and debugging.
+layout: docs
+---
+
+# Builtin Function: print
+
+The built-in function `print` is used for logging and debugging.
+
+Please see the [page on logging](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/functions/range.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/functions/range.mdx
new file mode 100644
index 0000000000..0f86bfc402
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/functions/range.mdx
@@ -0,0 +1,49 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Range'
+sidebar_current: docs-funcs-range
+description: The built-in function `range` returns a list of numbers in a range.
+layout: docs
+---
+
+# Builtin Function: range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it
+defaults to `1`.
+
+Negative steps are permitted and count down from `start` towards `end`, instead
+of up.
+
+The range ends when the next value given by the sum of the last value and `step`
+would exceed `end`, regardless of if it has been reached.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4] - starts at 1 instead of 0
+range(1, 5, 2) // [1,3] - 3+2 does not satisfy r[-1] < end
+range(0, -3, -1) // [0,-1,-2] - example of negative range
+```
+
+Impossible ranges, or ranges where `start` will never reach `end` given `step`,
+yield an empty list.
+
+```
+range(1, 1) // [] - already starting at 1
+range(0, 1, -1) // [] - negative step will never reach 1
+```
+
+Supplying an undefined value to any argument of `range` yields an undefined
+value back.
+
+A range with a zero step is a runtime error.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/functions/values.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/functions/values.mdx
new file mode 100644
index 0000000000..8f98d3ab56
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/functions/values.mdx
@@ -0,0 +1,22 @@
+---
+page_title: 'Sentinel Language - Builtin Function: values'
+sidebar_current: docs-funcs-values
+description: The built-in function `values` returns the values of a map.
+layout: docs
+---
+
+# Builtin Function: values
+
+The built-in function `values` returns the values of a map.
+
+`values` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned values are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+values(data) // [3, 2]
+```
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/imports/base64.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/imports/base64.mdx
new file mode 100644
index 0000000000..4e58429268
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/imports/base64.mdx
@@ -0,0 +1,46 @@
+---
+page_title: 'Import: base64'
+sidebar_current: docs-imports-base64
+description: The base64 import enables a Sentinel policy to encode and decode Base64 values.
+layout: docs
+---
+
+# Import: base64
+
+The base64 import enables a Sentinel policy to encode and decode Base64 values.
+
+### base64.encode(str)
+
+Encodes the string `str` into a Base64 encoded string, using the standard
+encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-4).
+
+```sentinel
+enc = base64.encode("foo") // "Zm9v"
+```
+
+### base64.decode(str)
+
+Decodes the Base64 encoded string `str`, returning the string result,
+using the standard encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-4).
+
+```sentinel
+dec = base64.decode("Zm9v") // "foo"
+```
+
+### base64.urlencode(str)
+
+Encodes the string `str` into a Base64 encoded string, using the alternate
+encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-5).
+
+```sentinel
+enc = base64.urlencode("foo") // "Zm9v"
+```
+
+### base64.urldecode(str)
+
+Decodes the Base64 encoded string `str`, returning the string result, using the
+alternate encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-5).
+
+```sentinel
+dec = base64.urldecode("Zm9v") // "foo"
+```
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/imports/decimal.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/imports/decimal.mdx
new file mode 100644
index 0000000000..a6633a38e0
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/imports/decimal.mdx
@@ -0,0 +1,318 @@
+---
+page_title: 'Import: decimal'
+sidebar_current: docs-imports-decimal
+description: |-
+ The decimal import provides functions for operating on numbers represented
+ as decimals.
+layout: docs
+---
+
+# Import: decimal
+
+The decimal import provides functions for operating on numbers represented
+as decimals.
+
+Binary floating-point numbers provided by the `float` type are unable to
+fully represent numbers like `1.1` and `2.2`. The decimal import can
+represent these numbers exactly, and so should be used when exactness is
+required.
+
+Decimals are created through the `new` function, which takes any type that
+can represent a number. Arguments to the decimal operators can also take
+a value of any of these types. The full list is:
+
+- float
+- int
+- string
+- existing decimal value
+
+### decimal.infinity(sign)
+
+Creates an infinite-value decimal. Infinite-value decimals are always larger
+or smaller, depending on sign, than any non-infinite decimals.
+
+```sentinel
+decimal.infinity(1) // +Infinity
+decimal.infinity(-1) // -Infinity
+```
+
+Also available as `decimal.inf`.
+
+### decimal.nan
+
+A decimal representing a "not-a-number" value. NaNs are not equal to any
+other number, even themselves.
+
+```sentinel
+decimal.new(-1).sqrt() // NaN
+decimal.new(0).mul(decimal.inf(1)) // NaN
+decimal.nan.is(decimal.nan) // false
+```
+
+### decimal.is_infinite(d)
+
+Evaluates to true if the decimal is infinite.
+
+```sentinel
+decimal.is_infinite(100) // false
+decimal.is_infinite("Infinity") // true
+```
+
+Also available as `decimal.is_inf`, `decimal.isinf` and `decimal.isinfinite`.
+
+### decimal.is_nan(d)
+
+Evaluates to true if the decimal is not-a-number.
+
+```sentinel
+decimal.is_nan("NaN") // true
+```
+
+Also available as `decimal.isnan`.
+
+### decimal.new(v)
+
+Constructs a decimal from another value.
+
+```sentinel
+decimal.new("1.1") // Constructs a decimal from a string.
+decimal.new(2.2) // Constructs a decimal from a float.
+decimal.new(3) // Constructs a decimal from an integer.
+decimal.new("1.1234E+400") // Constructs a decimal from a string using E-notation.
+```
+
+## Type: number
+
+Numbers are represented internally by a sign, coefficient, and exponent.
+The value is calculated with the formula `sign * coefficient * 10^exponent`.
+Exponent is limited to 32 bits, and the coefficient is limited by the total
+precision.
+
+The maximum precision a number can represent is 100 digits. This includes
+both before and after the decimal place.
+
+### number.string
+
+A string representation of the decimal.
+
+```sentinel
+decimal.new(1.5).string // 1.5
+```
+
+### number.sign
+
+A number between `-1` and `+1` representing the sign of the decimal.
+
+```sentinel
+decimal.new(1.5).sign // 1
+```
+
+### number.coefficient
+
+The coefficient component of the decimal.
+
+```sentinel
+decimal.new(1.5).coefficient // 15
+```
+
+### number.exponent
+
+The exponent component of the decimal.
+
+```sentinel
+decimal.new(1.5).exponent // -1
+```
+
+### number.float
+
+A floating-point representation of the decimal.
+
+```sentinel
+decimal.new(1.5).float // 1.500000
+```
+
+### number.int
+
+An integer representation of the decimal. This always rounds down to the nearest integer.
+
+```sentinel
+decimal.new(1.5).int // 1
+```
+
+### number.is(v)
+
+Test for equality with another value.
+
+```sentinel
+decimal.new(1.5).is(1) // false
+```
+
+### number.is_not(v)
+
+Test for inequality with another value.
+
+```sentinel
+decimal.new(1.5).is_not(1) // true
+```
+
+### number.less_than(v)
+
+Test that the decimal is less than another value.
+Can also be called as `number.lt(v)`
+
+```sentinel
+decimal.new(1.5).less_than(1) // false
+```
+
+### number.less_than_or_equals(v)
+
+Test that the decimal is less than or equals to another value.
+Can also be called as `number.lte(v)`
+
+```sentinel
+decimal.new(1.5).less_than_or_equals(1) // false
+```
+
+### number.greater_than(v)
+
+Test that the decimal is greater than another value.
+Can also be called as `number.gt(v)`
+
+```sentinel
+decimal.new(1.5).greater_than(1) // true
+```
+
+### number.greater_than_or_equals(v)
+
+Test that the decimal is greater than or equals to another value.
+Can also be called as `number.gte(v)`
+
+```sentinel
+decimal.new(1.5).greater_than_or_equals(1) // true
+```
+
+### number.add(v)
+
+Add another number to the decimal.
+
+```sentinel
+decimal.new(1.5).add(1) // 2.5
+```
+
+### number.subtract(v)
+
+Subtract another number from the decimal.
+Can also be called as `number.sub(v)`
+
+```sentinel
+decimal.new(1.5).subtract(1) // 0.5
+```
+
+### number.multiply(v)
+
+Multiply the decimal by a number.
+Can also be called as `number.mul(v)`
+
+```sentinel
+decimal.new(1.5).multiply(2) // 3.0
+```
+
+### number.divide(v)
+
+Divide the decimal by a number.
+Can also be called as `number.div(v)`
+
+```sentinel
+decimal.new(3.0).divide(1.5) // 2
+```
+
+### number.modulo(v)
+
+Find the remainder after dividing the decimal by a number.
+Can also be called as `mod`, `remainder`, or `rem`.
+
+```sentinel
+decimal.new(1.5).modulo(1) // 0.5
+```
+
+### number.power(v)
+
+Raise the decimal to a power.
+Can also be called as `number.pow(v)`
+
+```sentinel
+decimal.new(1.5).power(2) // 2.25
+```
+
+### number.exp()
+
+The natural exponent of the decimal. This calculates `e^number`.
+
+```sentinel
+decimal.new(1.5).exp() // 4.4816...
+```
+
+### number.loge()
+
+The natural logarithm of the decimal.
+Can also be called as `number.ln()`
+
+```sentinel
+decimal.new(1.5).loge() // 0.4054...
+```
+
+### number.log10()
+
+The base-10 logarithm of the decimal.
+Can also be called as `number.log()`
+
+```sentinel
+decimal.new(1.5).log10() // 0.1760...
+```
+
+### number.square_root()
+
+The square root of the decimal.
+Can also be called as `number.sqrt()`
+
+```sentinel
+decimal.new(1.5).square_root() // 1.2247...
+```
+
+### number.ceiling()
+
+Round the decimal to the smallest integer greater or equal to itself.
+Can also be called as `number.ceil()`
+
+```sentinel
+decimal.new(1.5).ceiling() // 2
+```
+
+### number.floor()
+
+Round the decimal to the largest integer less than or equal to itself.
+
+```sentinel
+decimal.new(1.5).floor() // 1
+```
+
+### number.absolute()
+
+The absolute value of the decimal.
+Can also be called as `number.abs()`
+
+```sentinel
+decimal.new(1.5).absolute() // 1.5
+decimal.new(-1.5).absolute() // 1.5
+```
+
+### number.negate()
+
+The negated value of the decimal. A positive decimal will become negative,
+and a negative decimal will become positive.
+Can also be called as `number.neg()`
+
+```sentinel
+decimal.new(1.5).negate() // -1.5
+decimal.new(-1.5).negate() // 1.5
+```
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/imports/http.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/imports/http.mdx
new file mode 100644
index 0000000000..f94ae98c27
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/imports/http.mdx
@@ -0,0 +1,370 @@
+---
+page_title: 'Import: http'
+sidebar_current: docs-imports-http
+description: The http import enables the use of HTTP-accessible data from outside the runtime in Sentinel policy rules.
+layout: docs
+---
+
+# Import: http
+
+The http import enables the use of HTTP-accessible data from outside
+the runtime in Sentinel policy rules.
+
+The simplest example of the import in use would be:
+
+```sentinel
+import "http"
+
+resp = http.get("https://example.hashicorp.com")
+main = rule { resp.body contains "something" }
+```
+
+By default, any request response that is not a successful "200 OK" HTTP
+response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information and
+how to customize this behavior.
+
+For more advanced requests which require setting request headers, use a
+[`request` type](#type-request):
+
+```sentinel
+import "http"
+import "json"
+
+param token
+
+req = http.request("https://example.hashicorp.com").with_header("Authorization", "token "+token)
+resp = json.unmarshal(http.get(req).body)
+main = rule { resp["some_key"] is true }
+```
+
+The `with_header` function above returns the `request` object with the
+`Authorization` HTTP header set to value of the variable `some_value`. Many
+of the methods within the http import API are designed around this concept
+of method chaining, allowing for complex building of HTTP requests
+encapsulated in functions. The following is an idiomatic example further
+demonstrating this pattern:
+
+```sentinel
+import "http"
+import "json"
+
+param github_user
+param github_token
+
+client = func(req) {
+ return http.with_timeout(5).with_retries(3)
+}
+
+github_request = func(path) {
+ full_url = "https://api.github.com" + path
+ return http.request(full_url).
+ with_basic_auth(github_user, github_token).
+ with_header("Accept", "application/vnd.github.v3+json").
+ with_header("Custom", "foo"),
+}
+
+default_response = client.get(github_request("/example"))
+
+# Override the Custom header for this single request
+custom_header_response = json.unmarshal(
+ client.get(
+ github_request("/example").with_header("Custom", "bar"),
+ ),
+)
+
+# Override the timeout value for a known slower request
+slow_response = json.unmarshal(
+ client.with_timeout(15).get(github_request("/slow-endpoint")),
+)
+
+some_key_is_true = rule { json.unmarshal(default_response.body)["some_key"] is true }
+body_contains_text = rule { custom_header_response.body contains "something" }
+response_is_ok = rule { slow_response.status_code is 200 }
+
+main = rule { some_key_is_true and body_contains_text and response_is_ok }
+```
+
+### http.client
+
+Retrieve the base HTTP client with default settings. The returned client may be
+configured by calling configuration functions on it; all of these configuration
+functions on are also aliased as top-level functions in this namespace. See
+[`client`](#type-client) below.
+
+### http.request(url)
+
+Create a new [`request`](#type-request) to the specified `url` (string). A
+`request` type enables customization of headers and other concerns before then
+providing the constructed `request` to [`get()`](#client-get-url_or_request),
+[`post()`](#client-post-url_or_request) or [`send()`](#client-send-request).
+
+```sentinel
+req = http.request("example.hashicorp.com/foo").with_header("Foo", "bar")
+resp = http.get(req)
+```
+
+### http.methodPost
+
+Returns a string containing the accepted verb for POST requests, `"POST"`.
+
+### http.methodGet
+
+Returns a string containing the accepted verb for GET requests, `"GET"`.
+
+## Type: client
+
+All of the following configuration functions on the default client are also
+aliased as top-level functions in the import. This allows a short-hand (and
+idiomatic) way of configuring a new client without having to directly access
+`http.client` each time:
+
+```sentinel
+# The following two lines are semantically the same:
+resp = http.client.with_timeout(5).get(url)
+resp = http.with_timeout(5).get(url)
+```
+
+### url_or_request
+
+The [`get()`](#client-get-url_or_request) and [`post()`](#client-post-url_or_request)
+functions accept either a URL string or a request object, noted as
+`url_or_request` throughout the documentation.
+
+When `url_or_request` is a string, a request is made with the URL
+specified by the string. To customize HTTP headers and other settings, pass
+a request. By default, a request has a timeout value of 10 seconds and 1
+retry. These settings can be adjusted with [`with_timeout()`](#clientwith_timeoutinteger) and
+[`with_retries()`](#clientwith_retriesinteger), respectively.
+
+### Redirects
+
+If the response is one of the following redirect codes, the client follows the
+redirect, up to a maximum of 10 redirects:
+
+- 301 (Moved Permanently)
+- 302 (Found)
+- 303 (See Other)
+- 307 (Temporary Redirect)
+- 308 (Permanent Redirect)
+
+If the redirect limit is exceeded, the result is a policy error.
+
+By default, after any redirects are followed (up to the maximum), any request
+response that is not a successful "200 OK" response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information
+and how to customize this behavior.
+
+### client.get(url_or_request)
+
+Issue a GET request with a URL string or a `request` type. Returns a `response`
+type.
+
+```sentinel
+import "http"
+
+resp = http.get("https://example.hashicorp.com")
+main = rule { resp.body contains "something" }
+```
+
+### client.post(url_or_request)
+
+Issue a POST request with a URL string or a `request` type. Returns a `response`
+type.
+
+```sentinel
+import "http"
+
+req = http.request("https://example.hashicorp.com")
+ .with_body(json.marshal({"foo": "bar"}))
+resp = http.post(req)
+main = rule { resp.body contains "something" }
+```
+
+### client.send(request)
+
+Make a request using the supplied `request` type. Returns a `response`.
+
+```sentinel
+import "http"
+
+req = http.request("https://example.hashicorp.com")
+resp = http.send(req)
+main = rule { resp.body contains "something" }
+```
+
+### client.accept_status_codes(list)
+
+Configures a client to not automatically cause a policy failure if
+the resulting response's status code is in `list`. Any status code received
+that is not present in this list will trigger a runtime error.
+
+By default, any request response that is not a successful "200 OK" response
+causes a policy error. This is for convenience, as the most common scenario
+by far is to receive a response and immediately signal a policy error if the
+status code is not 200. Use this scoping to specify a list of non-redirect
+HTTP codes that you wish to accept without error and evaluate the response
+yourself.
+
+Redirects are not considered final status codes in this context. That is,
+redirects are always followed up to the maximum allowed value (see [`Redirects`](#redirects))
+and the final response code in the redirect chain is validated against
+`list`.
+
+```sentinel
+resp = http.accept_status_codes([200, 404]).get(url)
+main = rule { resp.status_code is 404 }
+```
+
+### client.accepted_status_codes
+
+Returns a list of the client's accepted status codes.
+
+```sentinel
+client = http.accept_status_codes([200, 404])
+main = rule { client.accepted_status_codes contains 404 }
+```
+
+### client.with_retries(integer)
+
+Sets the maximum number of retries, specified by `integer`, that should be
+attempted on an unsuccessful request. An unsuccessful request is considered
+to be any 5xx response except `501 (Not Implemented)` or a request timeout.
+By default, one retry is attempted.
+
+The maximum number of retries is 10, for a total of 11 request attempts.
+When a request exceeds the maximum number of retries without a response, the
+result is a policy error. Specifying a number larger than this will result
+in a runtime error.
+
+Between each retry, an exponentially increasing delay is observed, allowing
+time for the server to recover. This delay starts at 1 second for the first
+retry and increases each attempt thereafter. The maximum delay for a single
+attempt is 30 seconds.
+
+Retries can be disabled by specifying 0.
+
+### client.retries
+
+Returns the client's configured number of retries as an integer.
+
+### client.with_timeout(integer)
+
+Sets the request timeout to the number of seconds indicated by `integer`.
+
+Each individual request performed by the HTTP client will be limited by the
+given request timeout. This timeout includes connection time, any redirects,
+and reading the response body.
+
+A maximum of 30 seconds is allowed. Specifying a number larger than this
+will result in a runtime error.
+
+By default, requests will time out after 10 seconds.
+
+### client.timeout
+
+Returns the client's configured timeout in whole seconds as an integer.
+
+### client.without_certificate_verification()
+
+Skips verification of TLS certificates during secure HTTP operations,
+allowing for requests to `https://` endpoints which are secured with a TLS
+certificate signed by an untrusted certificate authority. Takes no arguments.
+
+By default, the HTTP client will trigger a runtime error if a TLS certificate
+presented by a server is from an untrusted certificate authority.
+
+Do not change this setting unless you are aware of the risks involved.
+Without verification, TLS accepts any certificate presented by the server
+and any host name in that certificate. In this mode, TLS is susceptible to
+man-in-the-middle attacks. This option should only be used for testing or in
+trusted networks with self-signed certificates.
+
+```sentinel
+http.without_certificate_verification().get(url)
+```
+
+### client.certificate_verification
+
+Returns true if the client requires TLS certificates to be verifiable.
+
+## Type: request
+
+### request.url
+
+Returns the request URL as a string.
+
+### request.headers
+
+Returns the configured request headers as a string-keyed map of strings.
+
+### request.body
+
+Returns the configured body as a string.
+
+### request.with_header(key, value)
+
+Returns a `request` with the header `key` (string) set to `value` (string).
+
+Values are overridden on subsequent calls to the same `key`. To specify
+multiple values, use the existing value when setting the new one.
+
+```sentinel
+original = http.request("http://example.hashicorp.com").with_header("Foo", "bar")
+overridden = original.with_header("Foo", "baz")
+multi_value = original.with_header("Foo", original.headers["Foo"] + ",baz")
+
+main = rule {
+ original.headers["Foo"] is "bar" and
+ overridden.headers["Foo"] is "baz" and
+ multi_value.headers["Foo"] is "bar,baz"
+}
+```
+
+### request.with_basic_auth(username, password)
+
+Returns a `request` with the `Authorization` header set to use HTTP Basic
+Authentication with the provided username and password.
+
+Note that with HTTP Basic Authentication the provided username and password
+are encoded, but not encrypted.
+
+### request.with_body(body)
+
+Returns a `request` with the `body` set. This value will then be sent as a body
+as part of the request. Commonly used along with the [`post()`](#client-post-url_or_request) function.
+
+```sentinel
+req = http.request("http://example.hashicorp.com")
+ .with_header("Content-Type", "application/json")
+ .with_body(json.marshal({ "foo": "bar" }))
+
+resp = http.post(req)
+```
+
+## Type: response
+
+### response.status_code
+
+The response status code as an integer, e.g. `200`.
+
+### response.headers
+
+The response headers as a map.
+
+### response.body
+
+The response body as a string. Callers should unmarshal the content to a useful
+representation as necessary based on the content type. For example, if the
+response is in JSON:
+
+```sentinel
+import "http"
+import "json"
+
+# This example endpoint returns {"some_key": true}
+req = http.request("https://example.hashicorp.com/some-json")
+
+resp = json.unmarshal(http.get(req).body)
+main = rule { keys(resp) contains "some_key" and resp["some_key"] is true }
+```
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/imports/index.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/imports/index.mdx
new file mode 100644
index 0000000000..e45bf81b81
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/imports/index.mdx
@@ -0,0 +1,18 @@
+---
+page_title: Sentinel Language - Standard Imports
+sidebar_current: docs-imports
+description: >-
+ Standard Imports are the built-in imports that are available to all Sentinel
+ policies.
+layout: docs
+---
+
+# Standard Imports
+
+Standard Imports are the built-in [imports](/sentinel/language/imports)
+that are available to all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in import.
+
+Sentinel-embedded applications can choose to allow or deny certain
+standard imports. Please reference the documentation for the Sentinel-enabled
+application you're using to determine if all standard imports are available.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/imports/json.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/imports/json.mdx
new file mode 100644
index 0000000000..3355458f14
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/imports/json.mdx
@@ -0,0 +1,31 @@
+---
+page_title: 'Import: json'
+sidebar_current: docs-imports-json
+description: The json import enables a Sentinel policy to parse and access a JSON document.
+layout: docs
+---
+
+# Import: json
+
+The json import enables a Sentinel policy to parse and access a JSON
+document.
+
+### json.unmarshal(obj)
+
+Unmarshals the JSON object `obj` into a native Sentinel structure.
+
+All native JSON types can be represented perfectly as Sentinel native types.
+
+```sentinel
+// Typically the input for this would come from an external source.
+config = json.unmarshal("{ \"foo\": 42 }")
+config.foo // 42
+config.bar // undefined (as usual for accessing a non-existent map key)
+```
+
+### json.marshal(obj)
+
+Marshals the Sentinel object `obj` into a JSON object encoded as a string.
+
+Functions and `undefined` cannot be natively encoded as JSON. If values of
+either type are found in the structure, this will return undefined.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/imports/runtime.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/imports/runtime.mdx
new file mode 100644
index 0000000000..6493bbbb22
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/imports/runtime.mdx
@@ -0,0 +1,16 @@
+---
+page_title: 'Import: runtime'
+sidebar_current: docs-imports-runtime
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: runtime
+
+The runtime import contains various information about the Sentinel runtime. It
+can be used to get information about the runtime as it's embedded in the CLI or
+a specific integration, such as validating a specific runtime version.
+
+### runtime.version
+
+Returns the version of Sentinel within this implementation.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/imports/sockaddr.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/imports/sockaddr.mdx
new file mode 100644
index 0000000000..a6842ba5f0
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/imports/sockaddr.mdx
@@ -0,0 +1,41 @@
+---
+page_title: 'Import: sockaddr'
+sidebar_current: docs-imports-sockaddr
+description: The sockaddr import makes working with IP addresses easy.
+layout: docs
+---
+
+# Import: sockaddr
+
+The sockaddr import makes working with IP addresses easy.
+
+### sockaddr.is_contained(outer, inner)
+
+The is_contained function returns true if `inner` is an address that
+is contained within `outer`.
+
+```sentinel
+sockaddr.is_contained("192.168.0.0/24", "192.168.0.32") // true
+sockaddr.is_contained("192.168.0.0/24", "192.174.1.1") // false
+```
+
+### sockaddr.is_equal(addr1, addr2)
+
+The is_equal function returns true if addr1 is equal to addr2.
+
+```sentinel
+sockaddr.is_equal("192.168.12.24", "192.168.12.24") // true
+```
+
+### sockaddr.is_ipv4(addr)
+
+The is_ipv4 function returns true if addr is an IPv4 address.
+
+```sentinel
+sockaddr.is_ipv4("192.168.12.24") // true
+sockaddr.is_ipv4("2001:0db8:85a3:0000:0000:8a2e:0370:7334") // false
+```
+
+### sockaddr.is_ipv6(addr)
+
+The is_ipv6 function returns true if addr is an IPv6 address.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/imports/strings.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/imports/strings.mdx
new file mode 100644
index 0000000000..823c5d8f07
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/imports/strings.mdx
@@ -0,0 +1,91 @@
+---
+page_title: 'Import: strings'
+sidebar_current: docs-imports-strings
+description: The strings import exposes common string operations.
+layout: docs
+---
+
+# Import: strings
+
+The strings import exposes common string operations.
+
+### strings.has_prefix(s, prefix)
+
+Returns true if `s` starts with `prefix`.
+
+```sentinel
+strings.has_prefix("billing-id", "billing-") // true
+strings.has_prefix("bill-id", "billing-") // false
+```
+
+### strings.has_suffix(s, suffix)
+
+Returns true if `s` ends with `suffix`.
+
+```sentinel
+strings.has_suffix("billing-id", "id") // true
+strings.has_suffix("billing-name", "id") // false
+```
+
+### strings.join(a, sep)
+
+Joins a list with the string supplied by `sep`.
+
+Multi-dimensional lists are supported, and non-string primitive
+types will be converted to their string equivalents. Maps and
+other complex non-list types are not supported.
+
+```sentinel
+strings.join(["foo", "bar", "baz"], ".") // "foo.bar.baz"
+strings.join([["foo", "bar"], "baz"], ".") // "foo.bar.baz"
+strings.join(["a", 1, 1.01, true], ",") // "a,1,1.01,true"
+```
+
+### strings.trim_prefix(s, prefix)
+
+Trim the prefix from the string `s`. If the string doesn't have the
+prefix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_prefix("billing-id", "billing-") // "id"
+strings.trim_prefix("bill-id", "billing-") // "bill-id"
+```
+
+### strings.trim_suffix(s, suffix)
+
+Trim the suffix from the string `s`. If the string doesn't have the
+suffix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_suffix("billing-id", "-id") // "billing"
+strings.trim_suffix("bill-id", "-foo") // "bill-id"
+```
+
+### strings.to_lower(s)
+
+Lowercase the string s.
+
+```sentinel
+strings.to_lower("FoO") // "foo"
+```
+
+### strings.to_upper(s)
+
+Uppercase the string s.
+
+```sentinel
+strings.to_upper("FoO") // "FOO"
+```
+
+### strings.split(s, sep)
+
+Split the string 's' via a substring 'sep'. If 'sep' is not contained in
+'s', the return value will be a single-element list containing only 's'.
+As a special-case, if the input is already a list, it will be returned
+as-is. This allows calling the split function when not knowing if the input
+is already a list or not.
+
+```sentinel
+strings.split("foo,bar,baz", ",") // ["foo", "bar", "baz"]
+strings.split(["foo", "bar", "baz"], ",") // ["foo", "bar", "baz"]
+```
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/imports/time.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/imports/time.mdx
new file mode 100644
index 0000000000..3a675e2bbd
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/imports/time.mdx
@@ -0,0 +1,257 @@
+---
+page_title: 'Import: time'
+sidebar_current: docs-imports-time
+description: The time import provides access to the execution time and time functions.
+layout: docs
+---
+
+# Import: time
+
+The time import provides access to the execution time and time functions.
+
+During the execution of a policy the time is fixed to the moment of
+execution. This gives the illusion that a policy instantly executes at a
+single moment of time.
+
+The import uses Coordinated Universal Time (UTC).
+
+As an example of this, if a policy accessed `time.now.second` multiple times,
+for each access the same value would be returned even if they are several seconds apart.
+This is the execution `timespace`, which is mapped to `time.now`.
+
+It is also possible to run functions on a custom time by using the
+`time.load()` function to create a new timespace from the specified value.
+See the documentation for available actions on timespaces.
+
+Times specified as the reference time or via the `time.load()` function can
+be specified in a `timeish` fashion. This is either one of:
+
+- The number of seconds since the Unix epoch (Thursday, 1 January 1970,
+ 00:00:00 UTC),
+- A timestamp matching the format outlined in RFC3339, either in the
+ format `2006-01-02T15:04:05+07:00`, which includes a timezone offset, or
+ `2006-01-02T15:04:05Z`, which indicates UTC.
+
+### time.now
+
+Create a `timespace` locked either to execution time or, if set, the
+reference time. In a given execution, this will always produce the same
+value.
+
+```sentinel
+time.now // {"day": 17, "hour": 23, "minute": 19, "month": 11 ... }
+```
+
+### time.load(timeish)
+
+Create a timespace using the given value. Please see the import
+documentation for valid values for "timeish".
+
+```sentinel
+time.load("2019-11-16T01:20:30Z") // {"day": 16, "hour": 1, "minute": 20, "month": 11 ... }
+```
+
+### time.nanosecond
+
+A nanosecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.nanosecond * 1) // 2019-11-16T01:20:30.000000001Z
+```
+
+### time.microsecond
+
+A microsecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.microsecond * 1) // 2019-11-16T01:20:30.000001Z
+```
+
+### time.millisecond
+
+A millisecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.millisecond * 1) // 2019-11-16T01:20:30.001Z
+```
+
+### time.second
+
+A second duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.second * 1) // 2019-11-16T01:20:31Z
+```
+
+### time.minute
+
+A minute for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.minute * 1) // 2019-11-16T01:21:30Z
+```
+
+### time.hour
+
+An hour duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+## Type: timespace
+
+### timespace.hour
+
+The hour from 0 to 23.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").hour // 1
+```
+
+### timespace.minute
+
+The minute from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").minute // 20
+```
+
+### timespace.second
+
+The second from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").second // 30
+```
+
+### timespace.day
+
+The day of the month, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").day // 16
+```
+
+### timespace.month
+
+The month of the year as an integer, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month // 11
+```
+
+### timespace.month_name
+
+The name of the month of the year, in English. Example: `January`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month_name // November
+```
+
+### timespace.weekday
+
+The weekday as an integer, where 0 is Sunday and 6 is Saturday.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday // 6
+```
+
+### timespace.weekday_name
+
+The name of the day of the week, in English. Example: `Sunday`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday_name // Saturday
+```
+
+### timespace.year
+
+The year as an integer.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").year // 2019
+```
+
+### timespace.unix
+
+The number of seconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix // 1573867230
+```
+
+### timespace.unix_nano
+
+The number of nanoseconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix_nano // 1573867230000000000
+```
+
+### timespace.zone
+
+The timezone offset, in seconds. This can be a negative number to indicate
+time west of UTC.
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone // -28800
+```
+
+### timespace.zone_string
+
+The timezone offset, in the format `+HH:MM` (example: `-08:00` for PST).
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone_string // -08:00
+```
+
+### timespace.before(timeish_or_timespace)
+
+Check if the time in the timespace is before the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").before("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.after(timeish_or_timespace)
+
+Check if the time in the timespace is after the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").after("2019-11-15T01:20:30Z") // true
+```
+
+### timespace.equal(timeish_or_timespace)
+
+Check if two times are equivalent
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").equal("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.add(duration)
+
+Add a duration in nanoseconds to the time in the timespace and return a new timespace. The
+duration can be negative. The duration should be specified as an integer
+multiplied with the correct unit.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+### timespace.sub(timeish_or_timespace)
+
+Subtract a time from the time in the timespace and return a duration in nanoseconds.
+
+```sentinel
+// Gives a duration of 3 hours.
+duration = time.load(
+ "2019-11-16T01:20:30Z",
+).sub("2019-11-16T04:20:30Z")
+
+// Returns 2019-11-16T01:20:30Z
+time.load(
+ "2019-11-16T04:20:30Z",
+).add(duration)
+```
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/imports/types.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/imports/types.mdx
new file mode 100644
index 0000000000..fcfdbd2ae5
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/imports/types.mdx
@@ -0,0 +1,35 @@
+---
+page_title: 'Import: types'
+sidebar_current: docs-imports-types
+description: The types import enables a Sentinel policy to parse an object's type.
+layout: docs
+---
+
+# Import: types
+
+The types import enables a Sentinel policy to parse an object's type.
+
+### types.type_of(obj)
+
+Returns the object's type as a string
+
+```sentinel playground
+import "types"
+
+// Typically the inputs would come from an external source.
+is_boolean = rule { types.type_of(true) is "bool" }
+is_string = rule { types.type_of("Hello!") is "string" }
+is_integer = rule { types.type_of(42) is "int" }
+is_float = rule { types.type_of(42.123) is "float" }
+is_null = rule { types.type_of(null) is "null" }
+is_undefined = rule { types.type_of(undefined) is "undefined" }
+
+main = rule {
+ is_boolean and
+ is_string and
+ is_integer and
+ is_float and
+ is_null and
+ is_undefined
+}
+```
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/imports/units.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/imports/units.mdx
new file mode 100644
index 0000000000..e2ad3442f9
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/imports/units.mdx
@@ -0,0 +1,39 @@
+---
+page_title: 'Import: units'
+sidebar_current: docs-imports-units
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: units
+
+The units import provides access to quick calculations for various byte
+units.
+
+Note that this import uses base-2 terminology:
+
+- One kilobyte is 1024 bytes
+- One megabyte is 1024^2 = 1048576 bytes
+- One gigabyte is 1024^3 = 1073741824 bytes
+- One terabyte is 1024^4 = 1099511627776 bytes
+- One petabyte is 1024^5 = 1125899906842624 bytes
+
+### units.kilobyte
+
+The base-2 representation of a kilobyte (1024 bytes).
+
+### units.megabyte
+
+The base-2 representation of a megabyte (1048576 bytes).
+
+### units.gigabyte
+
+The base-2 representation of a gigabyte (1073741824 bytes).
+
+### units.terabyte
+
+The base-2 representation of a terabyte (1099511627776 bytes).
+
+### units.petabyte
+
+The base-2 representation of a petabyte (1125899906842624 bytes).
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/imports/version.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/imports/version.mdx
new file mode 100644
index 0000000000..59366c1087
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/imports/version.mdx
@@ -0,0 +1,151 @@
+---
+page_title: 'Import: version'
+sidebar_current: docs-imports-version
+description: |-
+ The version import provides functions for parsing versions and version constraints,
+ and verifying versions against a set of constraints.
+layout: docs
+---
+
+# Import: version
+
+The version import provides functions for parsing versions and version constraints,
+and verifying versions against a set of constraints.
+
+Versions are created through the `new` function, which accepts a string type in dotted-tri format (i.e. 1.0.0-alpha.1+001) that can represent a version.
+
+### version.new(v)
+
+Constructs a version from string value.
+
+```sentinel
+version.new("1.0.0-alpha.1+001")
+```
+
+### version.major
+
+An integer representation of the Major number for a given version .
+
+```sentinel
+version.new("1.0.0-alpha.1+001").major // 1
+```
+
+### version.minor
+
+An integer representation of the Minor number for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").minor // 0
+```
+
+### version.patch
+
+An integer representation of the Patch number for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").patch // 0
+```
+
+### version.prerelease
+
+A string representation of the Prerelease for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").prerelease // "alpha.1"
+```
+
+### version.metadata
+
+A string representation of the Metadata for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").metadata // "001"
+```
+
+### version.version
+
+A string representation of the version including pre-release and metadata information.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").version // "1.0.0-alpha.1+001"
+```
+
+### version.greater_than(v)
+
+Tests that the version is greater than a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").greater_than("0.12.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").gt("1.0.1-alpha.1+001") // False
+```
+
+### version.greater_than_or_equals(v)
+
+Tests that the version is greater than or equal to a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").greater_than_or_equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").gte("1.0.1-alpha.1+001") // False
+```
+
+### version.less_than(v)
+
+Tests that the version is less than a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").less_than("1.0.1-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").lt("0.12.0-alpha.1+001") // False
+```
+
+### version.less_than_or_equals(v)
+
+Tests that the version is less than or equal to a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").less_than_or_equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").lte("0.12.0-alpha.1+001") // False
+```
+
+### version.less_than_or_equals(v)
+
+Tests that the version equals a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").eq("0.12.0-alpha.1+001") // False
+```
+
+### version.compare(v)
+
+Compares one version with a given version. Compare returns -1, 0, or 1 if the version is less, equal, or greater than the other version, respectively.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").compare("0.12.0-alpha.1+001") // -1
+version.new("1.0.0-alpha.1+001").compare("1.0.0-alpha.1+001") // 0
+version.new("0.12.0-alpha.1+001").cmp("1.0.0-alpha.1+001") // 1
+```
+
+### version.satisfies(v, x)
+
+Tests that a version adheres to one or more version constraints. Satisfies supports the following restriction operators:
+
+- =
+- !=
+- \>
+- <
+- \>=
+- <=
+- ~>
+
+Satisfies supports the following usage scenarios:
+
+- A constraint with a pre-release can only match a pre-release version that contains the same major, minor and patch identifiers.
+- A constraint without a pre-release can only match a version without a pre-release.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").satisfies("> 1.0.0-alpha.1+001") // false
+version.new("1.0.0-alpha.1+001").satisfies(">= 1.0.0-alpha.1+001") // true
+version.new("1.0.0-alpha.1+001").satisfies(">= 1.0.0-alpha.1+001, < 1.0.0-beta+001") // true
+version.new("1.0.0").sat("= 1.0.0") // true
+version.new("0.12.7").sat("~> 0.12.0") // true
+```
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/index.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/index.mdx
new file mode 100644
index 0000000000..4497b40b83
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/index.mdx
@@ -0,0 +1,31 @@
+---
+layout: 'docs'
+page_title: 'Documentation'
+sidebar_current: 'docs-home'
+description: |-
+ Sentinel is a language and framework for policy built to be embedded in existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+[introduction](/sentinel/intro) to understand what Sentinel is, how it
+compares to other software, and more.
+
+
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/language/arithmetic.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/language/arithmetic.mdx
new file mode 100644
index 0000000000..06b1256a1b
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/language/arithmetic.mdx
@@ -0,0 +1,67 @@
+---
+page_title: Sentinel Language - Arithmetic
+sidebar_current: docs-language-arith
+description: >-
+ Sentinel supports arithmetic operators for integers and floats. Sentinel
+ supports sum, difference, product, quotient, and remainder.
+layout: docs
+---
+
+# Language: Arithmetic
+
+Sentinel supports arithmetic operators for integers and floats. Sentinel
+supports sum, difference, product, quotient, and remainder.
+
+```plaintext
++ sum
+* difference
+* product
+/ quotient
+% remainder
+```
+
+These operators work in a typical infix style:
+
+```sentinel
+4 + 8 // 12
+8 * 2 // 16
+8 / 4 // 2
+8 / 5 // 1
+8 % 5 // 3
+```
+
+## Order of Operations
+
+Arithmetic follows a standard mathematical order of operations.
+Grouping with parentheses can be used to affect ordering.
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+(4 + 5) * 2 // 18
+```
+
+A full table of operator precendence can be found on the
+[boolean expressions page](/sentinel/language/boolexpr). This
+shows how arithmetic operators relate to other operators.
+
+## Integer Division
+
+Integer division with a remainder rounds down to the nearest integer.
+Example: `8 / 3 is 2`.
+
+If the divisor is zero, an error occurs.
+
+## Mixed Numeric Operations
+
+Mixed numeric operations between integer and floating-point values are
+permitted. The result is a floating-point operation with the integer converted
+to a floating-point value for purposes of calculation.
+
+```sentinel
+import "types"
+
+a = 1.1 + 1 // 2.1
+types.type_of(a) // "float"
+```
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/language/boolexpr.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/language/boolexpr.mdx
new file mode 100644
index 0000000000..22e13e05c4
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/language/boolexpr.mdx
@@ -0,0 +1,207 @@
+---
+page_title: Sentinel Language - Boolean Expressions
+sidebar_current: docs-language-boolexpr
+description: >-
+ Boolean expressions are expressions that evaluate to a boolean value from the
+ result of a combination of one or more comparisons and logical operators.
+layout: docs
+---
+
+# Language: Boolean Expressions
+
+Boolean expressions are expressions that evaluate to a boolean value
+from the result of a combination of one or more comparisons and logical
+operators. Boolean expressions form the basis of policy since policy can be broken
+down to a set of logical decisions that turn into true or false.
+
+A single boolean expression is the body of a [rule](/sentinel/language/rules).
+Additionally, boolean expressions are used with [conditionals](/sentinel/language/conditionals),
+and more.
+
+## Order of Operations
+
+The precedence of operators is shown below. Operators with higher
+precedence values (larger numbers) are evaluated first. Operators with
+the same precedence value are evaluated left-to-right.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Examples of this precedence are shown below:
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+```
+
+## Logical Operators
+
+Logical operators are used to change or combine logical expressions.
+There are three binary operators `and`, `or`, and `xor`. There is
+a single unary operator `not` (which can also be specified as `!`).
+
+The binary operators require two boolean operands and the unary
+operator requires a single boolean operand. In both case, the operands
+are values or expressions that are boolean types.
+
+Below is a basic explanation of the logical operators directly from
+the specification:
+
+```sentinel
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+Using parentheses to group boolean expressions, you can combine
+boolean expressions to become more complex or affect ordering:
+
+```sentinel
+(p and q) or r
+p and (q or r)
+```
+
+## Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+For any comparison, the two operands must be equivalent types. The one
+exception are integers and floats. When an integer is compared with
+a float, the integer is promoted to a floating point value.
+
+The available comparison operators are:
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+The behavior of `is` with `==` and `is not` with `!=` is identical.
+
+Example comparisons:
+
+```sentinel
+name is "Mitchell"
+idnumber > 42
+idnumber <= 12
+```
+
+Using the logical operators, these can be combined:
+
+```sentinel
+name is "Mitchell" and idnumber > 42
+```
+
+The language specification provides more detail on exactly
+[how comparison behaves](/sentinel/language/spec#comparison-operators).
+
+## Set Operators
+
+The set operators `contains` and `in` test for inclusion in a collection
+(a list or map).
+
+Set operators may 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.
+
+`contains` tests if the left-hand collection contains the right-hand value.
+`in` tests if the right-hand collection contains the left-hand value. For
+maps, "contains" looks at the keys, not the values.
+
+Examples:
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+```
+
+## Matches Operator
+
+The `matches` operator tests if a string matches a regular expression.
+The `matches` operator can be negated by prefixing the operator with
+`not`, such as `not matches`.
+
+The left-hand value is the string to test. The right-hand value is
+a string value representing a regular expression. The syntax of the regular
+expression is the same general syntax used by Python, Ruby, and other languages.
+The precise syntax accepted is the syntax accepted by RE2.
+
+Regular expressions are not anchored by default; any anchoring must be
+explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+## Any, All Expressions
+
+`any` and `all` expressions allow testing that any or all elements of a
+collection match some boolean expression. The result of an `any` and `all`
+expression is a boolean.
+
+`any` returns the boolean value `true` if any value in the collection expression
+results in the body expression evaluating to `true`. If the body expression
+evalutes to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression
+result in the body expression evaluating to `true`. If any value in the
+collection expression result in the body expression evaluating to `false`,
+the `all` expression returns `false`.
+
+For empty collections, `any` returns `false` and `all` returns `true`.
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" }
+any group.tasks as t { t.driver is "vmware" }
+```
+
+Since `any` and `all` expressions are themselves boolean expressions, they
+can be combined with other operators:
+
+```sentinel
+any ["a", "b"] as char { char is "a" } or
+other_value is "another"
+```
+
+## Emptiness Comparison
+
+The expressions `is empty` and `is not empty` provide a convenience
+method for determining the emptiness of a value. It supports the same types
+as the [built-in length](/sentinel/functions/length), collections and strings.
+
+```sentinel
+[] is empty // true
+[] is not empty // false
+["foo"] is empty // false
+["foo"] is not empty // true
+undefined is empty // undefined
+undefined is not empty // undefined
+```
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/language/collection-operations.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/language/collection-operations.mdx
new file mode 100644
index 0000000000..41da8f3584
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/language/collection-operations.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Sentinel Language - Collection Operations
+sidebar_current: docs-language-collection-operations
+description: |-
+ Operations for interacting with collections (list, map).
+layout: docs
+---
+
+# Language: Collection Operations
+
+Collection operations are expressions that are performed on a list or map to
+return a variation of the initial data.
+
+At the moment, `filter` is the only collection operation available.
+
+## Filter Expression
+
+`filter` is a [quantifier
+expression](/sentinel/language/spec#quantifier-expressions-any-all-filter-map) that
+returns a subset of the provided collection. Only elements whose filter body
+returns true will be returned. If any of the elements filter body returns
+undefined, the final result will be undefined.
+
+Filter uses the same syntax as the `any` and `all` [boolean
+expressions](/sentinel/language/boolexpr#any-all-expressions):
+
+```sentinel
+filter list as value { condition } // Single-iterator, list
+filter list as idx, value { condition } // Double-iterator, list
+
+filter map as key { condition } // Single-iterator, map
+filter map as key, value { condition } // Double-iterator, map
+```
+
+Examples:
+
+```sentinel
+l = [1, 1, 2, 3, 5, 8]
+evens = filter l as v { v % 2 is 0 } // [2, 8]
+
+m = { "a": "foo", "b": "bar" }
+matched_foo = filter m as _, v { v is "foo" } // { "a": "foo" }
+```
+
+## Map Expression
+
+`map` is a [quantifier
+expression](/sentinel/language/spec#quantifier-expressions-any-all-filter-map) that
+returns a list, regardless of the input collection type. Each element within the
+input collection is evaluted according to the map expression body and appended
+to the result.
+
+```sentinel
+l = [1, 2]
+r = map l as v { v % 2 } // [false, true]
+
+m = { "a": "foo", "b": "bar" }
+r = map m as k, v { v } // ["foo", "bar"]
+```
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/language/conditionals.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/language/conditionals.mdx
new file mode 100644
index 0000000000..1736795dbb
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/language/conditionals.mdx
@@ -0,0 +1,161 @@
+---
+page_title: Sentinel Language - Conditionals
+sidebar_current: docs-language-conditional
+description: |-
+ Conditional statements allow your policy to behave differently depending on a condition.
+layout: docs
+---
+
+# Language: Conditionals
+
+Conditional statements allow your policy to behave differently depending
+on a condition.
+
+Conditional statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## If Statements
+
+`if` statements only execute their bodies if a condition is met.
+The syntax of an `if` statement is:
+
+```sentinel
+if condition {
+ // ... this is executed if condition is true
+}
+```
+
+The `condition` must result in a boolean, such as by calling a function
+or evaluating a [boolean expression](/sentinel/language/boolexpr). If
+the `condition` is `true`, the body (within the `{}`) is executed. Otherwise,
+the body is skipped.
+
+Examples:
+
+```sentinel
+// This would execute the body
+value = 12
+if value is 18 {
+ print("condition met")
+}
+
+// Direct boolean values can be used
+value = true
+if value {
+ print("condition met")
+}
+
+// This would not execute the body since the boolean expression will
+// result in undefined.
+value = {}
+if value["key"] > 12 {
+ print("condition met")
+}
+```
+
+### Else, Else If
+
+An `else` clause can be given to an `if` statement to execute a body
+in the case the condition is _not met_. By putting another `if` statement
+directly after the `else`, multiple conditions can be tested for.
+The syntax is:
+
+```sentinel
+if condition {
+ // ...
+} else {
+ // ...
+}
+
+if condition {
+ // ...
+} else if other_condition {
+ // ...
+} else {
+ // ...
+}
+```
+
+### Scoping
+
+The body of an `if` statement does not create a new
+[scope](/sentinel/language/scope). Any variables assigned within the body
+of an if statement will modify the scope that the `if` statement itself
+is in.
+
+Example:
+
+```sentinel
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+```sentinel
+a = 18
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+## Case Statements
+
+`case` statements are a selection control mechanism that execute a clause based
+on matching expressions. It is worth noting that the expression for `case` is
+optional. When no expression is provided, it defaults the expression to `true`.
+Additionally, the order of clauses is important, as they are evaluated from top
+to bottom, executing the first match.
+The syntax of a case statement is:
+
+```sentinel
+case expression {
+ when clause_expression:
+ // executed when clause_expression and expression are equal
+ else:
+ // executed if no clause matches expression
+}
+```
+
+### When Clause
+
+Any clause that has an expression for comparison must use the `when` keyword.
+It accepts a list of expressions, seperated by a `,`.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+}
+```
+
+```sentinel
+case {
+ when x > 40:
+ return true
+}
+```
+
+### Else Clause
+
+The `else` keyword allows for capturing any expressions that have no matching
+`when` clause.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+ else:
+ return false
+}
+```
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/language/functions.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/language/functions.mdx
new file mode 100644
index 0000000000..8214078b6b
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/language/functions.mdx
@@ -0,0 +1,229 @@
+---
+page_title: Sentinel Language - Functions
+sidebar_current: docs-language-functions
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Functions
+
+Functions allow you to create reusable code to perform computations.
+
+Below is a trivial example function that doubles a number and shows
+the main rule using the function:
+
+```sentinel playground
+double = func(x) {
+ return x * 2
+}
+
+main = rule { double(12) is 24 }
+```
+
+Functions may contain any expressions,
+[conditionals](/sentinel/language/conditionals),
+and [loops](/sentinel/language/loops). They must return a value
+at the end of their execution. If no reasonable return value exists,
+the function should return [undefined](/sentinel/language/undefined).
+
+## Function Types
+
+### Named Functions
+
+-> **NOTE:** Named functions must be created within the [package scope](/sentinel/language/scope#package-scope).
+
+Named functions are declared using the `func` keyword as its own statement.
+They provide a safe method of creating functions and have additional
+restrictions that do not apply to anonymous functions.
+
+Firstly, named functions cannot be re-assigned, and also cannot use a name
+that is already used elsewhere. For instance, the below example will error due
+to the attempt to reassign the named function identifier to a new value:
+
+```sentinel
+func sum(a, b) {
+ return a + b
+}
+
+sum = 4
+```
+
+Additionally, the following will error due to the named function attempting
+to make use of an already assigned identifier:
+
+```sentinel
+sum = 4
+
+func sum(a, b) {
+ return a + b
+}
+```
+
+Named functions are helpful for policy authors to declare critical functions
+whose value or implementation should not be changed.
+
+### Anonymous Functions
+
+An anonymous function is created by assigning a variable to a `func`. The
+variable can be reassigned at any time including to different value types.
+Anonymous functions are helpful for use cases like closures, where a function
+can return another function.
+
+```sentinel
+func makeAdder(a) {
+ return func(b) {
+ return a + b
+ }
+}
+```
+
+## Creating a Function
+
+A function can have zero or more parameters. These parameters are specified
+within parentheses `()` separated by commas. If a function has no parameters,
+the parentheses must still be present.
+
+A function must terminate with a `return` statement to return a value back to
+the caller. Only a single value can be returned. The type of a return can
+vary between return statements.
+
+Anonymous function example:
+
+```sentinel
+add1 = func(x) { return x + 1 }
+```
+
+Named function example:
+
+```sentinel
+func add1(x) {
+ return x + 1
+}
+```
+
+Both examples create a function that adds 1 to the parameter `x`.
+
+## Calling a Function
+
+Functions are called by accessing their name followed by parentheses with
+a list of comma-separated values for the parameters. If the function takes no
+parameters, an empty set of parentheses must still be used to denote a
+function call.
+
+To call the function in the example above:
+
+```sentinel
+x = 1
+y = add1(x)
+```
+
+## Scoping
+
+The body of a `func` creates a new [scope](/sentinel/language/scope).
+
+The parent scope is the scope in which the function is created. This allows
+for the creation of functions within functions that can access their outer
+scopes.
+
+Example:
+
+```sentinel
+f = func() {
+ a = 42
+ print(a) // 42
+ return undefined
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+f = func() {
+ a = 42
+ return undefined
+}
+
+print(a) // 18
+```
+
+And below is an example of creating a function in a function which uses
+outer values:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double()
+}
+
+print(f()) // 84
+```
+
+A more complex example below shows how scoping works when passing
+functions around as arguments or results:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double
+}
+
+double = f()
+print(double()) // 84
+```
+
+## Pass By Value
+
+The parameters to a function are passed by value, but not deep copied.
+This means that elements of collections can still be modified but the
+root value cannot be changed.
+
+Example:
+
+```sentinel
+f = func(x) {
+ x = "value"
+ return x
+}
+
+x = "outside"
+f(x)
+print(x) // "outside"
+```
+
+```sentinel
+f = func(x) {
+ append(x, "value")
+ return x
+}
+
+x = []
+f(x)
+print(x) // ["value"]
+```
+
+## Recursion
+
+A function is allowed to call other functions, including itself.
+This can be used to implement recursion. For example, the fibonacci sequence
+is implemented below:
+
+```sentinel
+fib = func(x) {
+ if x <= 0 {
+ return undefined
+ }
+
+ if x == 1 {
+ return 1
+ } else {
+ return x + fib(x - 1)
+ }
+}
+```
+
+Note that this example also shows using
+[undefined](/sentinel/language/undefined)
+as a return value in cases where a function has undefined behavior.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/language/imports.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/language/imports.mdx
new file mode 100644
index 0000000000..0ed1541b85
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/language/imports.mdx
@@ -0,0 +1,116 @@
+---
+page_title: Sentinel Language - Imports
+sidebar_current: docs-language-imports
+description: >-
+ Imports enable a Sentinel policy to access reusable libraries and external
+ data and functions.
+layout: docs
+---
+
+# Language: Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external
+data and functions. The exact list of available imports is dependent on the
+host system. Host systems may also make the available imports configurable
+via plugins.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that
+the import `calendar` allows access to engineer calendars. This import
+only exists for the purpose of this example and at the time of writing is
+not a real available import.
+
+```sentinel
+import "calendar"
+
+// Get the calendar for Bob for today
+bob_calendar = calendar.for("bob").today
+
+// Allow this policy to pass if Bob is not on vacation.
+main = rule { not bob_calendar.has_event("vacation") }
+```
+
+This example shows an import being used to deny a behavior if Bob is on
+vacation. Hopefully real policies do not depend on a single person being
+in the office, but the example shows the power of imports.
+
+In addition to consuming imports, you can also
+[extend Sentinel by writing custom imports](/sentinel/extending)
+This allows you to add any arbitrary behavior to your Sentinel-protected
+systems.
+
+## Importing
+
+Whether accessing a built-in library or an external plugin, the syntax
+for an import is the same:
+
+```sentinel
+import "name"
+```
+
+This adds the import with the name `name`. It can be referenced using
+the identifier `name`. For example, if the import above exposed first
+and last name data, you could access it via `name.first` or `name.last`.
+
+You can shadow imports by assigning a variable. In the example below,
+the import `name` becomes unavailable within the function because it is
+shadowed by a variable of the same name.
+
+Shadowing an import is not the same as overwriting a variable. Imports
+are not part of the [scope](/sentinel/language/scope), meaning that
+the shadow only exists for the scope it is created within. For everything
+else, the import works even after it is shadowed. The example below shows that
+as well.
+
+```sentinel
+import "name"
+
+f = func() {
+ name = "jane"
+ return name
+}
+
+print(f()) // "jane"
+print(name.first) // "bob"
+```
+
+## Aliases
+
+An import can be aliased to another name using the syntax below:
+
+```sentinel
+import "name" as other
+```
+
+This would make the import "name" available as `other`.
+
+An import may only be imported once, regardless of aliases. The following
+would be **invalid** and would result in an error:
+
+```sentinel
+import "name" as one
+import "name" as two
+```
+
+## Accessing Import Data
+
+Imports are accessed with dot-separated identifiers, such as `name.first` or
+`name.stage.first`. These are called _selector expressions_. An import may
+return nested data that further can be accessed via selector expressions.
+
+In the first example on this page with the "calendar" import, we see this:
+
+```sentinel
+import "calendar"
+
+a = calendar.for("bob") // selector expression calendar.for
+b = a.today // selector expression on the result
+c = b.vacation // accessing further nested data
+```
+
+The exact structure of an import is dependent on the import. Please reference
+the documentation for an import to learn more.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/language/index.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/language/index.mdx
new file mode 100644
index 0000000000..7f588c94cd
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/language/index.mdx
@@ -0,0 +1,114 @@
+---
+page_title: Sentinel Language
+sidebar_current: docs-language-basics
+description: |-
+ Sentinel policies are written using the Sentinel language. This language
+ is easy to learn and easy to write. You can learn the Sentinel language
+ and be productive within an hour. Learning Sentinel doesn't require any
+ formal programming experience.
+layout: docs
+---
+
+# Sentinel Language
+
+Sentinel policies are written using the Sentinel language. This language
+is easy to learn and easy to write. You can learn the Sentinel language
+and be productive within an hour. Learning Sentinel doesn't require any
+formal programming experience.
+
+This language guide will contain many details that are not necessary to
+be productive with Sentinel or are targeted to those who are looking for
+exact information about the language (such as what types of line endings are
+allowed). You can safely ignore these sentences.
+
+You may also view the
+[official language specification](/sentinel/language/spec)
+This is a specific and detailed document on the syntax and behavior of
+the language primarily intended for implementation creators and to disambiguate
+the language.
+
+## Simplest Example
+
+The example below is about the simplest practical example of Sentinel.
+It is reasonable to imagine this as a realistic policy. This shows that
+in most cases, Sentinel will be extremely simple:
+
+```sentinel
+main = rule { request.method is "GET" and request.headers contains "X-Key" }
+```
+
+## Files
+
+Sentinel policies are single files that end in the `.sentinel` file extension.
+There is currently no built-in mechanism to Sentinel for merging multiple
+files. This is purposefully done to make Sentinel policies easy to submit
+to systems that support Sentinel policies.
+
+Sentinel policy files must be UTF-8 encoded and can end in both
+Unix (LF) or Windows (CRLF) line breaks. When running the
+[auto-formatter](#),
+line endings will always use Unix line breaks.
+
+## Ordering
+
+Sentinel policies are executed top-down. For example:
+
+```sentinel
+a = 1 // a = 1 here
+b = a + 1 // b = 2 here
+a = 3 // a = 3, b = 2
+```
+
+In this example, the value of `a` and `b` is shown at each line. Since Sentinel
+executes values top-down, the final value of `a` is 3 and `b` is 2. `b` _does
+not_ become 4.
+
+## Main
+
+Sentinel expects there to be a `main` [rule](/sentinel/language/rules).
+The value of this rule is the result of the entire policy.
+
+The result of the policy depends on the evaluated contents of the `main` rule.
+For booleans, a policy passes on a `true` value, and fails on a `false` value.
+Other types generally follow a zero or zero-length pattern for determining
+success.
+
+| Type | Passing Condition | Failing Condition |
+| ------- | ----------------- | -------------------------- |
+| Boolean | `true` | `false` |
+| String | `""` | Any non-zero length string |
+| Integer | `0` | Any non-zero value |
+| Float | `0.0` | Any non-zero value |
+| List | `[]` | Any non-zero length list |
+| Map | `{}` | Any non-zero length map |
+
+A value of main that falls outside of the above types will result in a policy
+error. As a special case, if `main` evaluates to an undefined value, the error
+message will indicate as such, with a reference to where the undefined value was
+encountered.
+
+## More Complex Example
+
+The simple example above is a full working example. In our experience with
+Sentinel, many policies can be representing using this simple form. However,
+to show more features of the language, a more complex example is shown below.
+This example is also a realistic example of what Sentinel may be used for.
+
+```sentinel
+import "units"
+
+memory = func(job) {
+ result = 0
+ for job.groups as g {
+ for g.tasks as t {
+ result += t.resources.memory else 0
+ }
+ }
+
+ return result
+}
+
+main = rule {
+ memory(job) < 1 * units.gigabyte
+}
+```
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/language/lists.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/language/lists.mdx
new file mode 100644
index 0000000000..bc5269d8eb
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/language/lists.mdx
@@ -0,0 +1,135 @@
+---
+page_title: Sentinel Language - Lists
+sidebar_current: docs-language-lists
+description: Lists are a collection of zero or more values.
+layout: docs
+---
+
+# Language: Lists
+
+Lists are a collection of zero or more values.
+
+Lists can be created using by wrapping values in `[]` and separating them
+by commas. An optional trailing comma is allowed. List elements can be
+differing types. Examples:
+
+```sentinel
+[] // An empty list
+["foo"] // Single element list
+["foo", 1, 2, true] // Multi element list with different types
+["foo", [1, 2]] // List containing another list
+```
+
+A list can be [sliced](/sentinel/language/slices) to create sublists.
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for value inclusion in a list.
+
+## Accessing Elements
+
+List elements can be accessed with the syntax `name[index]` where
+`index` is zero-indexed.
+
+A negative index accesses the list in reverse. It is the same as
+reversing a list and then using a positive index. Similar to a positive
+index, it is bounded by the length of the list as a negative value.
+
+Accessing beyond the length of the list results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+a = ["foo", 1, true, [1, 2]]
+
+a[0] // "foo"
+a[2] // true
+a[4] // undefined
+a[-2] // true
+a[-4] // "foo"
+a[-5] // undefined
+a[3][1] // 2
+```
+
+## List Append
+
+Values can be appended to a list using the built-in
+[append](/sentinel/functions/append) function.
+
+This modifies the list in-place and returns
+[undefined](/sentinel/language/undefined). For more information on
+why `append` behaves this way, please read the full documentation for the
+[append](/sentinel/functions/append) function.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append([1,2], "foo") // [1, 2, "foo"]
+append([1,2], [3]) // [1, 2, [3]]
+append(1, 3) // error()
+```
+
+## List Concatenation
+
+Two lists can be concatenated using the `+` operator or the shorthand
+`+=` assignment operator. For the `+` operator, a new list is returned.
+For `+=`, the left-hand list is modified in place.
+
+Examples:
+
+```sentinel
+[1] + [2] // [1, 2]
+[1] + [[1]] // [1, [1]]
+[1] + 1 // error
+
+a = [1]
+a += [2] // a = [1, 2]
+a += 3 // error
+```
+
+## List Length
+
+The length of a list can be retrieved using the
+[length](/sentinel/functions/length) function.
+
+Examples:
+
+```sentinel
+length([]) // 0
+length(["foo"]) // 1
+```
+
+## Removing Items From a List
+
+You can use a combination of [list concatenation](#list-concatenation) and
+[slicing](/sentinel/language/slices) to remove elements from a list.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+a = a[:2] + a[3:] // [1, 2, 4, 5]
+```
+
+The shorthand shown here is effectively the same as `a[0:2] + a[3:length(a)]`,
+which creates a new list out of the concatenation two sub-lists composed of the
+first two elements, and the rest of the list starting at index 3. This
+effectively removes the 3rd element from the list (index 2).
+
+## List Comparison
+
+Lists may be [compared](/sentinel/language/boolexpr#comparison-operators)
+for equality. Lists are equal if they are of equal length and their
+corresponding elements are comparable and equal. Lists with the same elements
+but in a different order are not equal.
+
+```sentinel
+[1, 2] is [1, 2] // true
+[1, 2] is [2, 1] // false
+["a"] is ["a", "b"] // false
+["a", ["b", "c"]] is ["a", ["b", "c"]] // true
+```
+
+List comparison speed is O(N), meaning that the speed of the comparison is
+_linearly_ proportional to the number of elements in the list. The more
+elements, the more iterations that are necessary to verify equality.
+
+-> The N-value quoted above should account for the sum of the elements of _all_
+lists in the subjects of comparison, as list comparison will recurse into these
+lists to check for equality.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/language/logging-errors.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/language/logging-errors.mdx
new file mode 100644
index 0000000000..705a72cbd4
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/language/logging-errors.mdx
@@ -0,0 +1,52 @@
+---
+page_title: Sentinel Language - Logging and Errors
+sidebar_current: docs-language-log
+description: The built-in functions `print` and `error` can be used for logging and errors.
+layout: docs
+---
+
+# Language: Logging and Errors
+
+The built-in functions `print` and `error` can be used for logging
+and errors. Logging is helpful to understand how a policy is executing
+in development. And errors are useful to halting execution immediately in
+certain scenarios.
+
+## Print
+
+The `print` function is used to output debug information. The exact location
+where this print statements go is dependent on the host application and
+the Sentinel runtime itself.
+
+The `print` function takes zero or more Sentinel values as arguments.
+Each value is formatted for human-friendly output and space-separated.
+Example:
+
+```sentinel
+value = 42
+print("the value is", value) // the value is 42
+
+map = { "foo": false }
+print(map) // { "foo": false }
+
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // false
+```
+
+## Errors
+
+Certain actions in Sentinel, such as accessing an undefined variable,
+attempting to add two incompatible types, etc. result in _runtime errors_.
+These errors halt the execution of a policy immediately. The pass/fail result
+of a policy is considered fail.
+
+Runtime errors can be manually triggered using the `error` function.
+The `error` function behaves exactly like the `print` function except that
+execution is halted immediately.
+
+This should only be used in scenarios where the policy _must fail_ due to
+some unexpected or invalid condition. The line between `error` and
+[undefined](/sentinel/language/undefined) can sometimes be unclear. Undefined
+is recommended when a policy can conceivably recover or safely ignore the
+undefined behavior. An error should be used when the policy should fail and
+notify someone regardless.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/language/loops.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/language/loops.mdx
new file mode 100644
index 0000000000..622732bd35
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/language/loops.mdx
@@ -0,0 +1,92 @@
+---
+page_title: Sentinel Language - Loops
+sidebar_current: docs-language-loops
+description: >-
+ Loop statements allow you to execute a body of code for each element in a
+ collection or for some fixed number of times.
+layout: docs
+---
+
+# Language: Loops
+
+Loop statements allow you to execute a body of code for each element in
+a collection or for some fixed number of times.
+
+Loop statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## For Statements
+
+`for` statements allow repeated execution of a block for each
+element in a collection.
+
+Example:
+
+```sentinel
+// A basic sum
+count = 0
+for [1, 2, 3] as num {
+ count += num
+}
+```
+
+The syntax is `for COLLECTION as value`. This will iterate over the
+collection, assigning each element to `value`. In the example above,
+each element is assigned to `num`. The body is executed for each element.
+In the example above, the body adds `num` to the `count` variable. This
+creates a basic sum of all values in the collection.
+
+For a map, the assigned element is the key in the map. In the example
+below, `name` would be assigned map keys.
+
+```sentinel
+list = []
+for { "a": 1, "b": 2 } as name {
+ append(list, name)
+}
+
+print(list) // ["a" "b"]
+```
+
+An alternate syntax is `for COLLECTION as key, value`. This will assign
+both the key and value to a variable. For a list, the key is the element
+index. For a map, it is the key and value is assigned the element value.
+Example:
+
+```sentinel
+count = 0
+for { "a": 1, "b": 2 } as name, num {
+ count += num
+}
+
+print(count) // 3
+```
+
+## Scoping
+
+The body of a `for` statement creates a new
+[scope](/sentinel/language/scope). If a variable is assigned within
+the body of a for statement that isn't assigned in a parent scope,
+that variable will only exist for the duration of the body execution.
+
+Example:
+
+```sentinel
+for list as value {
+ a = 42
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+for list as value {
+ a = 42
+}
+
+print(a) // 18
+```
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/language/maps.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/language/maps.mdx
new file mode 100644
index 0000000000..298bf9a567
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/language/maps.mdx
@@ -0,0 +1,121 @@
+---
+page_title: Sentinel Language - Maps
+sidebar_current: docs-language-maps
+description: >-
+ Maps are a collection of zero or more key/value pairs. These are useful for
+ storing values that are looked up by a unique key.
+layout: docs
+---
+
+# Language: Maps
+
+Maps are a collection of zero or more key/value pairs. These are useful
+for storing values that are looked up by a unique key.
+
+Maps can be created using `{}` and specifying key/value pairs. Keys and
+values can be differing types. An optional trailing comma is allowed.
+Examples:
+
+```sentinel
+// Empty map
+{}
+
+// Map with a single value on one line
+{ "key": "value" }
+
+// Map with multiple values with differing types on multiple lines
+{
+ "key": "value",
+ 42: true,
+}
+```
+
+Maps are **unordered**. When
+[looping](/sentinel/language/loops)
+over a map, the key/value pairs can be returned in any order.
+
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for key inclusion in a map.
+
+## Accessing Elements
+
+Map elements can be accessed with the syntax `name[key]`. This looks up
+a value by a key.
+
+Accessing a key that doesn't exist results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+map = { "key": "value", 42: true, }
+
+map["key"] // "value"
+map[42] // true
+map[0] // undefined
+```
+
+## Modifying or Adding Elements
+
+Elements can be added or modified in a map by assigning to `name[key]`.
+If the key doesn't exist, the value is added. If the key already exists,
+the value is overridden.
+
+```sentinel
+map = { "key": "value" }
+
+map[42] = true // Add a new key/value
+map["key"] = 12 // Modify the value of "key"
+```
+
+## Deleting Elements
+
+An element can be deleted from a map using the
+[delete](/sentinel/functions/delete) function.
+
+Examples:
+
+```sentinel
+map = { "key": "value" }
+delete(map, "key") // map is now empty
+delete(map, "other") // no effect for non-existent key
+```
+
+## Keys and Values
+
+The keys and values of a map can be retrieved as
+[lists](/sentinel/language/lists) using the
+[keys](/sentinel/functions/keys) and
+[values](/sentinel/functions/values) functions.
+
+Because maps are unordered, the keys and values are returned in an
+unspecified order. It should not be assumed that keys and values will be
+returned in a consistent order.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+## Map Comparison
+
+Maps may be [compared](/sentinel/language/boolexpr#comparison-operators) for
+equality. Maps are equal if they are of equal length and both their
+corresponding keys and values are comparable and equal.
+
+```sentinel
+{"foo": "bar"} is {"foo": "bar"} // true
+{"foo": "bar"} is {"baz": "bar"} // false
+{"foo": "bar"} is {"foo": "baz"} // false
+{"foo": "bar"} is {"foo": "bar", "baz": "qux"} // false
+{1: "a"} is {1.0: "a"} // true (int/float comparable)
+
+// also true (maps are not ordered):
+{"m": {"a": "b"}, "l": ["a"]} is {"l": ["a"], "m": {"a": " b"}}
+```
+
+-> The current worst-case comparison speed for maps is O(N²), or _quadratic
+time_. This is the square of the cumulative elements or the parent and all child
+maps contained within the map. Consider this when making comparisons on larger,
+more complex maps. This may be optimized in the future.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/language/parameters.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/language/parameters.mdx
new file mode 100644
index 0000000000..348acf2593
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/language/parameters.mdx
@@ -0,0 +1,133 @@
+---
+page_title: Sentinel Language - Parameters
+sidebar_current: docs-language-parameters
+description: >-
+ Parameteres allow a Sentinel policy to describe variables that are expected
+ to be supplied by the calling application, in addition to any default value.
+layout: docs
+---
+
+# Language: Parameters
+
+Sentinel allows a policy author to supply parameters to help facilitate policy
+reuse and ensure sensitive values do not need to be hard-coded in a policy.
+
+Parameters are supplied by using the `param` keyword, followed by an identifier.
+A default value can also be supplied by using the `default` keyword.
+
+```sentinel
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+```
+
+Once declared, parameters can be used like any other variable, including being
+re-assigned.
+
+```sentinel
+param foo default 1 // 1 (default)
+
+foo = foo + 1 // 2
+```
+
+## Variable Descriptions
+
+You can supply a description to a parameter by adding a comment at the top of
+it. This value can be communicated to a specific implementation of Sentinel to
+provide information about what the parameter is for during configuration.
+
+```sentinel
+// An example parameter. Must be supplied or the policy will fail.
+param foo
+```
+
+## Supplying Parameter Values Using the Sentinel CLI
+
+In a production implementation, supplying parameters to a policy is an
+implementation-specific detail - see the documentation for your particular
+implementation for details.
+
+Using the [Sentinel CLI](/sentinel/commands), you can supply parameters one of
+four ways.
+
+### Supplying Parameter Values Using the Configuration File
+
+You can supply parameters using the
+[`param`](/sentinel/configuration#parameters) section of the
+[configuration file](/sentinel/configuration).
+
+```hcl
+param "foo" {
+ value = "bar"
+}
+```
+
+This method works for both `sentinel apply` and `sentinel test`.
+
+### Supplying Parameter Values Using the Environment
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can supply a value using environment variables - prefix the parameter with
+`SENTINEL_PARAM_`, using the name of the parameter to supply.
+
+```plaintext
+SENTINEL_PARAM_foo=bar sentinel apply policy.sentinel
+```
+
+### Supplying Parameter Values Using the CLI
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can also use the `-param` CLI argument to supply parameter in a `key=value`
+pair.
+
+```plaintext
+sentinel apply -param foo=bar policy.sentinel
+```
+
+### Interactive CLI Prompting
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+If a required value has not been supplied when a policy is run with `sentinel apply`, it will be prompted for, along with its description:
+
+
+
+ $ sentinel apply policy.sentinel
+
+ policy.sentinel:2:7: requires value for parameter foo
+
+ An example parameter. Must be supplied or the policy will fail.
+
+
+ Values can be strings, floats, or JSON array or object values. To
+ force
+
+ strings, use quotes.
+
+
+ Enter a value: bar
+
+
+
+ Pass
+
+
+
+
+### CLI Value Format
+
+-> **NOTE:** This section contains details for the parameter features supported by `sentinel apply`.
+
+The CLI takes either strings, or JSON numbers, arrays, or maps. If you need a
+literal string value, quote the value.
+
+```sentinel
+foo // string
+42 // number (float)
+"42" // string ("42", without quotes)
+[1, 2] // array (list)
+{"a": "b"} // object (map)
+```
+
+-> **NOTE:** Boolean values are not supported by this method.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/language/rules.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/language/rules.mdx
new file mode 100644
index 0000000000..e31924d414
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/language/rules.mdx
@@ -0,0 +1,204 @@
+---
+page_title: Sentinel Language - Rules
+sidebar_current: docs-language-rules
+description: >-
+ Rules form the basis of a policy by representing behavior that is either
+ passing or failing (true or false).
+layout: docs
+---
+
+# Language: Rules
+
+Rules form the basis of a policy by representing behavior that is either passing
+or failing. A policy can be broken down into a set of rules. Breaking down a
+policy into a set of rules can make it more understandable and aids with
+testing.
+
+An example is shown below:
+
+```sentinel playground
+weather = "sunny"
+day = "wednesday"
+is_sunny = rule { weather is "sunny" }
+is_wednesday = rule { day is "wednesday" }
+
+main = rule {
+ is_sunny and
+ is_wednesday
+}
+```
+
+A rule contains a single expression. This can be a simple [boolean
+expression](/sentinel/language/boolexpr), or an expression representing the
+discovery of a set of violations using more in-depth expressions like [`filter`
+and `map`](/sentinel/language/collection-operations). You can split expressions
+into multiple lines for readability, as you would with any other expression
+within Sentinel.
+
+A rule is also _lazy_ and _memoized_. _Lazy_ means that the rule is only
+evaluated when it is actually required, not at the point that it is
+created. This is covered in more detail in the [lazy](#lazy-and-memoized) section.
+_Memoized_ means that the value is only computed once and then saved. Once
+a rule is evaluated, the result of it is reused anytime the rule is referenced
+again.
+
+## Making rules easier to understand
+
+Rules are an abstraction that make policies much more understandable
+by making it easier for humans to see what the policy is trying to do.
+
+For example, consider the policy before which doesn't abstract into rules:
+
+```sentinel
+main = rule {
+ ((day is "saturday" or day is "sunday") and homework is "") or
+ (day in ["monday", "tuesday", "wednesday", "thursday", "friday"] and
+ not school_today and homework is "")
+}
+```
+
+Assume that `day`, `homework`, and `school_day` are available.
+
+In plain English, this policy is trying to say: you can play with your friends
+only on the weekend as long as there is no homework, or during the week if
+there is no school and no homework.
+
+Despite the relatively simple nature of this policy (a few lines, about 5
+logical expressions), it is difficult to read and understand, especially if
+you've not seen it before.
+
+The same policy using rules as an abstraction:
+
+```sentinel
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+is_valid_weekend = rule { is_weekend and homework is "" }
+is_valid_weekday = rule { not is_weekend and not school_today and homework is "" }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+By reading the names of the rules, its much clearer to see what each individual
+part of the policy is trying to achieve. The readability is improved even
+further when adding comments to the rules to explain them further, which is
+a recommended practice:
+
+```sentinel
+// A weekend is Sat or Sun
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+// A valid weekend is a weekend without homework
+is_valid_weekend = rule { is_weekend and homework is "" }
+
+// A valid weekday is a weekday without school
+is_valid_weekday = rule { not is_weekend and not school_today and homework is "" }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+## "When" Predicates
+
+A rule may have an optional `when` predicate attached to it. When this is
+present, the rule is only evaluated when the predicate results in `true`.
+Otherwise, the rule is not evaluated and the result of the rule is always
+`true`.
+
+"When" predicates should be used to define the context in which the rule
+is semantically meaningful. It is common when translating human language
+policy into Sentinel to have scenarios such as "when the key has a prefix
+of `/account/`, the remainder must be numeric." In that example, when the
+key _does not_ have the specified prefix, the policy doesn't apply.
+
+Assume you have rules `is_prefix` and `is_numeric` to check both the
+conditions in the example above. An example is shown with and without
+the "when" predicate:
+
+```sentinel
+example_no_when = rule { (is_prefix and is_numeric) or not is_prefix }
+
+example_when = rule when is_prefix { is_numeric }
+```
+
+The rules are equivalent in behavior for all values of `is_prefix` and
+`is_numeric`, but the second rule is more succint and easier to understand.
+
+## Testing
+
+To verify a policy works correct, the
+[built-in Sentinel test framework](/sentinel/writing/testing)
+uses rules as the point where you can assert behavior. You say that
+you expect certain rules to be true or false. By doing so, you ensure that
+the policy results in the value you expect using the rule flow that you
+expect.
+
+Therefore, in addition to readability, we recommend splitting policies into
+rules to aid testability.
+
+## Non-Boolean Values
+
+Sentinel supports non-boolean values in rules. This can be useful for passing
+more detail along to a calling integration when more detail is required than a
+boolean value would be able to communicate. This data shows up in the [policy
+trace](/sentinel/writing/tracing) and can be utilized in different ways
+depending on the integration you are using Sentinel with.
+
+Consider a different take on our policy above:
+
+```sentinel
+// A policy to check a set of scheduled days to determine what days you can't
+// come out and play, based on the day not being a weekend day and there being
+// homework to do.
+
+param days
+
+main = rule {
+ filter days as d {
+ d.day not in ["saturday", "sunday"] and
+ d.homework is not ""
+ }
+}
+```
+
+When given a value in the set of `days` that has a `day` that is a weekday, and
+`homework` present, the policy will fail. Additional, when tracing was enabled,
+the days that were detected as violating the policy would be given in the trace
+for the `main` rule.
+
+Generally, for non-boolean values, a policy fails on non-zero data. See the
+details on [the `main` rule](/sentinel/language#main) for more details.
+
+The result of a rule must be either a boolean, string, integer, float, list, or
+map. All other types will result in a runtime error.
+
+## Lazy and Memoized
+
+A rule is _lazy_ and _memoized_.
+
+_Lazy_ means that the rule is only evaluated when it is actually required,
+not at the point that it is created. And _memoized_ means that the value is
+only computed once and then saved. Once a rule is evaluated, the result of it
+is reused anytime the rule is referenced again.
+
+Both of these properties have important implications for Sentinel
+policies:
+
+**Performance:** Rules are a way to improve the performance of a Sentinel
+policy. If you have a boolean expression that is reused a lot, a rule will
+only be evaluated once. In the example shown above, `is_weekend` is used
+multiple times, but will only have to be evaluated once.
+
+**Behavior:** Rules accessing variables see the value of those variables
+at _the time they are evaluated_. This can lead to surprising behavior
+in some cases. For example:
+
+```sentinel playground
+a = 1
+b = rule { a == 1 }
+a = 2
+main = b
+```
+
+In this example, `main` will actually result to `false`. It is evaluated
+when it is needed, which is when the policy executes `main`. At this point,
+`a` is now 2 and `b` has not been evaluated yet. Therefore, `b` becomes `false`.
+All future references to `b` will return `false` since a rule is memoized.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/language/scope.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/language/scope.mdx
new file mode 100644
index 0000000000..f498e26dc2
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/language/scope.mdx
@@ -0,0 +1,47 @@
+---
+page_title: Sentinel Language - Scope
+sidebar_current: docs-language-scope
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Scope
+
+Scope is the context in which variables are created and accessed. If a
+variable exists in a parent scope, it is accessed and can be modified.
+Otherwise, the variable is created in your current scope.
+
+Scopes are created with _blocks_. A block is a possibly empty sequence
+of statements within matching brace brackets `{}`. You may nest blocks
+to create nested scopes.
+
+## Package Scope
+
+The package scope is the top level scope within a policy file and encapsulates
+the entire file contents. Imports, parameters and named functions must be
+declared within the package scope.
+
+## Implicit Scopes
+
+Each `any`, `all`, and `for` statement is considered to be in its own block.
+Note that `if` statements _do not_ create their own block.
+
+## Examples
+
+The example policy below shows various effects of scopes:
+
+```sentinel
+a = 1
+print(a) // 1
+
+f = func() {
+ print(a) // 1
+ a = 12
+ b = 1
+ return undefined
+}
+f()
+
+print(a) // 12
+print(b) // undefined
+```
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/language/slices.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/language/slices.mdx
new file mode 100644
index 0000000000..82dc7f4c18
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/language/slices.mdx
@@ -0,0 +1,45 @@
+---
+page_title: Sentinel Language - Slices
+sidebar_current: docs-language-slices
+description: >-
+ Slices are an efficient way to create a substring or sublist from an existing
+ string or list, respectively.
+layout: docs
+---
+
+# Language: Slices
+
+Slices are an efficient way to create a substring or sublist from an
+existing string or list, respectively.
+
+The syntax for creating a slice is:
+
+```sentinel
+a[low : high]
+```
+
+`low` is the lowest index to slice from. The resulting slice includes
+the value at `low`. `high` is the index to slice to. The resulting slice
+will not include the value at `high`. The length of the resulting list
+or string is always `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+
+a = "hello"
+b = a[1:4] // "ell"
+```
+
+## Convenience Shorthands
+
+Some convenience shorthands are available by omitting the value for either the
+low or high index in the slice expression: omitting `low` implies a low index of
+0, and omitting `high` implies a high index of the length of the list.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+
+b = a[:2] // [1, 2] (same as a[0:2])
+b = a[2:] // [3, 4, 5] (same as a[2:length(a)])
+```
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/language/spec.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/language/spec.mdx
new file mode 100644
index 0000000000..fb436296a1
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/language/spec.mdx
@@ -0,0 +1,1329 @@
+---
+page_title: Sentinel Language Specification
+sidebar_current: docs-language-spec
+description: This is the specification for the Sentinel policy language.
+layout: docs
+---
+
+# Sentinel Language Specification
+
+This is the specification for the Sentinel policy language.
+
+The Sentinel language is designed with policy enforcement in mind. It is dynamically typed and garbage collected and has explicit support for _rule_ construction representing boolean logic.
+
+The language is designed to be easy to learn and use by non-programmers. It is expected to be embedded within applications.
+
+## Table of Contents
+
+
+
+
+- [Source code representation](#source-code-representation)
+- [Declarations and Scope](#declarations-and-scope)
+- [Blocks](#blocks)
+- [Lexical Elements](#lexical-elements)
+ - [Comments](#comments)
+ - [Identifiers](#identifiers)
+ - [Keywords](#keywords)
+ - [Operators and Delimiters](#operators-and-delimiters)
+ - [Integer Literals](#integer-literals)
+ - [Floating-point Literals](#floating-point-literals)
+ - [String Literals](#string-literals)
+ - [Implicit Line Joining](#implicit-line-joining)
+ - [Whitespace](#whitespace)
+ - [Semicolons](#semicolons)
+- [Variables](#variables)
+- [Undefined](#undefined)
+- [Expressions](#expressions)
+ - [Operand](#operand)
+ - [Primary Expressions](#primary-expressions)
+ - [Null](#null)
+ - [Booleans](#booleans)
+ - [Boolean Literals](#boolean-literals)
+ - [Boolean Expressions](#boolean-expressions)
+ - [List Literals](#list-literals)
+ - [Map Literals](#map-literals)
+ - [Function Literals](#function-literals)
+ - [Rule Expressions](#rule-expressions)
+ - [Index Expressions](#index-expressions)
+ - [Selectors](#selectors)
+ - [Slice Expressions](#slice-expressions)
+ - [Calls](#calls)
+ - [Operators](#operators)
+ - [Operator Precedence](#operator-precedence)
+ - [Arithmetic Operators](#arithmetic-operators)
+ - [Integer operators](#integer-operators)
+ - [Integer overflow](#integer-overflow)
+ - [Floating-point operators](#floating-point-operators)
+ - [String Concatenation](#string-concatenation)
+ - [List Concatenation](#list-concatenation)
+ - [Comparison Operators](#comparison-operators)
+ - [Logical Operators](#logical-operators)
+ - [Set Operators](#set-operators)
+ - [Matches Operator](#matches-operator)
+ - [Else Operator](#else-operator)
+ - [Quantifier Expressions (any, all, filter, map)](#quantifier-expressions-any-all-filter-map)
+- [Statements](#statements)
+ - [Expression Statements](#expression-statements)
+ - [Assignments](#assignments)
+ - [If Statements](#if-statements)
+ - [Case Statements](#case-statements)
+ - [For Statements](#for-statements)
+ - [Break Statements](#break-statements)
+ - [Continue Statements](#continue-statements)
+ - [Return Statements](#return-statements)
+- [Imports](#imports)
+- [Parameters](#parameters)
+- [Built-in Functions](#built-in-functions)
+ - [Length](#length)
+ - [Collections](#collections)
+ - [List Append](#list-append)
+ - [Map Delete](#map-delete)
+ - [Keys and Values](#keys-and-values)
+ - [Range](#range)
+ - [Type Conversion](#type-conversion)
+ - [Printing](#printing)
+ - [Errors](#errors)
+- [Program Execution](#program-execution)
+
+
+
+## Source code representation
+
+Source code is Unicode text encoded in UTF-8. A "character" referenced in this document refers to a Unicode code point. Each code point is distinct: upper and lower case letters are different characters.
+
+The underscore character \_ (U+005F) is considered a "letter".
+
+```ebnf
+letter = unicode_letter | "_" .
+decimal_digit = "0" … "9" .
+octal_digit = "0" … "7" .
+hex_digit = "0" … "9" | "A" … "F" | "a" … "f" .
+```
+
+## Declarations and Scope
+
+A declaration binds an identifier to a value. An identifier is declared at the point it is first assigned. An assignment is considered a first assignment if the identifier hasn't already been previously declared in the current scope or any parent scopes.
+
+The scope of an identifier is the extent of source text in which the identifier denotes the specified value. Sentinel is lexically scoped using blocks.
+
+An identifier declared in a block may be redeclared in an inner block. While the identifier of the inner declaration is in scope, it denotes the value assigned in the inner declaration.
+
+## Blocks
+
+A block is a possibly empty sequence of statements within matching brace brackets.
+
+```ebnf
+Block = "{" StatementList "}" .
+StatementList = { Statement ";" } .
+```
+
+Blocks nest and affect scoping.
+
+In addition to explicit blocks in the source code, there are implicit blocks:
+
+1. The universe block encompasses all source text.
+2. Each program has a program block containing all source text for that program.
+3. Each "any", "all", and "for" statements is considered to be in its own implicit block. "if" does not create an implicit block.
+
+## Lexical Elements
+
+### Comments
+
+Comments are sections of source text used for documentation.
+
+```plaintext
+# Single line comment
+// Single line comment
+/* multi
+line
+ comment */
+```
+
+Three forms of comments are supported: two single-line forms and one multi-line form. A single-line comment begins with the token `//` or `#`. Everything between the starting token and the end of the line is ignored.
+
+A multi-line comment begins with the token `/*` and ends with the token `*/`. Everything between `/*` and `*/` is ignored.
+
+A comment may not start inside a string literal or inside another comment.
+
+A multi-line comment containing no newlines acts like a space.
+
+### Identifiers
+
+Identifiers name program entities such as rules, variables, and functions. An identifier is a sequence of one or more letters and digits. The first character in an identifier must be a letter.
+
+```ebnf
+identifier = letter { letter | unicode_digit } .
+```
+
+```sentinel
+a
+_a
+_A
+αβ
+```
+
+#### Pre-Declared Identifiers
+
+The following identifiers are implicitly declared in the [universe block](#blocks):
+
+```plaintext
+Constants:
+ false null true undefined
+
+Functions:
+ append bool delete error float int keys length print range string values
+```
+
+### Keywords
+
+The following keywords are reserved and may not be used as identifiers:
+
+```plaintext
+all
+any
+as
+break
+case
+continue
+default
+else
+empty
+filter
+for
+func
+if
+import
+map
+param
+return
+rule
+when
+```
+
+### Operators and Delimiters
+
+The following character sequences represent operators, delimiters, and other special tokens:
+
+```plaintext
++
+-
+*
+/
+%
++=
+-=
+*=
+/=
+%=
+==
+<
+>
+=
+!
+!=
+<=
+>=
+( )
+[ ]
+{ }
+,
+.
+:
+;
+and
+contains
+else
+in
+is
+matches
+not
+or
+xor
+```
+
+As a special case, `else` is both a keyword and operator, depending on the context. See [Else Operator](#else-operator) for more details.
+
+### Integer Literals
+
+An integer literal is a sequence of digits representing an integer constant. An optional prefix sets a non-decimal base: `0` for octal, `0x` or `0X` for hexadecimal. In hexadecimal literals, letters `a-f` and `A-F` represents values 10 through 15.
+
+Integers are signed 64-bit values (-9223372036854775808 to 9223372036854775807).
+
+```ebnf
+int_lit = decimal_lit | octal_lit | hex_lit .
+decimal_lit = ( "1" … "9" ) { decimal_digit } .
+octal_lit = "0" { octal_digit } .
+hex_lit = "0" ( "x" | "X" ) hex_digit { hex_digit } .
+```
+
+```
+42
+0600
+0xBadFace
+170141183460469231731687303715884105727
+```
+
+### Floating-point Literals
+
+A floating-point literal is a decimal representation of a floating-point constant. It has an integer part, a decimal point, a fractional part, and an exponent part. The integer and fractional part comprise decimal digits; the exponent part is an e or E followed by an optionally signed decimal exponent. One of the integer part or the fractional part may be elided; one of the decimal point or the exponent may be elided.
+
+Floating-point numbers are IEEE-754 64-bit floating numbers.
+
+```ebnf
+float_lit = decimals "." [ decimals ] [ exponent ] |
+ decimals exponent |
+ "." decimals [ exponent ] .
+decimals = decimal_digit { decimal_digit } .
+exponent = ( "e" | "E" ) [ "+" | "-" ] decimals .
+```
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+### String Literals
+
+String literals are character sequences between double quotes, as in "bar". Within the quotes, any character may appear except newline and unescaped double quote. The text between the quotes forms the value of the literal.
+
+A multi-character sequence beginning with a backslash encode values in various formats.
+
+Backslash escapes allow arbitrary values to be encoded as ASCII text. There are four ways to represent the integer value as a numeric constant: `\x` followed by exactly two hexadecimal digits; `\u` followed by exactly four hexadecimal digits; `\U` followed by exactly eight hexadecimal digits, and a plain backslash `\` followed by exactly three octal digits. In each case the value of the literal is the value represented by the digits in the corresponding base.
+
+After a backslash, certain single-character escapes represent special values:
+
+```plaintext
+\a U+0007 alert or bell
+\b U+0008 backspace
+\f U+000C form feed
+\n U+000A line feed or newline
+\r U+000D carriage return
+\t U+0009 horizontal tab
+\v U+000b vertical tab
+\\ U+005c backslash
+\" U+0022 double quote
+```
+
+The three-digit octal (\nnn) and two-digit hexadecimal (\xnn) escapes represent individual bytes of the resulting string; all other escapes represent the (possibly multi-byte) UTF-8 encoding of individual characters. Thus inside a string literal \377 and \xFF represent a single byte of value 0xFF=255, while ÿ, \u00FF, \U000000FF and \xc3\xbf represent the two bytes 0xc3 0xbf of the UTF-8 encoding of character U+00FF.
+
+A string value is a (possibly empty) sequence of bytes. Strings are immutable: once created, it is impossible to change the contents of a string.
+
+The length of a string s (its size in bytes) can be discovered using the built-in function `length`. An individual character (of type string) can be accessed by integer indices 0 through `length(s)-1`.
+
+```ebnf
+string_lit = `"` { unicode_value | byte_value } `"` .
+unicode_value = unicode_char | little_u_value | big_u_value | escaped_char .
+byte_value = octal_byte_value | hex_byte_value .
+octal_byte_value = `\` octal_digit octal_digit octal_digit .
+hex_byte_value = `\` "x" hex_digit hex_digit .
+little_u_value = `\` "u" hex_digit hex_digit hex_digit hex_digit .
+big_u_value = `\` "U" hex_digit hex_digit hex_digit hex_digit
+ hex_digit hex_digit hex_digit hex_digit .
+escaped_char = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | `"` ) .
+```
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+### Implicit Line Joining
+
+Expressions can be split over more than one line. For example:
+
+```sentinel
+a or
+b or # This is a comment
+c
+```
+
+Implicitly continued lines can have trailing comments. Blank continued lines are allowed.
+
+### Whitespace
+
+Whitespace is needed to separate tokens, but no distinction is made between the number and combination of whitespace characters. Whitespace characters can be space (U+0020), horizontal tabs (U+0009), carriage returns (U+000D), and newlines (U+000A).
+
+```sentinel
+a or b
+
+a or b
+
+a or
+ b
+
+rule { a or b }
+
+rule {
+ a or b }
+
+rule {
+ a or
+ b
+}
+```
+
+### Semicolons
+
+The formal grammar uses semicolons ";" as terminators in a number of productions.
+It is idiomatic Sentinel source to omit semicolons in most cases.
+
+A semicolon is automatically inserted into the token stream immediately after
+a line's final token if that token is:
+
+- An identifier
+- An integer, float, or string literal
+- The keyword `break`, `continue`, or `return`
+- The delimiter `)`, `]`, or `}`
+
+## Variables
+
+A variable is a storage location for holding a value.
+
+A variable has a dynamic type, which is the concrete type of the value assigned to the variable at run time. The dynamic type may vary during execution and change as a result of further assignments.
+
+A variable's value is retrieved by referring to the variable in an expression; it is the most recent value assigned to the variable. If a variable has not yet been declared (initially assigned), it is an error.
+
+```sentinel
+x = 7 // x is type int
+x = "foo" // x is now type string
+
+x = y // error if y is not previously assigned
+```
+
+## Undefined
+
+The value denoted by the keyword `undefined` represents undefined behavior or values. It can be created directly using the keyword `undefined`. It is also returned as a result of expressions in specified cases.
+
+`undefined` is a valid operand for any operations. Only `undefined or true` will result in true. All other operations result in `undefined`. An exception is if `undefined` is not reached as a result of short-circuit operations.
+
+```sentinel
+undefined OR true = true
+undefined OR false = undefined
+undefined OR undefined = undefined
+undefined AND true = undefined
+undefined AND false = undefined
+undefined AND undefined = undefined
+undefined XOR true = undefined
+undefined XOR false = undefined
+undefined XOR undefined = undefined
+
+// Short-circuit examples
+false OR true OR undefined = true
+false OR undefined OR true = true
+true AND false AND undefined = false
+true AND undefined AND false = undefined
+
+// Non-logical operators
+undefined + 5 = undefined
+-undefined = undefined
+!undefined = undefined
+```
+
+If the result of the main rule is `undefined`, it is treated as `false` but is indicative of erroneous logic.
+
+## Expressions
+
+### Operand
+
+Operands denote the elementary values in an expression. An operand may be a literal, an identifier denoting a variable, rule, or function, or a parenthesized expression.
+
+```ebnf
+Operand = Literal | OperandName | MethodExpr | "(" Expression ")" .
+Literal = BasicLit | CompositeLit | FunctionLit | RuleLit .
+BasicLit = int_lit | float_lit | string_lit .
+OperandName = identifier | QualifiedIdent.
+```
+
+### Primary Expressions
+
+Primary expressions are the operands for unary and binary expressions.
+
+```ebnf
+PrimaryExpr =
+ Operand |
+ PrimaryExpr Selector |
+ PrimaryExpr Index |
+ PrimaryExpr Slice |
+ PrimaryExpr Arguments |
+ PrimaryExpr ( "is empty" | "is not empty" ).
+
+Selector = "." identifier .
+Index = "[" Expression "]" .
+Slice = "[" [ Expression ] ":" [ Expression ] "]" |
+ "[" [ Expression ] ":" Expression ":" Expression "]" .
+Arguments = "(" [ ( ExpressionList | Type [ "," ExpressionList ] ) [ "..." ] [ "," ] ] ")" .
+```
+
+```sentinel
+x
+2
+(s + ".txt")
+f(3.1415, true)
+m["foo"]
+s[i : j + 1]
+obj.color
+f.p[i].x()
+x is empty
+x is not empty
+```
+
+### Null
+
+The reserved word `null` denote the singleton value `null`. Null represents the explicit absense of a value. Behavior of `null` within expressions is specified explicitly for each expression.
+
+### Booleans
+
+### Boolean Literals
+
+The reserved words `true` and `false` denote objects that represent the boolean values `true` and `false`, respectively. These are boolean literals.
+
+```ebnf
+BoolLit = "true" | "false" .
+```
+
+#### Boolean Expressions
+
+Boolean expressions are expressions that must result in a boolean value. Any other value type becomes the `undefined` value.
+
+```ebnf
+BoolExpr = Expression .
+```
+
+### List Literals
+
+A list literal denotes a list, which is an integer indexed collection of values.
+
+```ebnf
+ListLit = "[" [ ElementList [ "," ] ] "]" .
+ElementList = Element { "," Element } .
+Element = Expression | LiteralValue .
+```
+
+A list may contain zero or more values. The number of values in a list is its length.
+
+A list has a set of indices. An empty list has an empty set of indices. A non-empty list has the index set `{0...n - 1}` where `n` is the length of the list. Attempting to access a list using an index that is not a member of its set of indices results in the `undefined` value.
+
+### Map Literals
+
+A map literal denotes a map, which is an unordered group of elements indexed by a set of unique keys.
+
+```ebnf
+MapLit = "{" [ KeyedElementList [ "," ] ] "}" .
+KeyedElementList = KeyedElement { "," KeyedElement } .
+KeyedElement = Element ":" Element .
+```
+
+Keys can only be a boolean, numeric, or string type.
+
+The value of a non-existent key is the `undefined` value.
+
+### Function Literals
+
+A function literal represents a function.
+
+```ebnf
+FunctionLit = "func" Function .
+Function = Parameters FunctionBody .
+FunctionBody = Block .
+Parameters = "(" [ IdentifierList [ "," ] ] ")" .
+IdentifierList = identifier { "," identifier } .
+```
+
+```sentinel
+func(a, b) { return b }
+```
+
+Function literals are only allowed in the file scope. They may refer to variables defined in surrounding blocks.
+
+A function must terminate with a return statement. If a function has no meaningful return value, it should return `undefined`.
+
+### Rule Expressions
+
+A rule is an expression that is evaluated lazily and the result is memoized.
+
+If the optional "when" predicate is present, the rule is evaluated only when
+the "when" boolean expression results in true. If the predicate is false,
+the rule is not evaluated and returns true. The predicate is evaluated when
+the rule would be evaluated; it is also lazy and memoized in the same way.
+
+```ebnf
+RuleExpr = "rule" [ "when" BoolExpr ] "{" Expr "}" .
+```
+
+```sentinel
+rule { x is y }
+
+rule {
+ x == "value" or
+ y == "other"
+}
+
+rule when x is y { y > 42 }
+
+rule { map ["a", "b", "c"] as _, id {
+ { "id": id }
+}}
+```
+
+### Index Expressions
+
+A primary expression of the form `a[x]` denotes the element of a list or map indexed by x. The value x is called the index or key.
+
+For `a` of type map:
+
+- If `a` contains a key `x`, `a[x]` is the map value with key `x`.
+- If `a` does not contain key `x`, `a[x]` is the `undefined` value.
+
+For `a` of type list:
+
+- `x` must be an integer
+- `x` must be in the range `[-1 * length(a), length(a)-1]`
+- If `x` is contained in the set of indices of `a`, `a[x]` is the list element at index `x`. If `x` is negative, `a[x]` is equivalent to `a[length(a)+x]`.
+- If `x` is not contained in the set of indices of `a`, `a[x]` is the `undefined` value.
+
+For `a` of value `null`:
+
+- `a[x]` is the `undefined` value.
+
+Otherwise `a[x]` is an error.
+
+### Selectors
+
+For a primary expression x, the selector expression `x.f` denotes the field `f` of the value `x`. The identifier `f` is called the selector. The type of the selector expression is the type of the selector.
+
+As a special case, selectors can be [reserved words](#keywords) and keyword [operators](#operators-and-delimiters), but cannot be any other non-identifier element.
+
+Selectors are used to access data from [imports](#imports). The first primary expression `x` in `x.f` denotes the import name. The field `f` is the selector to access data from the import.
+
+Selectors may also be used to access map data with static keys. They are syntactic sugar over index expressions. `math.pi` is equivalent to `math["pi"]` and exhibit the same limitations and behavior as an index expression. Selectors cannot, however, be used to assign values to map data.
+
+Selectors on undefined result in undefined.
+
+```sentinel
+math.pi
+time.pst.hour
+```
+
+### Slice Expressions
+
+Slice expressions construct a substring or list from a string or list.
+
+The primary expression
+
+```sentinel
+a[low : high]
+```
+
+constructs a substring or list. The indices `low` and `high` select which elements of operand `a` appear in the result. The result has indices starting at 0 and length equal to `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+```
+
+For convenience, any of the indices may be omitted. A missing `low` index defaults to zero; a missing `high` index defaults to the length of the sliced operand:
+
+```sentinel
+a[2:] // same as a[2 : length(a)]
+a[:3] // same as a[0 : 3]
+a[:] // same as a[0 : length(a)]
+```
+
+The indices are in range if `0 <= low <= high <= length(a)`, otherwise they are out of range. If the indices are out of range at run time, the result is the `undefined` value.
+
+If `a` is the value `null`, the result is the `undefined` value.
+
+If `a` is any other value type, it is an error.
+
+### Calls
+
+Given an expression `f` where `f` is a function value:
+
+```sentinel
+f(a1, a2, … an)
+```
+
+calls `f` with arguments `a1, a2, ... an`. The type of the expression is the result of `f`. The arguments are evaluated left to right. Arguments are passed by value.
+
+### Operators
+
+```ebnf
+Expression = UnaryExpr | Expression binary_op Expression .
+UnaryExpr = PrimaryExpr | unary_op UnaryExpr .
+
+binary_op = logical_op | set_op | rel_op | add_op | mul_op | else_op .
+logical_op = "and" | "or" | "xor" .
+set_op = ["not"] ( "contains" | "in" ).
+rel_op = "==" | "!=" | "<" | "<=" | ">" | ">=" |
+ "is" | "is not" | "matches" | "not matches" .
+add_op = "+" | "-" .
+mul_op = "*" | "/" | "%" .
+else_op = "else" .
+unary_op = "+" | "-" | "!" | "not" .
+```
+
+#### Operator Precedence
+
+Unary operators have the highest precedence.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Binary operators of the same precedence associate from left to right. For instance, x / y _ z is the same as (x / y) _ z.
+
+### Arithmetic Operators
+
+Arithmetic operators apply to numeric values and yield a result of the same type when both operands are the same.
+
+Arithmetic operations between integer and floating-point types result in a floating-point value with the integer operand treated as a floating-point value for purposes of calculation.
+
+Arithmetic operations between numeric values (integer, floating-point) and non-numeric values (strings, lists) are not permitted.
+
+All five supported arithmetic operators (+, -, \*, /, %) apply to both integer and floating-point types; + also applies to strings.
+
+```plaintext
++ sum integers, floats, strings, lists
+* difference integers, floats
+* product integers, floats
+/ quotient integers, floats
+% remainder integers, floats
+```
+
+#### Integer operators
+
+For two integer values x and y, the integer quotient `q = x / y` and remainder `r = x % y` satisfy the following relationships:
+
+```sentinel
+x = q*y + r and |r| < |y|
+```
+
+with x / y truncated towards zero.
+
+```plaintext
+ x y x / y x % y
+ 5 3 1 2
+-5 3 -1 -2
+ 5 -3 -1 2
+-5 -3 1 -2
+```
+
+As an exception to this rule, if the dividend x is the most negative value for the int type of x (-9223372036854775808), the quotient `q = x / -1` is equal to x (and r = 0).
+
+If the divisor is a constant, it must not be zero. If the divisor is zero at run time, an error occurs.
+
+#### Integer overflow
+
+For signed integers, the operations `+`, `-`, and `*` may legally overflow and the resulting value exists and is deterministically defined by the signed integer representation, the operation, and its operands. No exception is raised as a result of overflow.
+
+#### Floating-point operators
+
+For floating-point numbers, +x is the same as x, while -x is the negation of x. The result of a floating-point division by zero is not specified beyond the IEEE-754 standard.
+
+#### String Concatenation
+
+Strings can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = "hi"
+y = "hello"
+x = x + ", " + y // "hi, hello"
+x += " and good bye" // "hi, hello and good bye"
+```
+
+String addition creates a new string by concatenating the operands.
+
+#### List Concatenation
+
+Lists can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = [1, 2]
+x = x + [2, 3] // [1, 2, 2, 3]
+x += [4] // [1, 2, 2, 3, 4]
+```
+
+List addition creates a new list by concatenating the operands.
+
+### Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+In any comparison, the two operands must be equivalent types. The only exception are integers and floats, which are considered numeric and may be compared. Any comparison between other non-matching types results in the `undefined` value.
+
+The equality operators `==`, `!=`, `is`, and `is not` apply to operands that are comparable. The ordering operators `<`, `<=`, `>`, and `>=` apply to operands that are ordered. The behavior of `is` with `==` and `is not` with `!=` is identical. These terms and the result of the comparisons are defined as follows:
+
+- Boolean values are comparable. Two boolean values are equal if they are either both true or both false.
+- Integer values are comparable and ordered, in the usual way.
+- Floating point values are comparable and ordered, as defined by the IEEE-754 standard.
+- An integer compared with floating point value treats the integer as the converted floating point value.
+- String values are comparable and ordered, lexically byte-wise.
+- Lists are comparable. Lists are equal if they are of equal length and their
+ corresponding elements are comparable and equal.
+- Maps are comparable. Maps are equal if they are of equal length and both their
+ corresponding keys and values are comparable and equal.
+
+If either operand is the `undefined` value, the result of the expression is the `undefined` value.
+
+### Emptiness Comparisons
+
+Checking the emptiness of an object can be achieved by using one of the emptiness expressions, `is empty` or `is not empty`. Although similar to [Comparison Operators](#comparison-operators) in that they yield a boolean value and read as though a comparison is taking place, both `is empty` and `is not empty` are evaluated as a multi-word expression.
+
+An emptiness comparison can only be performed against collections, strings or `undefined`, with the same rules as the [built-in length](/sentinel/functions/length) function.
+
+```sentinel
+"" is empty // true
+"foo" is empty // false
+[] is empty // true
+[1] is empty // false
+{} is empty // true
+{"a": "b"} is empty // false
+
+"" is not empty // false
+"foo" is not empty // true
+[] is not empty // false
+[1] is not empty // true
+{} is not empty // false
+{"a": "b"} is not empty // true
+
+undefined is empty // undefined
+undefined is not empty // undefined
+```
+
+### Logical Operators
+
+Logical operators apply to boolean values and yield a boolean result.
+
+Logical operators are evaluated left-to-right and perform short-circuit logic. The right-hand side is not guaranteed to be evaluated if a short-circuit can be performed.
+
+```plaintext
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+### Set Operators
+
+The set operators `contains` and `in` test for set inclusion for lists
+and maps, and substring inclusion for strings.
+
+Set operators may be negated by prefixing the operator with `not`: `not contains` and `not in`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+`contains` tests if the left-hand collection contains the right-hand value. For lists, this tests equality of any value. For maps, this tests equality of any key. For strings, this tests for substring existence.
+
+`in` tests if the right-hand collection contains the left-hand value. The behavior is equivalent to `contains`.
+
+The collection must be a list, map, or string. If it is the `undefined` value, the result is the `undefined` value. For any other value, the result is an error.
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+
+"test" contains "est" // true
+"test" contains "best" // false
+"test" in "testing" // true
+"best" in "testing" // false
+```
+
+### Matches Operator
+
+The matches operator tests if a string matches a regular expression.
+
+The matches operators may be negated by prefixing the operator with `not`: `not matches`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+The left-hand value is the string to test. The right-hand value is a string value representing a regular expression.
+
+The syntax of the regular expression is the same general syntax used by Python, Ruby, and other languages. More precisely, it is the syntax accepted by RE2. The regular expression is not anchored by default; any anchoring must be explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+If either operand is `undefined`, the result is the `undefined` value. For any other non-string value, the result is an error.
+
+### Else Operator
+
+Else operators can be used to provide a default value for an expression that may evaluate to the `undefined` value. Else operators return their left-hand value unless it is `undefined`, otherwise they return their right-hand value.
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else null
+```
+
+### Quantifier Expressions (any, all, filter, map)
+
+Quantifiers are expressions that apply a predicate to a collection (list or map). The purpose of the quantifier is to produce a result based on its particular type.
+
+`any` and `all` expressions are existential and universal quantifiers, respectively. `any` expressions are equivalent to a chain of `or` and `all` expressions are equivalent to a chain of `and`. Both expressions implement short-circuiting equivalent to logical operators.
+
+The `map` expression is an apply-to-all quantifier and returns a new list for all values in the input collection. The output list will be the same length as the input collection.
+
+The `filter` expression is a subset quantifier and returns the subset of all values in the input collection for which the supplied predicate applies. This will be zero or more elements, up to the length of the input.
+
+The body of a quantifier expression is a boolean expression.
+
+`any` returns the boolean value `true` if any value in the collection expression results in the body expression evaluating to `true`. If the body expression evaluates to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression result in the body expression evaluating to `true`. If any value in the collection expression result in the body expression evaluating to `false`, the all expression returns `false`.
+
+For empty collections, `any` returns false and `all` returns `true`.
+
+`map` always returns a list, regardless of if the input collection is a list itself; maps (as in the data type) also return lists when processed through `map`. Each element is the result of the supplied expression body.
+
+`filter` returns a list or map, the same type as its input collection. Only the elements for which the expression body evaluated to `true` will be returned. If an iteration of the expression body results in `undefined`, the entire result set is `undefined`.
+
+When the collection is a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When the collection is a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+QuantExpr = QuantOp Expression "as" [ identifier "," ] identifier "{" BoolExpr "}" .
+QuantOp = "all" | "any" | "filter" | "map" .
+```
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" } // true if every iterations is true
+any group.tasks as t { t.driver is "vmware" } // true if any iteration is true
+map group.tasks as t { { "driver": t.driver } } // [{"driver": "vmware"}, ...]
+filter group.tasks as t { t.driver is "vmware" } // subset of tasks that is true
+```
+
+## Statements
+
+### Expression Statements
+
+Function call expressions may exist in the statement context.
+
+```ebnf
+ExpressionStmt = Expression .
+```
+
+### Assignments
+
+Assignments set the value of an expression to the value of another expression.
+
+```ebnf
+Assignment = AssignExpr assign_op Expression .
+AssignExpr = identifier | IndexExpr .
+assign_op = [ add_op | mul_op ] "=" .
+```
+
+The assignment `x op= y` is equivalent to `x = x op (y)` for supported values of `op`.
+
+```sentinel
+a = 1
+b[12] = 42
+c["key"] = "value"
+
+a *= 12
+b[12] += 8
+```
+
+Assignments to lists and maps can be carried out using [index expressions](#index-expressions), with the operands of the index expression being evaluated alongside the right hand side expression in a right-to-left (RHS, LHS) order.
+
+In addition to the rules detailed in the section describing their use, the following rules apply when assigning to maps or lists using index expressions:
+
+- The [variable](#variables) must exist and be a list or map. Attempts to use an index assignment on an unknown variable, or a variable that not a list or map, results in a runtime error.
+- Assigning to a list or map index that already exists overwrites that index's data with evaluated right hand side's value.
+- Assigning to an unknown key in a map creates a key for that map with the evaluated right hand side's value assigned to that key.
+- Attempting to assign a value to a list index that is out of range results in a runtime error.
+
+### If Statements
+
+If statements specify the conditional execution of two branches according to the value of a boolean expression. If the expression evaluates to true, the "if" branch is executed, otherwise, if present, the "else" branch is executed.
+
+```ebnf
+IfStmt = "if" BoolExpr Block [ "else" ( IfStmt | Block ) ] .
+```
+
+```sentinel
+if x < y {
+ return x
+} else if x > z {
+ return z
+} else {
+ return y
+}
+```
+
+### Case Statements
+
+Case statements specify a selection control mechanism to conditionally execute a branch based on expression equality.
+
+Each clause that wishes to provide an expression must use the "when" keyword. Multiple expressions can be provided, separated with a comma (",").
+
+There can be one, optional, "else" clause within a `case` statement. The "else" clause is executed if all other clauses failed to run.
+
+The clauses are evaluated in the order they are listed, with the first successful evaluation being executed.
+
+A `case` statement can optionally provide an expression. If no expression is provided, it is the equivalent of writing `case true {`.
+
+```ebnf
+CaseStmt = "case" [ Expression ] "{" { CaseWhenClause } "}" .
+CaseWhenClause = CaseWhenCase ":" StatementList .
+CaseWhenCase = "when" Expression { "," Expression } | "else" .
+```
+
+```sentinel
+case x {
+when y, z:
+ return true
+else:
+ return false
+}
+
+case {
+when x > 42:
+ return true
+else:
+ return false
+}
+```
+
+### For Statements
+
+A `for` statement specifies repeated execution of a block.
+
+The expression must evaluate to a list or a map.
+
+When iterating over a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When iterating over a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+ForStmt = "for" Expressions "as" [ identifier "," ] identifier Block .
+```
+
+```sentinel
+for [1, 2, 3] as v { count += v } // basic sum
+
+for [1, 2, 3] as idx, v {
+ if idx > 1 { count += v }
+}
+
+data = { "a": 12, "b": 32 }
+for data as k { count += data[k] } // sum map values
+for data as k, v { count += v }
+```
+
+### Break Statements
+
+A `break` statement terminates execution of the innermost `for` statement
+within the same function.
+
+```ebnf
+BreakStmt = "break" .
+```
+
+```sentinel
+// Will only print "1"
+for [1, 2, 3] as v {
+ print(v)
+ break
+}
+```
+
+### Continue Statements
+
+A `continue` statement begins the next iteration of the innermost `for` loop.
+The `for` loop must be within the same function.
+
+```ebnf
+ContinueStmt = "continue" .
+```
+
+```sentinel
+// Will print 1, 3
+for [1, 2, 3] as v {
+ if v == 2 {
+ continue
+ }
+
+ print(v)
+ break
+}
+```
+
+### Return Statements
+
+A return statement in a function F terminates the execution of F and provides the result value.
+
+```ebnf
+ReturnStmt = "return" Expression .
+```
+
+A function must terminate with a return statement.
+
+The return statement can be invoked at any time during a function to terminate execution.
+
+## Imports
+
+An import adds an assignment to the global scope to external definitions.
+
+The import declarations must only appear at the top of the source file, before any other statements. Comments may appear before imports.
+
+```ebnf
+ImportDecl = "import" Name ["as" identifier] .
+Name = string_lit .
+```
+
+An import name and identifier must be distinct. Two names may not repeated even if they're declared with different identifiers.
+
+If an identifier is not specified, the identifier is the name of the import itself.
+
+An import is not a valid value. The identifier representing the import may not be used as an expression, such as in assignment statements or call expressions.
+
+Values in imports are not assignable directly via their selectors. Function calls may be used to alter the state of the external data represented by the import. Whether or not this is supported is specific to the import implementation. The exception to this assignment restriction is the memoized data returned by a call to an import (see below).
+
+Data returned by imports can be memoized, meaning that data is returned by the import in the form of a map which is then used as much as possible without having to return to the import for more data. For example, `t = time.now` returns a map so that `t.hour` will be able to be looked up without having to go back to the import for that data.
+
+Data returned by imports may be callable, meaning that methods may be called on the memoized data returned by an import. For example, given `t = time.now`, `t.after(some_previous_time)` is valid, with the receiver data being set to the local memoized data stored in `t`. It is a valid case to accept modifications to the receiver data (example: assignment to the memoized data via index expressions), as long as all data in in the receiver continues to be string-keyed. It is an invalid case to accept receiver data with non-string-typed keys. Methods may also alter the contents of receiver data so that it is different after the call is finished.
+
+As with methods, imports may also have callable keys where the data may not be memoized. Example: in the event of `v = foo.bar`, if `v.baz` is not included in the memoized data, a call will be made to the import to fetch it. The same rules and restrictions to receiver data apply to callable keys as do to method calls.
+
+The support for callable methods and non-memoized keys on data returned by imports is specific to the import implementation.
+
+The handling of import loading is specific to the runtime implementation.
+
+Implicit imports may be added by the runtime, for example for standard library definitions. An explicit import of the same name should override a matching implicit import. For example, if "time" is an implicit import and the program specifies `import "time" as cal`, then only `cal` is defined.
+
+```sentinel
+import "time"
+import "math" as m
+```
+
+## Parameters
+
+```ebnf
+ParamDecl = "param" identifier [ "default" Expression ]
+```
+
+A parameter is a way for a policy to describe variables that are expected to be supplied by the calling application, in addition to any default value.
+
+Parameter declarations must appear at the top of the source file, following imports.
+
+Like imports, comments may appear before parameters; this is mainly pertinent when the policy is not importing anything, which would make parameters the first declarations that appear in a policy.
+
+A parameter declaration describes a valid variable that is added to the scope of a policy at runtime. This variable has the same properties and rules as other variables assigned during the course of policy evaluation as defined by the specification. This includes having a dynamic type and being able to be re-assigned during execution.
+
+Parameters may only be strings, integers, floating point numbers, booleans, lists, or maps. More complex types such as rules or functions are not allowed.
+
+A parameter can specify a default value by adding one in the declaration via use of the "default" keyword, and supplying a literal for the default value. The literal for a default is a very limited expression, comprising of:
+
+- For strings, a simple string literal;
+- For integers and floating-point numbers, either a literal denoting the value, or a unary expression with the literal, and the operators - or +;
+- For booleans, the pre-declared identifiers true or false;
+- For lists and maps, their respective literals composed of values and keys (for maps) of the above values only.
+
+Any other type of expression or identifier is not allowed.
+
+A parameter that does not have a default value defined is required by the policy. Evaluating a policy with a required parameter that has not been supplied at execution results in a runtime error.
+
+Parameters must not conflict with imports or any other identifiers currently defined in the global scope, including any reserved or pre-declared identifiers. Attempting to assign a parameter to an existing value results in a runtime error.
+
+```sentinel
+import "time"
+
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+param time default "11:00" // error, conflicts with "time" import
+param undefined // error, reserved identifier
+```
+
+## Built-in Functions
+
+Built-in functions are predeclared. They are called like any other function.
+
+### Length
+
+The built-in function `length` returns the length of a collection of string.
+
+The length of a string is the number of bytes in the string. It is not necessarilly the number of characters.
+
+The length of a collection is the number of elements in that collection.
+
+The length of `undefined` is `undefined`.
+
+### Collections
+
+#### List Append
+
+The built-in function `append` appends a value to the end of a list in-place.
+
+Appending to a non-list value results in an immediate `fail`.
+
+The return value of `append` is always the `undefined` value.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+#### Map Delete
+
+The built-in function `delete` deletes elements from a map by key. The map is modified in-place.
+
+Deleting a key that does not exist does nothing.
+
+Calling delete for a non-map value results in an immediate `fail`.
+
+The return value of `delete` is always the `undefined` value.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
+
+#### Keys and Values
+
+The built-in function `keys` and `values` return the keys and values of a map, respectively. The return value is a list. Both functions return values in an unspecified order.
+
+The `keys` or `values` of `undefined` is `undefined`.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+### Range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it defaults to `1`.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4]
+range(1, 5, 2) // [1,3]
+range(0, -3, -1) // [0,-1,-2]
+```
+
+### Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a value of that type according to the rules below.
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
+
+### Printing
+
+The built-in function `print` can be used to output formatted debug information. The runtime may omit print function calls depending on its execution mode. The runtime may choose where the output of a print function goes.
+
+`print` is a variadic function, accepting one or more values to be printed; values are separated by a single whitespace character (`" "`).
+
+The return value of `print` is always `true` to allow it to be used in boolean expressions.
+
+```sentinel
+print("hello") // "hello"
+print("hello", "world") // "hello world"
+print("The", "number", "is", 42) // "The number is 42"
+print([1, 2, 3]) // "[1, 2, 3]"
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // "false"
+```
+
+### Errors
+
+The built-in function `error` is equivalent to `print` but immediately causes execution to halt and the main rule to evaluate to `false`. The program execution is considered to have failed.
+
+## Program Execution
+
+Program execution begins by evaluating the source top to bottom. If evaluation is successful, the `main` rule is evaluated and its result is returned. Execution can be interrupted at any time by an error, which can be called explicitly or implicitly through illegal or undefined behavior.
+
+---
+
+> The content of this page is licensed under the [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
+>
+> Based on [The Go Programming Language Specification](https://golang.org/ref/spec) licensed under [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/language/undefined.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/language/undefined.mdx
new file mode 100644
index 0000000000..dc51531783
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/language/undefined.mdx
@@ -0,0 +1,97 @@
+---
+page_title: Sentinel Language - Undefined
+sidebar_current: docs-language-undefined
+description: >-
+ The value `undefined` is a special value representing undefined behavior or an
+ unknown value. Any operation on an `undefined` value results in `undefined`.
+layout: docs
+---
+
+# Language: Undefined
+
+The value `undefined` is a special value representing undefined behavior
+or an unknown value.
+
+Undefined is not an error because there are situations where the undefined
+value can be safely ignored and the language also provides construts for
+recovering from an undefined value.
+
+The undefined value can be created manually with `undefined`. In most cases,
+undefined is created by accessing a non-existent list element or value.
+The undefined value is created in a number of other circumstances. The documentation
+will note when an undefined value may be created.
+
+Almost any operation with `undefined` results in `undefined`. For example,
+performing math on undefined, attempting to call a function that is undefined,
+etc.
+
+## Main and Undefined
+
+If the value `undefined` is returned from the top-level `main` rule, then
+the policy is considered `false`. However, the runtime provides mechanisms
+for the host application to determine the policy failure was caused by
+an undefined value and report that to the user.
+
+The `main` rule returning the undefined value should be considered a logical
+error in most cases and should probably be corrected by the policy writer.
+
+## Debugging Undefined
+
+When an `undefined` value is first created (either explicitly or implicitly),
+the runtime will record the position where the undefined was created. This
+location can be used to find logic that could be erroneous.
+
+## Boolean Logic and Undefined
+
+Boolean logic is one way to safely recover from `undefined`. If boolean
+logic can safely determine a result despite an undefined value, that result
+will be returned.
+
+For example: `undefined or true` will result in `true`, because no matter
+what actual value `undefined` could've been if the policy behaved properly,
+the boolean expression would still be true.
+
+Another scenario where `undefined` can be safely ignored is short-circuit
+logic with `and`. `false and undefined` will result in `false`.
+
+The full table of logical operations on `undefined` is below:
+
+```sentinel
+undefined or true = true
+undefined or false = undefined
+undefined or undefined = undefined
+undefined and true = undefined
+undefined and false = undefined
+undefined and undefined = undefined
+undefined xor true = undefined
+undefined xor false = undefined
+undefined xor undefined = undefined
+
+// Short-circuit examples
+false or true or undefined = true
+false or undefined or true = true
+true and false and undefined = false
+true and undefined and false = undefined
+```
+
+## Else Expressions
+
+The `else` expression allows explicit recovery from undefined and is useful
+for setting default values.
+
+`else` is an operator where the left and right hand side are values. If the
+left-hand value is `undefined`, the right-hand value is returned. Otherwise,
+the left-hand value is returned.
+
+Examples:
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else "default"
+
+// In more complex scenarios
+
+foo() else 42 == 12
+a = config.value else "default"
+```
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/language/values.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/language/values.mdx
new file mode 100644
index 0000000000..f932480340
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/language/values.mdx
@@ -0,0 +1,210 @@
+---
+page_title: Sentinel Language - Values
+sidebar_current: docs-language-values
+description: Values are the data that Sentinel policies operate on.
+layout: docs
+---
+
+# Language: Values
+
+Values are the _data_ that Sentinel policies operate on. You can create this
+data yourself, for example by just typing the number `42`, or you may access
+this data from an external source to make policy decisions.
+
+Values have a _type_, which determines what kind of operations can be performed
+on the data. Example types are booleans (true and false), numbers,
+and strings (text).
+
+This page documents all the available value types. You can also
+[convert between types](#type-conversion).
+
+## Boolean
+
+A boolean is a value that is either true or false.
+
+A boolean value is created literally with `true` or `false`.
+
+As a policy language, booleans are central to the behavior of Sentinel.
+Booleans are used as conditions in [if statements](/sentinel/language/conditionals),
+are the result of [rules](/sentinel/language/rules), and more. The ultimate
+result of a Sentinel policy is true or false.
+
+## Integer
+
+An integer is a whole number.
+
+An integer is a 64-bit value. This means it can represent numbers from
+-9,223,372,036,854,775,808 to 9,223,372,036,854,775,808.
+
+Integers are created by typing them out literally with no
+separators (such as a comma). An optional prefix can set a non-decimal base:
+`0` for octal, and `0x` for hexadecimal. In hexadecimal literals, letters
+`a-f` and `A-F` represent values 10 to 15.
+
+Example integers are shown below:
+
+```sentinel
+42
+0600
+0xBadFace
+170141183460469
+```
+
+Integers are used for math and numerical comparison.
+
+## Float
+
+A float is a number with a decimal point. It has an integer part, a decimal
+point, a fractional part, and optionally an exponent part. Example
+floats are shown below:
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+Floats are IEEE-754 64-bit floating point numbers.
+
+## String
+
+Strings are text values.
+
+Strings are created by wrapping text in double quotes, such as `"hello"`.
+Within the quotes, any character may appear except newline and an unescaped
+double quote. The text between the quotes is the value.
+
+Because Sentinel policies must be UTF-8 encoded text, strings themselves are
+UTF-8 encoded text. This means you can put any value UTF-8 character into
+a string, such as `"日本語"`.
+
+You can have a string with a literal double-quote by _escaping_ it with
+a backslash. For example: `"they said \"hello\""` turns into the value
+`they said "hello"`.
+
+Backslash escapes can be used for much more than only escaping a double quote.
+Newlines are `\n`, a literal backslash is `\\`, and many more. The full list
+of available backslash escapes can be found
+[in the language specification](/sentinel/language/spec#string-literals).
+
+Example strings:
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+Strings do not support indexing, however it is possible to achieve a similar
+result through the combination of the [strings](/sentinel/imports/strings)
+standard import and list indexing.
+
+```sentinel playground
+import "strings"
+
+asciistr = "Hello, world!"
+utf8str = "日本語"
+
+utf8split = rule {
+ strings.split(utf8str, "")[2] == "語"
+}
+
+asciisplit = rule {
+ strings.split(asciistr, "")[5] == ","
+}
+
+main = rule { utf8split and asciisplit }
+```
+
+Strings support [slicing](/sentinel/language/slices) to efficiently create
+substrings.
+
+## List
+
+See [Lists](/sentinel/language/lists).
+
+## Map
+
+See [Maps](/sentinel/language/maps).
+
+## Rule
+
+See [Rules](/sentinel/language/rules).
+
+## Function
+
+See [Functions](/sentinel/language/functions).
+
+## Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a
+value of that type. Some examples are shown below followed by a list of exact
+rules for type conversion.
+
+```sentinel
+int(42) // 42
+int("42") // 42
+int(42.8) // 42
+int(true) // 1
+
+float(1.2) // 1.2
+float(1) // 1.0
+float("4.2") // 4.2
+float(true) // 1.0
+
+string("foo") // "foo"
+string(88) // "88"
+string(0xF) // "15"
+string(true) // "true"
+
+bool("true") // true
+bool(1) // true
+bool(-1) // true
+bool(0.1) // true
+bool("false") // false
+bool(0) // false
+```
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/language/variables.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/language/variables.mdx
new file mode 100644
index 0000000000..7516890212
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/language/variables.mdx
@@ -0,0 +1,99 @@
+---
+page_title: Sentinel Language - Variables
+sidebar_current: docs-language-vars
+description: Variables store values that can be used later.
+layout: docs
+---
+
+# Language: Variables
+
+Variables store values that can be used later.
+
+Most notably, a variable assignment of `main` is required for all
+Sentinel policies. This is the main [rule](/sentinel/language/rules) that
+is executed to determine the result of a policy. The `main` rule may
+use other variables that are assigned in the policy.
+
+Example:
+
+```sentinel
+a = 1
+b = a + 1
+```
+
+In this example, two variables are assigned: `a` and `b`. `a` is given
+the value of "1" and `b` uses the `a` to calculate its own value.
+
+## Syntax
+
+The syntax for assigning a variable is:
+
+```sentinel
+IDENTIFIER = VALUE
+```
+
+On the left of the equal sign is an _identifier_. This is a name for your
+variable and will be how you reference it later. An identifier is any
+combination of letters and digits, but must start with a letter. An
+underscore `_` is also a valid letter.
+
+On the right is any valid Sentinel value or expression. A value is a
+literal value such as a number or string. An expression is some computed
+value such as doing math, calling a function, etc.
+
+## Assignment and Reassignment
+
+A variable is _assigned_ when it is given a value. You can also _reassign_
+variables at any time by setting it to a new value. The new value takes
+effect for any subsequent use of that variable. A variable can be reassigned
+to a different type.
+
+For example:
+
+```sentinel
+a = 1 // a = 1
+b = a // b = 1
+a = "value" // a = "value", b = 1
+c = a // c = "value", b = 1
+```
+
+In the above example you can see that the variables are set and reassigned.
+Notice that the value of a variable is the _current_ value, and that reassigning
+a variable only affects _future_ uses of that variable. You can see this with
+`c` and `b` being different values.
+
+## Unassigned Variables
+
+Using a variable that is _unassigned_ is an error.
+
+In the example below, the first line would result in the policy erroring.
+Sentinel is executed top-down and the value of `c` is not available yet
+on the first line.
+
+```sentinel
+a = c // Error!
+c = 1
+```
+
+## Type Conversion
+
+While a topic more related to [values][lang-values], variables can be assigned
+(or-reassigned) a different value type via type conversion.
+
+[lang-values]: /sentinel/language/values
+
+```sentinel
+s = "1.1"
+a = int(s) // a = 1
+a = float(s) // a = 1.1
+
+a = 1
+s = string(a) // s = "1"
+```
+
+For more details, see the type conversion sections in the
+[values][lang-values-type-conversion] page, or the [Sentinel Language
+Specification][lang-spec-type-conversion].
+
+[lang-values-type-conversion]: /sentinel/language/values#type-conversion
+[lang-spec-type-conversion]: /sentinel/language/spec#type-conversion
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/nomad.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/nomad.mdx
new file mode 100644
index 0000000000..a105b0337d
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/nomad.mdx
@@ -0,0 +1,51 @@
+---
+page_title: Nomad and Sentinel
+sidebar_current: docs-app-nomad
+description: >-
+ Nomad Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies can currently execute on job
+ submission (creation, update).
+layout: docs
+---
+
+# Nomad
+
+Nomad is a simple, flexible scheduler and workload orchestrator.
+[Nomad Enterprise](https://www.hashicorp.com/products/nomad/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies can currently execute on job submission (creation, update).
+
+Sentinel policies have full access to the job structure. This allows the
+Sentinel policy to control behavior based on any attribute within a job,
+such as the driver, resource requests, network configuration, volume
+configuration, and more. The information that Sentinel policies have access
+to will expand over time.
+
+Nomad fully supports all [enforcement levels](/sentinel/concepts/enforcement-levels).
+For soft mandatory policies, the `sentinel-override` capability must be
+available on the user's ACL policy to allow override. Overrides are always
+logged.
+
+The Nomad integration with Sentinel is documented in depth in the
+[Nomad Enterprise documentation](https://www.nomadproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Only allow Docker-based jobs.**
+
+```sentinel
+# Test policy only allows Docker based tasks
+main = rule { all_drivers_docker }
+
+# all_drivers_docker checks that all the drivers in use are Docker
+
+all_drivers_docker = rule {
+ all job.task_groups as tg {
+ all tg.tasks as task {
+ task.driver is "docker"
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/terraform.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/terraform.mdx
new file mode 100644
index 0000000000..5922fd0cfb
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/terraform.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Terraform and Sentinel
+sidebar_current: docs-app-terraform
+description: >-
+ Sentinel can be used with Terraform and Terraform Enterprise to enforce policy
+ on Terraform configurations, states, and plans.
+layout: docs
+---
+
+# Terraform
+
+[Terraform Enterprise](https://www.hashicorp.com/products/terraform/) uses Sentinel to enforce
+policy on [Terraform](https://www.terraform.io) configurations, states, and plans.
+
+The Sentinel integration with Terraform runs within
+[Terraform Enterprise](https://www.hashicorp.com/products/terraform/)
+after a `terraform plan` and before a `terraform apply`. The policies
+have access to the created plan, the state at the time of the plan,
+and the configuration at the time of the plan.
+
+The Terraform integration with Sentinel is documented in depth in the [Terraform Enterprise documentation](https://www.terraform.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show basic examples.
+
+### Examples
+
+**Example: All AWS instances must have a tag**
+
+```sentinel
+import "tfplan"
+
+main = rule {
+ all tfplan.resources.aws*instance as *, instances {
+ all instances as \_, r {
+ (length(r.applied.tags) else 0) > 0
+ }
+ }
+}
+```
+
+**Example: Only allow GCP instance sizes smaller than `n1-standard-16`**
+
+```sentinel
+import "tfplan"
+
+allowed_machine_types = [
+ "n1-standard-1",
+ "n1-standard-2",
+ "n1-standard-4",
+ "n1-standard-8",
+]
+
+main = rule {
+ all tfplan.resources.google_compute_instance as _, instances {
+ all instances as _, r {
+ r.applied.machine_type in allowed_machine_types
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/vault.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/vault.mdx
new file mode 100644
index 0000000000..6db2f8af22
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/vault.mdx
@@ -0,0 +1,64 @@
+---
+page_title: Vault and Sentinel
+sidebar_current: docs-app-vault
+description: >-
+ Vault Enterprise uses Sentinel to augment the built-in policy system to
+ provide Role Governing Policies (RGPs) and Endpoint Governing Policies (EGPs)
+ to enable complex, flexible policies across identities and endpoints.
+layout: docs
+---
+
+# Vault
+
+[Vault Enterprise](https://www.hashicorp.com/products/vault/) uses Sentinel
+to augment the built-in policy system to provide Role Governing Policies (RGPs)
+and Endpoint Governing Policies (EGPs) to enable complex, flexible policies
+across identities and endpoints.
+
+**Role Governing Policies (RGPs)** are Sentinel policies that are tied to
+particular tokens, Identity entities, or Identity groups. They have access to
+a rich set of controls across various aspects of Vault. These are evaluated
+whenever a token they're attached to is used.
+
+**Endpoint Governing Policies (EGPs)** are Sentinel policies that are tied to
+particular paths instead of tokens. They have access to as much request
+information as possible, but they can take effect even on unauthenticated
+paths, such as login paths.
+
+The Vault integration with Sentinel is documented in depth in the
+[Vault Enterprise documentation](https://www.vaultproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Endpoint policy that requires MFA authentication from a corporate network.**
+
+```sentinel
+import "sockaddr"
+
+# We expect logins to come only from our private IP range
+cidrcheck = rule {
+ sockaddr.is_contained(request.connection.remote_addr, "10.20.0.0/16")
+}
+
+# Require Ping MFA validation to succeed
+ping_valid = rule {
+ mfa.methods.ping.valid
+}
+
+main = rule when request.path is "auth/ldap/login" {
+ ping_valid and cidrcheck
+}
+```
+
+**Example: Endpoint policy that disallows tokens created before a certain time.**
+
+```sentinel
+import "time"
+import "strings"
+
+main = rule when not strings.has_prefix(request.path, "auth/ldap/login") {
+ time.load(token.creation_time).unix > time.load("2017-09-17T13:25:29Z").unix
+}
+```
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/writing/basic.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/writing/basic.mdx
new file mode 100644
index 0000000000..b62533466e
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/writing/basic.mdx
@@ -0,0 +1,113 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-basic
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Policy Basics
+
+Sentinel policies are easy to write while still supporting advanced constructs
+for creating complex policies. This page will explain the basics of writing
+Sentinel policies to get started. You don't need any prior Sentinel knowledge,
+but we do recommend reading the
+[getting started guide](/sentinel/intro/getting-started/first-policy) and
+[language guide](/sentinel/language) after this.
+
+Sentinel policies are text files written using the [Sentinel language](/sentinel/language).
+The policies are evaluated top-to-bottom. The value of `main` after execution
+determines whether a policy passes or fails.
+
+## The Simplest Policy
+
+Sentinel only requires that a policy have a `main` variable that evaluates to
+a boolean value.
+
+A valid example is shown below:
+
+```sentinel playground
+main = 10 > 5
+```
+
+This type of minimal policy is not purely academic. In practice, simple
+policies can often be reduced to a single line logical statement resulting
+in `true` or `false`. However, the expression is usually wrapped in a
+[rule](/sentinel/language/rules) for testing reasons.
+
+You can verify Sentinel will execute this minimal policy using the CLI:
+
+```
+$ sentinel apply minimal.sentinel
+Pass
+```
+
+## Logical Expressions
+
+Policy is at its core a set of logic: you can or can not perform some action
+under a certain set of circumstances. Those circumstances are logical
+expressions. Therefore, Sentinel policies primarily translate into logical
+expressions.
+
+Detailed documentation on boolean expressions is
+[available in the language guide](/sentinel/language/boolexpr).
+
+A simple numerical comparison was seen in the first example on this page.
+Sentinel also provides inclusion operators such as `contains`, `any`, `all`, and
+more. Sentinel allows some operators to have aliases to promote readability
+while remaining programmer-familiar, such as `==` which can equivalently be `is`.
+
+The example below verifies that all numbers in a list are even:
+
+```sentinel playground
+numbers = [6, 22, 8, 4, 12]
+main = all numbers as n { n % 2 is 0 }
+```
+
+## Variables
+
+A policy will very often use variables. Applications such as
+[Nomad](https://www.nomadproject.io) inject variables into the global
+scope of a policy for making policy decisions. For example, Nomad injects
+the job that is being run into the policy scope. Knowing how to use
+variables is critical to effectively using Sentinel.
+
+Detailed documentation on how to define and access variables is
+[available in the language guide](/sentinel/language/variables).
+
+Variables can be defined and used explicitly. For example:
+
+```sentinel playground
+value = 10
+main = value > 5
+```
+
+But they may also be introduced implictly by the host system. Nomad
+injects `job` into policies to describe the job that is being run. The
+policy below is a valid policy that requires a job have two task groups.
+Notice that `job` is not defined anywhere. It is implicitly inserted by
+the host application (in this case Nomad). Refer to the application you're
+writing policy for to determine if it implicitly inserts values.
+
+```json sentinel
+{
+ "policy": "main = length(job.task_groups) is 2",
+ "globals": {
+ "job": {
+ "task_groups": ["foo", "bar"]
+ }
+ }
+}
+```
+
+## And more!
+
+The Sentinel language supports many more features such as functions,
+loops, and more. You can learn about all of this in the
+[complete language guide](/sentinel/language).
+
+The other pages in the [writing policy](/sentinel/writing)
+will cover other information you need to know about writing Sentinel
+policies that isn't simply a language reference.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/writing/debugging.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/writing/debugging.mdx
new file mode 100644
index 0000000000..7dc284d222
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/writing/debugging.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-debugging
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Debugging
+
+As policies become more complex, it becomes harder to understand exactly why
+a policy may be behaving differently than you expect.
+
+Prior to actively debugging, there are multiple best practices we recommend
+following. We recommend breaking your policy down into a set of
+[rules](/sentinel/writing/rules). This will make it easier to understand
+[traces](/sentinel/writing/tracing) and make it easier to
+[test](/sentinel/writing/testing) your policies.
+This should help to debug policies up to a significant complexity.
+
+## Print Logging
+
+The built-in [print function](/sentinel/language/logging-errors#print)
+can be used to log data. The print output is only shown during failures
+or when [tracing is explicitly enabled](/sentinel/writing/tracing).
+
+The `print` function outputs each argument, which can be of any type, converted
+to a human-friendly string format. Arguments are separated with a single space.
+
+Example:
+
+```sentinel playground
+value = 42
+print("the number is", value) // the number is 42
+
+object = { "foo": false }
+print(object) // { "foo": false }
+
+main = rule { true }
+```
+
+The `print` function returns the value `true` so that it can also be
+used within rule expressions. Note that the `print` function should be
+used at the beginning of statements otherwise they may never be
+reached. This is due to internal performance techniques within Sentinel
+like [short-circuiting](https://en.wikipedia.org/wiki/Short-circuit_evaluation).
+
+Example:
+
+```sentinel playground
+// rule_a and rule_b will evaluate to false
+rule_a = rule { print("rule_a is printed") and false } // "rule_a is printed"
+rule_b = rule { false and print("rule_b is printed") } // Nothing is printed
+
+// rule_c and rule_d will evaluate to true
+rule_c = rule { print("rule_c is printed") or true } // "rule_c is printed"
+rule_d = rule { true or print("rule_d is printed") } // Nothing is printed
+
+main = rule { rule_a or rule_b or rule_c and rule_d }
+```
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/writing/imports.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/writing/imports.mdx
new file mode 100644
index 0000000000..74ac751ee4
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/writing/imports.mdx
@@ -0,0 +1,117 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom import](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only local
+context for making policy decisions.
+
+Sentinel also comes with a set of [standard imports](/sentinel/imports).
+Standard imports are available to every Sentinel policy to help policy writers
+with common tasks such as working with the time, network addresses, and more.
+
+-> **This page is about writing policies that _use_ imports.** If you're
+interested in _creating_ a new import, please see the section on [extending
+Sentinel](/sentinel/extending) for information on how to write modules and
+import plugins.
+
+## Using Imports
+
+To use an import, you use the `import` keyword at the top of your policy. This
+specifies the name of the import you want to use. The application you're writing
+the policy for must already be
+[configured](/sentinel/configuration#imports) to provide that
+import.
+
+Details on imports can be found in the
+[import section in the language reference](/sentinel/language/imports).
+
+In the example below, we use the [time](/sentinel/imports/time) import:
+
+```sentinel playground
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+There are two options to develop a policy locally when using imports:
+configure Sentinel to launch the import on `apply`, or mock the import
+using `mock`. The former requires access to the import while the
+latter is faster (doesn't have to launch a process for plugins) and
+doesn't require the import.
+
+## Mocking Imports
+
+The first option to developing policies locally is to mock the import values.
+When mocking an import, you don't need the import to be available. This can be
+useful since some imports may not be available as a plugin and may only be
+available to the application the policy runs in.
+
+Mocks are specified via the [configuration
+file](/sentinel/configuration). Mocks can also be used for
+[testing](/sentinel/writing/testing).
+
+You can supply mock configuration one of two ways, depending on your use case:
+
+- **[Using static data](/sentinel/configuration#mocking-static-data):**
+ Use this method when you can accurately represent your mock data in JSON and
+ do not need to mock complex Sentinel features such as functions.
+- **[Using Sentinel code](/sentinel/configuration#mocking-with-sentinel-code):**
+ Use this method when using a static JSON object is insufficient, such as when
+ you need to mock functions or other complex Sentinel features.
+
+Our example does not require complex data to be mocked, so a static object
+is sufficient:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ hour = 12
+ weekday_name = "Tuesday"
+ }
+ }
+}
+```
+
+This can be used via the CLI:
+
+```
+$ sentinel apply -config=config.hcl policy.sentinel
+Pass
+```
+
+## Launching Import Plugins
+
+If you have access to the plugin binary, you can launch the import. The
+benefit of this is that it is really using the import to test your
+policy. If the import changes, your policies may start failing. If you
+only use mock data and the import changes, your policies will still
+appear to work.
+
+Imports are configured in the configuration file:
+
+```hcl
+import "plugin" "custom_time" {
+ source = "/path/to/sentinel-time-import"
+}
+```
+
+This would require the `sentinel-time-import` binary. For this example
+this doesn't currently exist. We plan on writing one to provide for this
+section of the documentation.
+
+## Custom Imports
+
+You can also [create your own imports](/sentinel/extending).
+
+If your policy decisions could benefit from accessing external information,
+then you can use custom imports as a way to do this.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/writing/index.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/writing/index.mdx
new file mode 100644
index 0000000000..064069d389
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/writing/index.mdx
@@ -0,0 +1,35 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing
+description: >-
+ Sentinel provides a language and workflow for building policy across any
+ system that embeds Sentinel. By learning Sentinel once, you are able to
+ effectively control access to many systems using Sentinel's powerful features.
+ Basic concepts that are important to understand for Vault usage.
+layout: docs
+---
+
+# Writing Sentinel Policy
+
+This section covers how to write Sentinel policies.
+
+It is recommended that you complete the
+[getting started guide](/sentinel/intro/getting-started/first-policy) prior to
+reading this section of the documentation. The getting started guide will
+explain all the basics of writing and testing policies. This section of the
+documentation will serve as more of a reference guide to all available
+features of Sentinel.
+
+Sentinel provides a language and workflow for building policy across any system
+that embeds Sentinel. By learning Sentinel once, you are able to effectively
+control access to many systems using Sentinel's powerful features.
+
+Sentinel also provides a [local CLI](/sentinel/commands) for developing and testing
+Sentinel policies. This CLI can be integrated into a daily developer's workflow
+along with CI to treat [policy as code](/sentinel/concepts/policy-as-code).
+
+Sentinel uses its own [language](/sentinel/language) for writing
+policies. You can view a [language reference](/sentinel/language)
+as well as the [specification](/sentinel/language/spec) for details. You
+don't have to read those documents immediately, since the language should
+be easy enough to pick up throughout this section.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/writing/rules.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/writing/rules.mdx
new file mode 100644
index 0000000000..ab4fc69d08
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/writing/rules.mdx
@@ -0,0 +1,65 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-rules
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Rules
+
+Rules form the basis of a policy by representing behavior that is either passing
+or failing. Rules are a first class language construct in Sentinel. A policy can
+and should be broken down into rules to aid with readability, testability, and
+performance.
+
+Rules provide:
+
+- **Readability:** A policy that is broken down into rules can be read
+ more easily. The logic becomes clearer to see for policy writers when
+ they come back to it.
+
+- **Reportability:** When [tracing](/sentinel/writing/tracing) is enabled,
+ rules form the foundation of the data returned. All evaluated rules are added
+ to the trace with the data the rule evaluated to, and their position within
+ policy or module source files.
+
+- **Testability:** [Policy testing](/sentinel/writing/testing) is
+ built on asserting the values of rules. By breaking logic down into
+ rules, you're able to more effectively test your policies.
+
+- **Performance:** Rules are only evaluated on demand, and are also only
+ evaluated once. This means that a rule referenced multiple times only has a
+ one-time performance cost. For complex logic, this could result in improved
+ performance.
+
+An example usage of rules is shown below:
+
+```json sentinel
+{
+ "policy": "is_sunny = rule { weather is \"sunny\" }\nis_wednesday = rule { day is \"wednesday\" }\nmain = rule { is_sunny and is_wednesday }",
+ "globals": {
+ "weather": "sunny",
+ "day": "wednesday"
+ }
+}
+```
+
+Rules can also contain non-boolean data. This can be useful for passing more
+detail to the caller than an opaque boolean value would be able to communicate.
+
+```sentinel playground
+days = [{"weather": "sunny"}]
+main = rule {
+ filter days as d {
+ d.weather is not "sunny"
+ }
+}
+```
+
+Details about rules and their behavior can be found in
+[the language reference](/sentinel/language/rules). Rules have important
+behavior so we recommend reading the language reference on rules.
+Rules will be used throughout the remainder of the documentation.
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/writing/testing.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/writing/testing.mdx
new file mode 100644
index 0000000000..61ab0ea46c
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/writing/testing.mdx
@@ -0,0 +1,491 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-testing
+description: >-
+ Sentinel has a built-in test framework to validate a policy behaves as
+ expected.
+layout: docs
+---
+
+# Testing
+
+Sentinel has a built-in test framework to validate a policy behaves as expected.
+
+With an ever-increasing amount of automation surrounding technology,
+the guardrails provided by policies are a critical piece towards ensuring
+expected behavior. As a reliance on correct policy increases, it is important
+to test and verify policies.
+
+Testing is a necessary step to fully realize
+[policy as code](/sentinel/concepts/policy-as-code). Just as good software
+is well tested, a good set of policies should be equally well tested.
+
+## Test Folder Structure
+
+Policies are tested by asserting that [rules](/sentinel/writing/rules)
+are the expected values given a pre-configured input. Tests are run
+by executing the [test command](/sentinel/commands/test).
+
+Sentinel is opinionated about the folder structure required for tests.
+This opinionated structure allows testing to be as simple as running
+`sentinel test` with no arguments. Additionally, it becomes simple to test
+in a CI or add new policies.
+
+The structure Sentinel expects is `test//*.[hcl|json]` where ``
+is the name of your policy file without the file extension. Within that folder
+is a list of HCL or JSON files. Each file represents a single test case.
+Therefore, each policy can have multiple tests associated with it.
+
+-> Note that the primary configuration file format for Sentinel is HCL. While
+you can write configuration files in a HCL-equivalent JSON, we only discuss the
+use of HCL on this page.
+
+## Test Case Format
+
+Each HCL file within the test folder for a policy is a single test case.
+
+The file is the same configuration format as the [CLI configuration
+file](/sentinel/configuration). The format lets you define mock data, imports to
+use, and more. This mock data is the key piece in being able to test policies:
+you craft a specific scenario and assert your policy behaves as you expect.
+
+Test cases also use the `test` block within the configuration file to assert the
+value of [rules](/sentinel/writing/rules). If the `test` key is omitted, the
+policy is expected to pass. If the test key is specified, only the rules
+specified in the map will be asserted. This means if you omit `main`, then the
+final policy result is not asserted.
+
+Example with assertions:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+
+test {
+ rules = {
+ main = false
+ is_open_hours = false
+ is_weekday = true
+ }
+}
+```
+
+The configuration above specifies some parameter data, and asserts the result of
+some rules. This is the same configuration used in the example section below.
+
+## Example
+
+Lets use the following file as an example. Save this file to a directory
+and name it `policy.sentinel`. It can be named anything with the `sentinel`
+extension, but by naming it `policy.sentinel` your output should match
+the example output on this page.
+
+```sentinel
+// The day of the week.
+param day
+
+// The hour of the day.
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+### A Passing Test
+
+Next, let's define a single test case. Relative to where you saved
+the policy, create a file at the path `test/policy/good.hcl`.
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 14
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/good.hcl
+```
+
+This passed because the policy passed. We didn't assert any specific
+rules. By not specifying any assertions, `test` expects the policy itself
+to fully pass.
+
+### A Failing Test
+
+Define another test case to fail. We want to verify our policy fails when expected, too.
+
+Save the following as `test/policy/7-am.hcl`:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+FAIL - policy.sentinel
+ FAIL - test/policy/7-am.hcl
+ expected "main" to be true, got: false
+
+ trace:
+ policy.sentinel:9:1 - Rule "main"
+ bool: false
+
+ policy.sentinel:8:1 - Rule "is_open_hours"
+ bool: false
+
+ PASS - test/policy/good.hcl
+```
+
+As you can see, the test fails because "main" is false. This is good
+because the policy _should_ have failed since we specified an invalid
+hour. But, we expect main to be false and don't want our test to fail!
+Update `7-am.hcl` to add test assertions:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+
+test {
+ rules = {
+ main = false
+ is_open_hours = false
+ }
+}
+```
+
+And when we run the tests:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/7-am.hcl
+ PASS - test/policy/good.hcl
+```
+
+The test passes. We asserted that we expect the `main` rule to be false,
+the `is_open_hours` rule to be false, and the `is_weekday` rule to be
+true. By asserting some rules are true, we can verify that our policy
+is failing for reasons we expect.
+
+## Mocking
+
+The above example demonstrates how to test by supplying different
+[parameters](/sentinel/language/parameters). Parameters in a policy can be
+specifically useful when you want to control user-defined input values to a
+policy.
+
+However, generally, when testing, you will need mimic the conditions you will
+see in production. Production implementations of Sentinel will supply data using
+one of two methods:
+
+- **Global data:** Data is injected directly into the policy's scope and is
+ accessible using normal identifiers, similar to variables.
+- **Imports:** Data is stored behind an [import](/sentinel/language/imports)
+ and loaded on demand as needed by the policy author.
+
+Proper testing of a policy requires that these values be able to be _mocked_ -
+or, in other words, simulated in a way that allows the accurate testing of the
+scenarios that a policy could reasonably pass or fail under.
+
+Mocking both globals and imports can be done by setting various parts of the
+configuration file.
+
+### Mocking Globals
+
+Demonstrating the mocking of globals can be seen by making a few modifications to
+our example policy, removing the `param` declarations:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Then, change the `param` section in the configuration file to
+[`global`](/sentinel/configuration#global).
+
+```json
+global "day" {
+ value = "monday"
+}
+
+global "hour" {
+ value = 14
+}
+```
+
+This test should still pass, as if nothing had happened, although what we've
+done is shifted our parameters to globals, simulating an environment where `day`
+and `hour` are already defined for us.
+
+### Mocking Imports
+
+To mock imports, we need to use the
+[`mock`](/sentinel/configuration#mock-imports) section of the
+configuration file.
+
+Let's say the above example is behind an import named `time`.
+
+-> **NOTE:** [`time`](/sentinel/imports/time) is a valid standard import.
+This example may not be accurate to the
+import's syntax.
+
+The code now looks like this:
+
+```sentinel
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To mock this import, we can [mock it as static
+data](/sentinel/configuration#mocking-static-data). The configuration
+file now looks like, without assertions:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ weekday_name = "Monday"
+ hour = 14
+ }
+ }
+}
+```
+
+The policy will now pass, with the `time` import mocked.
+
+Data can also be [mocked as Sentinel
+code](/sentinel/configuration#mocking-with-sentinel-code). In this case,
+the above configuration file would look like:
+
+```hcl
+mock "time" {
+ module {
+ source = "mock-time.sentinel"
+ }
+}
+```
+
+And a file named `mock-time.sentinel` would now hold your mock values:
+
+```sentinel
+day = "monday"
+hour = 14
+```
+
+Mocking as Sentinel code allows more complex details to be mocked as well, such
+as functions. Say we wanted to mock the `time.load()` function. To mock this,
+just add it to the `mock-time.sentinel` file:
+
+```sentinel
+load = func(_) {
+ return {
+ "weekday_name": "Monday",
+ "hour": 14,
+ }
+}
+```
+
+Your code can now be written as:
+
+```sentinel
+import "time"
+
+t = time.load("a_mock_timestamp")
+
+is_weekday = rule { t.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { t.hour > 8 and t.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To see more details, see the [Mock
+Imports](/sentinel/configuration#mock-imports) section in the
+configuration file.
+
+## Asserting Non-Boolean Rules
+
+Non-boolean rules can also be asserted by `sentinel test`.
+
+To assert non-boolean values, simply enter the expected value into the rule
+contents:
+
+```hcl
+param "maintenance_days" {
+ value = [
+ {
+ day = "wednesday"
+ hour = 9
+ },
+ {
+ day = "friday"
+ hour = 1
+ },
+ {
+ day = "sunday"
+ hour = 1
+ },
+ ]
+}
+
+test {
+ rules = {
+ main = [
+ {
+ day = "wednesday"
+ hour = 9
+ },
+ ]
+ }
+}
+```
+
+This would assert a policy checking for violations of a maintenance policy,
+saying that maintenance hours should happen before 6AM on any given day:
+
+```sentinel
+param maintenance_days
+
+main = rule {
+ filter maintenance_days as d {
+ d.hour >= 6
+ }
+}
+```
+
+## Test JSON Output
+
+Running `sentinel test` with the `-json` flag will give you the test results in
+a JSON output format, suitable for parsing by reporting software.
+
+-> **Note:** The JSON output format is currently under development and the
+format may change at a later time. Additionally, support for other well-known
+formats, such as JUnit, may become available in the future.
+
+The current format is an object with the only top-level key being `policies`,
+with each test grouped up by policy being run.
+
+The policy result fields are:
+
+- `path`: The path of the policy and the index of the test result in the
+ `policies` field in the root object.
+- `status`: A string representation of the policy's test status as a whole. Can
+ be one of `PASS`, `FAIL`, `ERROR`, or `?`. The final status, `?`, represents a
+ policy that has no tests to process, and acts like a passing test.
+- `errors`: An array of any error messages encountered during processing the
+ policy for testing. Usually reserved for policy file or parser-related errors.
+ For case-specific errors, see the error field for the particular case.
+- `cases`: A map of case results, indexed by case path.
+
+The case result fields are:
+
+- `path`: The path of the test case and the result's index in the `cases` field
+ in the policy object.
+- `status`: A string representation of the case's test status. Can be one of
+ `PASS`, `FAIL` or `ERROR`.
+- `errors`: An array of any error messages encountered during running this test
+ case.
+- `trace`: The trace for this policy in JSON format. See the
+ [tracing](/sentinel/writing/tracing) page for more details.
+- `rule_detail`: When the `status` of this test case is `FAIL`, contains an
+ object of assertion failure detail, indexed by rule. Only failures are counted
+ here; any rules not found here can be assumed to have passed or not asserted.
+- `config_warnings`: An array of strings denoting any configuration warnings
+ found while processing the configuration for this test case.
+- `config_legacy`: Denotes whether or not the configuration is a legacy JSON
+ configuration and needs to be modernized. This field may be removed in future
+ releases.
+
+A passing example is shown below:
+
+```json
+{
+ "policies": {
+ "policy.sentinel": {
+ "path": "policy.sentinel",
+ "status": "PASS",
+ "errors": null,
+ "cases": {
+ "test/policy/pass.hcl": {
+ "path": "test/policy/pass.hcl",
+ "status": "PASS",
+ "errors": null,
+ "trace": {
+ "description": "A very basic policy to determine if a run is within working hours.",
+ "error": null,
+ "print": "",
+ "result": true,
+ "rules": {
+ "is_open_hours": {
+ "desc": "Passes if run during business hours.",
+ "ident": "is_open_hours",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 290,
+ "line": 13,
+ "column": 1
+ },
+ "value": true
+ },
+ "is_weekday": {
+ "desc": "Passes if the day does not fall on the weekend.",
+ "ident": "is_weekday",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 193,
+ "line": 10,
+ "column": 1
+ },
+ "value": true
+ },
+ "main": {
+ "desc": "",
+ "ident": "main",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 338,
+ "line": 14,
+ "column": 1
+ },
+ "value": true
+ }
+ }
+ },
+ "rule_detail": {},
+ "config_warnings": null,
+ "config_legacy": false
+ }
+ }
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.20.x/content/sentinel/docs/writing/tracing.mdx b/content/sentinel/v0.20.x/content/sentinel/docs/writing/tracing.mdx
new file mode 100644
index 0000000000..f2069bd682
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/docs/writing/tracing.mdx
@@ -0,0 +1,401 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-tracing
+description: >-
+ Sentinel provides execution traces to better understand the execution of a
+ policy.
+layout: docs
+---
+
+# Traces
+
+Sentinel provides execution traces to better understand the execution of a policy.
+
+When using the Sentinel [CLI](/sentinel/commands), traces are shown
+on policy failure during `apply` or `test`, or when the `-trace` flag is
+explicitly specified. For Sentinel-enabled applications, traces are optional
+and may require special configuration to enable.
+
+The availability of the trace may vary from application to application. While
+some applications may only log traces on failure, others, such as [Terraform
+Cloud](https://www.terraform.io/cloud), log the trace on every execution. For
+more details, consult your implementation's documentation.
+
+-> **Note:** We're actively improving Sentinel tracing in each release
+to provide better data and improve readability. The exact format of a
+trace may not match the Sentinel version embedded in the application you're
+using, but the data should be similar. The canonical format for a trace should
+get more stable as we approach a 1.0 release.
+
+## Analyzing a Trace
+
+To show traces, let's use the example policy below with the CLI:
+
+```sentinel
+param day
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+
+main = rule { is_open_hours and is_weekday }
+```
+
+Let's cause the policy to fail so the trace is more interesting.
+If you're on a terminal that supports it, the trace output will be colored
+so you can more clearly see passes, failures, and rules.
+
+
+
+ $ sentinel apply main.sentinel{'\n'}
+
+ main.sentinel:1:7: requires value for parameter day
+
+ {'\n'}
+ {'\n'}
+ {'\n'}
+ {' '}Values can be strings, floats, or JSON array or object values. To force
+ {'\n'}
+ {' '}strings, use quotes.{'\n'}
+ {'\n'}
+ {' '}Enter a value: sunday{'\n'}
+ {'\n'}
+
+ main.sentinel:2:7: requires value for parameter hour
+
+ {'\n'}
+ {'\n'}
+ {'\n'}
+ {' '}Values can be strings, floats, or JSON array or object values. To force
+ {'\n'}
+ {' '}strings, use quotes.{'\n'}
+ {'\n'}
+ {' '}Enter a value: 14{'\n'}
+ {'\n'}
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+
+ Fail - main.sentinel
+
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:7:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}
+ false
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:5:1 - Rule "is_open_hours"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}true{'\n'}
+ {'\n'}
+
+ main.sentinel:4:1 - Rule "is_weekday"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}false{'\n'}
+
+
+
+We can see all of our rules neatly and clearly displayed along with the result
+of the policy. If you are getting the trace due to a failed policy and not
+because you explicitly used `-trace`, an informational message is displayed.
+
+As we can see from our basic example, the `main` rule has failed and its result
+is highlighted in red. All peripheral rules that were also evaluated as part of
+the policy are also displayed below the main rule. Source positions are also
+displayed so that you can find the references to the rules in your code.
+
+Via the trace, we can clearly see that while `is_open_hours` returned a true
+result, `is_weekday` did not, and per the logic in our code, the policy is
+rejected.
+
+## Non-Boolean Rules and the Trace
+
+The trace is particularly important when working with rules that return
+non-boolean data, such as rules where you want to see violations instead of a
+simple opaque boolean value. Let's take our example from the [rules](./rules)
+section, and write a small static policy for it:
+
+```sentinel playground
+days = [
+ {"weekday": "Sunday", "weather": "clouds"},
+ {"weekday": "Monday", "weather": "rain"},
+ {"weekday": "Tuesday", "weather": "sunny"},
+ {"weekday": "Wednesday", "weather": "sunny"},
+ {"weekday": "Thursday", "weather": "clouds"},
+ {"weekday": "Friday", "weather": "rain"},
+ {"weekday": "Saturday", "weather": "sunny"},
+]
+
+main = rule {
+ filter days as d {
+ d.weather is not "sunny"
+ }
+}
+```
+
+Here is the output of our policy:
+
+
+
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+ {'\n'}
+
+ Fail - weather.sentinel
+
+ {'\n'}
+ {'\n'}
+
+ weather.sentinel:11:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+
+ {' [\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Sunday"'}
+ {'\n'}
+ {' "weather": "clouds"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Monday"'}
+ {'\n'}
+ {' "weather": "rain"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Thursday"'}
+ {'\n'}
+ {' "weather": "clouds"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Friday"'}
+ {'\n'}
+ {' "weather": "rain"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' ]'}
+
+
+
+
+We can now see all of the weekdays with non-sunny weather.
+
+Note that to prevent formatting issues and excessive output, the human-readable
+trace is limited in the volume of output it will display for collections.
+
+## JSON Output
+
+The trace can also be displayed in JSON by adding `-json` to `sentinel apply`
+and `sentinel test`, and will display the trace in its entirety, unlike the
+standard CLI output.
+
+Here is the result of running `sentinel apply -json` against our weather policy:
+
+```json
+{
+ "can_override": false,
+ "error": null,
+ "policies": [
+ {
+ "allowed_failure": false,
+ "error": null,
+ "policy": "weather.sentinel",
+ "result": false,
+ "trace": {
+ "description": "",
+ "error": null,
+ "print": "",
+ "result": false,
+ "rules": {
+ "main": {
+ "desc": "",
+ "ident": "main",
+ "position": {
+ "filename": "weather.sentinel",
+ "offset": 328,
+ "line": 11,
+ "column": 1
+ },
+ "value": [
+ {
+ "weather": "clouds",
+ "weekday": "Sunday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Monday"
+ },
+ {
+ "weather": "clouds",
+ "weekday": "Thursday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Friday"
+ }
+ ]
+ }
+ }
+ }
+ }
+ ],
+ "result": false
+}
+```
+
+Note that the trace is always included with this output, regardless of whether
+or not the policy passes or fails; using `-trace` is unnecessary.
+
+Additionally, with `sentinel apply`, you can display the value of a single rule
+in JSON, instead of the entire trace. This is done with the `-json-rule` flag.
+
+-> **Note:** `sentinel apply -json-rule` needs to be run either against a single
+on-disk policy, or against a single policy within a policy set. It is ignored
+in full policy set executions and functions exactly like `-json` in these
+scenarios.
+
+Here is the result of executing `sentinel apply -json-rule main weather.sentinel`:
+
+```json
+[
+ {
+ "weather": "clouds",
+ "weekday": "Sunday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Monday"
+ },
+ {
+ "weather": "clouds",
+ "weekday": "Thursday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Friday"
+ }
+]
+```
+
+## Other Trace Details
+
+There are some other details that are good to know when working with the trace:
+
+- Only rules that get executed are added to the trace. Considering our first
+ example where the `main` rule is the expression `is_open_hours and is_weekday`,
+ if our expression was using an `or` operator instead of `and`, in addition to
+ the policy passing, `is_open_hours` would be present in the trace, but
+ `is_weekday` would not be, as the policy would have never evaluated that rule.
+- Descriptions for both rules and policies will show up in the trace as well if
+ present. Here is the output to our first policy with the appropriate
+ annotations:
+
+
+
+ ...{'\n\n'}
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+
+ Fail - main.sentinel
+
+ {'\n'}
+ {'\n'}
+ Description:{'\n'}
+ {' A very basic policy to determine if a run is within working\n'}
+ {' hours.\n'}
+ {'\n'}
+
+ main.sentinel:7:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}
+ false
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:5:1 - Rule "is_open_hours"
+
+ {'\n'}
+ {' '}
+ Description:
+ {'\n'}
+ {' Passes if run during business hours.\n'}
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}true{'\n'}
+ {'\n'}
+
+ main.sentinel:4:1 - Rule "is_weekday"
+
+ {'\n'}
+ {' '}
+ Description:
+ {'\n'}
+ {' Passes if the day does not fall on the weekend.\n'}
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}false{'\n'}
+
+
diff --git a/content/sentinel/v0.20.x/content/sentinel/intro/getting-started/first-policy.mdx b/content/sentinel/v0.20.x/content/sentinel/intro/getting-started/first-policy.mdx
new file mode 100644
index 0000000000..38d7b22c9a
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/intro/getting-started/first-policy.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Your First Sentinel Policy
+sidebar_current: intro-getting-started-first
+description: Let's begin by writing a working Sentinel policy.
+layout: intro
+---
+
+# Your First Sentinel Policy
+
+Sentinel is a system to enforce complex policies on an integrated application.
+
+Writing Sentinel policy requires minimal programming experience. The Sentinel
+language is designed to be approachable and learned quickly and easily. Whether
+you're a professional programmer or someone who uses SQL and Excel, you can
+learn to write Sentinel policies.
+
+Let's begin by writing a simple, working Sentinel policy:
+
+```sentinel playground
+hour = 4
+main = rule { hour >= 0 and hour < 12 }
+```
+
+This is a valid Sentinel policy. It will pass since we hardcoded the
+`hour` to be 4. In a real system, `hour` may be something that is provided
+to us and actually set to the current hour. We'll learn more about that later.
+
+For now, try running this policy locally. Save the above example to a file
+named `policy.sentinel` and execute it. Then, modify the policy to make it
+fail. Play around more if you'd like.
+
+```
+$ sentinel apply policy.sentinel
+Pass
+```
+
+## Main
+
+Every Sentinel policy must have a `main` rule. This is the rule that
+is evaluated to determine the result of a policy.
+
+A rule describes an expression that generally means one of two things:
+
+- Does a policy _pass a condition_ that would authorize an operation? In our
+ above example, describe a policy that checks the supplied hour (4) is within an
+ authorized time window (between 0 - midnight, and 12 noon).
+- Conversely, can a policy find any _violations_ that would block authorization
+ of the operation? Building on the above, consider a policy that takes a
+ schedule, and finds all time blocks that fall outside of the example time window
+ supplied in the above policy.
+
+It is easy to imagine that such a rule might be used in a system such
+as [Nomad](https://www.nomadproject.io) to restrict the times when a
+deploy can occur. The power of arbitrary logical statements within Sentinel
+allows Sentinel policies to restrict almost any behavior.
+
+Next, we'll
+[introduce and explain rules](/sentinel/intro/getting-started/rules)
+so we can use them in our policies.
diff --git a/content/sentinel/v0.20.x/content/sentinel/intro/getting-started/imports.mdx b/content/sentinel/v0.20.x/content/sentinel/intro/getting-started/imports.mdx
new file mode 100644
index 0000000000..dbbb146ecf
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/intro/getting-started/imports.mdx
@@ -0,0 +1,58 @@
+---
+page_title: Imports
+sidebar_current: intro-getting-started-imports
+description: Imports enable Sentinel to access external data and functions.
+layout: intro
+---
+
+# Imports
+
+Imports enable Sentinel to access external data and functions.
+
+Sentinel ships with a set of [standard imports](/sentinel/imports).
+Standard imports are available by default to every Sentinel policy. Note that
+the application embedding Sentinel may allow or deny the available
+set of imports.
+
+To use an import, use the `import` statement. Then, access data and
+functions on the import using the import name followed by a period and
+the field you want to access. The example below checks whether the request
+path starts with a prefix. Assume that `request_path` is available.
+
+```sentinel
+import "strings"
+
+main = rule { strings.has_prefix(request_path, "/admin") }
+```
+
+## External Data
+
+The true power in imports is their ability to reference external data.
+Imports can be added to a Sentinel-enabled application through
+[plugins](/sentinel/extending). This enables any Sentinel-enabled
+application to make policy decision based on any source of data.
+
+This ability allows the policy system to enforce almost any necessary
+organizational policy, since the ability of a policy isn't restricted
+purely to the embedding application's data model.
+
+For example, policies in [Nomad](https://www.nomadproject.io) can
+access data in [Consul](https://www.consul.io) to determine attributes
+of a policy. In the example below, we use a hypothetical Consul import:
+
+```sentinel
+import "consul"
+
+main = rule {
+ job.tasks[0].resources.memory <= int(consul.get("policy/nomad/max-memory"))
+}
+```
+
+In this example, a Nomad job's memory usage is limited to the value of
+a Consul key/value item. By simply changing a Consul KV entry, policy
+can be changed. Imports can do anything, you can [write your own import plugins](/sentinel/extending)
+to extend Sentinel.
+
+Next, we'll learn how to
+[test policies](/sentinel/intro/getting-started/testing)
+to ensure our policy logic is correct.
diff --git a/content/sentinel/v0.20.x/content/sentinel/intro/getting-started/index.mdx b/content/sentinel/v0.20.x/content/sentinel/intro/getting-started/index.mdx
new file mode 100644
index 0000000000..5c9b6b43c5
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/intro/getting-started/index.mdx
@@ -0,0 +1,16 @@
+---
+page_title: Install Sentinel CLI - Getting Started
+sidebar_current: intro-getting-started-overview
+description: The first step to using Sentinel is to get the CLI installed.
+layout: intro
+---
+
+# Getting Started With Sentinel
+
+This section covers getting started with Sentinel. Here, we will take you
+through the first steps that you will need to do to get started with installing
+the Sentinel CLI, writing your first policy, and getting familiar with rules,
+boolean logic, imports, and testing.
+
+You can select a topic on the menu to get started. The first step is to [install
+the Sentinel CLI](/sentinel/intro/getting-started/install).
diff --git a/content/sentinel/v0.20.x/content/sentinel/intro/getting-started/install.mdx b/content/sentinel/v0.20.x/content/sentinel/intro/getting-started/install.mdx
new file mode 100644
index 0000000000..e478fd532f
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/intro/getting-started/install.mdx
@@ -0,0 +1,56 @@
+---
+page_title: Install Sentinel CLI - Getting Started
+sidebar_current: intro-getting-started-install
+description: The first step to using Sentinel is to get the CLI installed.
+layout: intro
+---
+
+# Install Sentinel CLI
+
+Sentinel is a policy framework that is embedded in the enterprise versions of
+HashiCorp tools. The policies you write are deployed to these applications and
+enforced there.
+
+The Sentinel CLI is a command-line interface for local development and testing.
+For the getting started guide, we'll use the CLI to learn how to write policies
+for Sentinel-enabled applications. The Sentinel CLI is distributed as a [binary
+package](/sentinel/downloads) for all supported platforms and architectures.
+
+## Installation Instructions
+
+To install the Sentinel CLI, find the [appropriate package](/sentinel/downloads) for your
+system and download it. The CLI is packaged as a zip archive.
+
+After downloading Sentinel, unzip the package. The CLI runs as a single binary
+named `sentinel`. Any other files in the package can be safely removed and
+Sentinel will still function.
+
+The final step is to make sure that the `sentinel` binary is available on the `PATH`.
+See [this page](https://stackoverflow.com/questions/14637979/how-to-permanently-set-path-on-linux)
+for instructions on setting the PATH on Linux and Mac.
+[This page](https://stackoverflow.com/questions/1618280/where-can-i-set-path-to-make-exe-on-windows)
+contains instructions for setting the PATH on Windows.
+
+## Verifying the Installation
+
+After installing the Sentinel CLI, verify the installation worked by opening a
+new terminal session and checking that the `sentinel` binary is available. By
+executing `sentinel`, you should see help output similar to the following:
+
+```
+$ sentinel
+Usage: sentinel [--version] [--help] []
+
+Available commands are:
+ apply Execute a policy and output the result
+ fmt Format Sentinel policy to a canonical format
+ test Test policies
+ version Prints the Sentinel version
+```
+
+If you get an error that the binary could not be found, then your `PATH`
+environment variable was not setup properly. Please go back and ensure that your
+`PATH` variable contains the directory where Sentinel was installed.
+
+Otherwise, the CLI is installed and ready to go. Let's celebrate by [writing our
+first policy!](/sentinel/intro/getting-started/first-policy)
diff --git a/content/sentinel/v0.20.x/content/sentinel/intro/getting-started/logic.mdx b/content/sentinel/v0.20.x/content/sentinel/intro/getting-started/logic.mdx
new file mode 100644
index 0000000000..d3716a64ea
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/intro/getting-started/logic.mdx
@@ -0,0 +1,54 @@
+---
+page_title: Logic
+sidebar_current: intro-getting-started-logic
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Logic
+
+The core of a policy is a set of logical expressions to determine whether
+a behavior is allowed or not.
+Sentinel makes it easy to write readable logical expressions to express
+the intended policy.
+
+The logical expression syntax will be familiar to anyone who has programmed
+before. Sentinel also supports a couple more unique constructs to assist
+with common policy requirements. Detailed documentation on how to write
+logical expressions and the supported operators is available in
+[the boolean expression reference](/sentinel/language/boolexpr).
+
+Example logic:
+
+```sentinel
+a is b // Equality, you can also use ==
+a is not b // Inequality, you can also use !=
+
+a > b // Relational operators, comparison
+a < b
+a >= b
+a <= b
+
+a contains b // Inclusion check, substrings, etc.
+a not contains b // Negated version of above
+```
+
+The full list of available operators can be found in
+[the boolean expression reference](/sentinel/language/boolexpr).
+
+Logic can be combined with `and` or `or`. When combined with `and`, both
+sides must result in `true`. When combined with `or`, only one side needs
+to be true to result in `true`.
+
+With these building blocks, basic rules can be built up:
+
+```sentinel
+main = rule {
+ hour >= 8 and
+ hour < 17
+}
+```
+
+Next, we'll introduce
+[imports](/sentinel/intro/getting-started/imports)
+so that we can write rules that interact with external data.
diff --git a/content/sentinel/v0.20.x/content/sentinel/intro/getting-started/next-steps.mdx b/content/sentinel/v0.20.x/content/sentinel/intro/getting-started/next-steps.mdx
new file mode 100644
index 0000000000..23b470be59
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/intro/getting-started/next-steps.mdx
@@ -0,0 +1,21 @@
+---
+page_title: Next Steps
+sidebar_current: intro-getting-started-nextsteps
+description: That concludes the getting started guide for Sentinel.
+layout: intro
+---
+
+# Next Steps
+
+That concludes the getting started guide for Sentinel. Hopefully you're excited
+about the possibilities of Sentinel and ready to put this knowledge to use to
+improve the safety of your environments.
+
+We've covered the basics of the core features of Sentinel in this guide.
+We recommend the following as next steps:
+
+- [Documentation](/sentinel) - The documentation is an in-depth
+ reference guide of all the features of Sentinel.
+
+- [Language Reference](/sentinel/language) - The language reference
+ is a complete reference to the features of the Sentine policy language.
diff --git a/content/sentinel/v0.20.x/content/sentinel/intro/getting-started/rules.mdx b/content/sentinel/v0.20.x/content/sentinel/intro/getting-started/rules.mdx
new file mode 100644
index 0000000000..f12a61fe75
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/intro/getting-started/rules.mdx
@@ -0,0 +1,86 @@
+---
+page_title: Rules
+sidebar_current: intro-getting-started-rules
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Rules
+
+Rules in Sentinel are a first-class concept. Within a policy, rules serve a few
+purposes:
+
+- They make complex logic more understandable by allowing said logic to be
+ broken down.
+- They allow assertion of this logic through
+ [testing](/sentinel/intro/getting-started/testing) of the rule's contents.
+- They facilitate reporting of data as rules get published as part of the [policy
+ trace](/sentinel/writing/tracing).
+
+A rule functions in ways similar to both a variable and a function: they hold a
+value, but are lazily evaluated; a rule's value is not assigned until the first
+time it's referenced in a policy. Additionally, while the value of evaluated
+rules will be available within a policy's trace after it's evaluated, values of
+variables - and the return value of functions - are not. Finally, a rule value
+is memoized - further references to the rule will not change its result.
+
+Rules can hold more than just boolean data. For more advanced rule patterns, see
+[the language reference](/sentinel/language/rules).
+
+## Writing Rules
+
+Let's look at the Sentinel policy we wrote in the previous section:
+
+```sentinel playground
+hour = 4
+main = rule { hour >= 0 and hour < 12 }
+```
+
+The logic in this policy is straightforward and easy to understand. However,
+it is common for policy logic to become much more complicated. Rules are the
+key abstraction for making complex logic understandable. We can turn the
+policy above into a set of rules:
+
+```sentinel playground
+hour = 4
+
+past_midnight = rule { hour >= 0 }
+before_noon = rule { hour < 12 }
+
+main = rule {
+ past_midnight and
+ before_noon
+}
+```
+
+This policy is easier to read. Our original policy was already quite
+simple, but it is easy to imagine a more complex policy becoming much
+more readable by extracting logical expressions into a set of rules.
+
+Rules don't just exist to make a policy more readable. They're also a
+testing and debugging point. For testing, you can assert that certain rules
+are certain values given a specific input to verify that your policy works
+as you expect. Testing and debugging are covered in later sections.
+
+## Conditional Rules
+
+In many cases, a rule is only valid when something else is also true.
+
+Consider the human language statement "before you eat, you must wash your hands."
+The rule "you must wash your hands" is only valid "before you eat". In any
+other scenario, the rule is meaningless.
+This is also true in a technical context. Imagine the rule "if the driver
+is Docker, you must not expose any ports." For this example, assume
+rules `is_docker` and `has_no_exposed_ports` exist. You can write this rule
+like this:
+
+```sentinel
+main = rule when is_docker { has_no_exposed_ports }
+```
+
+When `is_docker` is true, the rule is evaluated as normal. However,
+when `is_docker` is false, the rule always returns `true`, because the
+logic of the rule is meaningless.
+
+Let's learn how to create more complex rules with
+[logical expressions](/sentinel/intro/getting-started/logic).
diff --git a/content/sentinel/v0.20.x/content/sentinel/intro/getting-started/testing.mdx b/content/sentinel/v0.20.x/content/sentinel/intro/getting-started/testing.mdx
new file mode 100644
index 0000000000..7149a7966f
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/intro/getting-started/testing.mdx
@@ -0,0 +1,72 @@
+---
+page_title: Testing
+sidebar_current: intro-getting-started-testing
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Testing
+
+It is important to ensure the policies you write are correct. Sentinel
+includes a built-in test framework that can be run locally and in CI
+environments to test that policies behave as expected.
+
+A common pitfall with many simple ACL systems is that they provide no easy
+way to verify their correctness. You basically have to set the ACL and try
+the behaviors against a real system to verify it is working as expected.
+This requires a lot of setup and is unique to each system.
+
+[Sentinel's built-in test framework](/sentinel/writing/testing) has zero
+dependencies. Contained within the [Sentinel CLI](/sentinel/commands), it can
+mock the data that real systems are exposing to the policy. It is designed to be
+CI-friendly and enables continuous testing of your policies. This is necessary
+for [policy as code](/sentinel/concepts/policy-as-code).
+
+Detailed documentation on testing policies is available in [the Sentinel Testing
+reference](/sentinel/writing/testing).
+
+## Writing Tests
+
+For this example, save the following policy as `policy.sentinel`:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Next, let's write a passing test case. This will test that the policy tests
+when we expect it to pass. Save the following in `test/policy/good.hcl`:
+
+```hcl
+global "day" {
+ value = "monday"
+}
+
+global "hour" {
+ value = 14
+}
+```
+
+And run `sentinel test`:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/good.json
+```
+
+The `sentinel test` command will automatically find all Sentinel policies
+and their associated test cases and run them all. The test framework will
+run all HCL files as individual test cases, allowing you to test a variety
+of scenarios for your policies.
+
+Try adding another test case to force the policy to fail. This test case can
+be saved at `test/policy/fail.hcl`. For test cases with intentional
+failures, you'll need to use the [test assertions described in the testing
+reference](/sentinel/writing/testing#a-failing-test).
+
+Now that you have a good grasp of what Sentinel is and how to use it, please feel
+free to look through the
+[next steps](/sentinel/intro/getting-started/next-steps)
+you can take to further your Sentinel knowledge.
diff --git a/content/sentinel/v0.20.x/content/sentinel/intro/index.mdx b/content/sentinel/v0.20.x/content/sentinel/intro/index.mdx
new file mode 100644
index 0000000000..785a442fb8
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/intro/index.mdx
@@ -0,0 +1,32 @@
+---
+layout: intro
+page_title: Documentation
+sidebar_current: docs-home
+description: >-
+ Sentinel is a language and framework for policy built to be embedded in
+ existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+[introduction](/sentinel/intro) to understand what Sentinel is, how it
+compares to other software, and more.
+
+
diff --git a/content/sentinel/v0.20.x/content/sentinel/intro/what.mdx b/content/sentinel/v0.20.x/content/sentinel/intro/what.mdx
new file mode 100644
index 0000000000..36853d366c
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/intro/what.mdx
@@ -0,0 +1,64 @@
+---
+page_title: Introduction
+sidebar_current: intro-what
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: intro
+---
+
+# Introduction to Sentinel
+
+Welcome to the intro guide to Sentinel! This guide is the best
+place to start with Sentinel.
+
+If you are already familiar with the basics of Sentinel, the
+[documentation](/sentinel) provides a better reference
+guide for all available features as well as internals.
+
+## What is Sentinel?
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+Sentinel provides a language for writing policy and a workflow for developing
+and testing policies independent of the software they'll be written to. We
+also provide a framework for developers to build plugins that allow a
+Sentinel-enabled system to access external information to make policy
+decisions.
+
+Most systems today have some degree of access control. You are able to define
+identities and what they have access to. These ACL systems solve an immediate
+and necessary problem of locking down a system in very broad strokes. Sentinel
+is a reusable system for more advanced software policy decisions. Sentinel
+enables:
+
+- **Fine-Grained Policy**: Most ACL systems only enable coarse-grained
+ behaviors: "read", "write", etc. Sentinel enables fine-grained behavior such
+ as disallowing a certain API call when specific parameters are present.
+
+- **Logic-Based Policy**: You can write policy using full
+ conditional logic. For example, you may only allow a certain application behavior
+ on Monday to Thursday unless there is a manager override.
+
+- **Accessing External Information**: Sentinel can source external information
+ to be used in policy decisions. For example, a policy that restricts the size
+ of a payload may read data from [Consul](https://www.consul.io) to determine
+ the payload size limit.
+
+- **Enforcement levels**: Sentinel allows policies to be defined along
+ with an "enforcement level" that dictates the pass/fail behavior of a policy.
+ Advisory policies warn if they fail, soft mandatory policies can have their
+ failures overridden, and hard mandatory policies must pass under all
+ circumstances. Having this as a built-in concept enables you to model
+ policy more accurately and completely for your organization.
+
+## Next Steps
+
+See the page on [Why Sentinel?](/sentinel/intro/why) to learn more
+about the origins of the product. Then, continue onwards with
+the [getting started guide](/sentinel/intro/getting-started/install) to write
+your first policies and see how Sentinel works in practice.
diff --git a/content/sentinel/v0.20.x/content/sentinel/intro/why.mdx b/content/sentinel/v0.20.x/content/sentinel/intro/why.mdx
new file mode 100644
index 0000000000..98b58aab92
--- /dev/null
+++ b/content/sentinel/v0.20.x/content/sentinel/intro/why.mdx
@@ -0,0 +1,49 @@
+---
+page_title: Why Sentinel?
+sidebar_current: intro-why
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: intro
+---
+
+# Why Sentinel?
+
+The growth of infrastructure and applications has been enabled in part
+by an increasing trend towards automation everywhere.
+Configuration as code (such as Chef or Puppet with [Packer](https://www.packer.io))
+has enabled automated machine configuration. Infrastructure
+as code (such as [Terraform](https://www.terraform.io)) has enabled
+automatic infrastructure creation. And schedulers (such as
+[Nomad](https://www.nomadproject.io) and Kubernetes) have enabled
+automatic application deployment.
+Sentinel enables guardrails to be put in place
+on automation while allowing the codification and automatic enforcement
+of business requirements in critical areas of your infrastructure.
+
+Meanwhile, businesses have business requirements and sometimes legal
+requirements which must be expressed in policies. Traditionally, these
+policies are enforced by humans. But in a highly automated world, the
+automation is only as fast as its slowest component. In many cases, this
+is the human verification step.
+
+As an example: before infrastructure as code and autoscaling, if an order
+came through for 5,000 new machines, a human would likely respond to the
+ticket verifying that the user really intended to order 5,000 new machines.
+Today, automation can almost always freely order 5,000 new compute instances
+without any hesitation, which can result in unintended expense or system
+instability.
+
+Sentinel introduces [policy as code](/sentinel/concepts/policy-as-code)
+and a powerful framework built-in to HashiCorp tooling to allow automation
+guardrails, business requirements, legal compliance, and more to be
+actively enforced by the running systems in realtime.
+
+With Sentinel, you can require an override to create certain numbers of
+infrastructure resources. You can disallow unsafe deployment configurations
+with Nomad. You can enforce certain key/value formats in Consul. You
+can restrict secret access by time in Vault. And more.
+
+Sentinel is available today in HashiCorp enterprise products. You can
+try Sentinel immediately with the [getting started guide](/sentinel/intro/getting-started/install) or learn more about the integrations in the
+[documentation](/sentinel).
diff --git a/content/sentinel/v0.20.x/data/docs-nav-data.json b/content/sentinel/v0.20.x/data/docs-nav-data.json
new file mode 100644
index 0000000000..1e281f044a
--- /dev/null
+++ b/content/sentinel/v0.20.x/data/docs-nav-data.json
@@ -0,0 +1,324 @@
+[
+ {
+ "title": "Release Notes",
+ "path": "changelog"
+ },
+ {
+ "title": "Basic Concepts",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "concepts"
+ },
+ {
+ "title": "Policy as Code",
+ "path": "concepts/policy-as-code"
+ },
+ {
+ "title": "Policy Language",
+ "path": "concepts/language"
+ },
+ {
+ "title": "Imports",
+ "path": "concepts/imports"
+ },
+ {
+ "title": "Enforcement Levels",
+ "path": "concepts/enforcement-levels"
+ }
+ ]
+ },
+ {
+ "title": "Configuration File Syntax",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "configuration"
+ },
+ {
+ "title": "Override Files",
+ "path": "configuration/overrides"
+ },
+ {
+ "title": "Remote Sources",
+ "path": "configuration/remote-sources"
+ }
+ ]
+ },
+ {
+ "title": "Commands (CLI)",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "commands"
+ },
+ {
+ "title": "apply",
+ "path": "commands/apply"
+ },
+ {
+ "title": "fmt",
+ "path": "commands/fmt"
+ },
+ {
+ "title": "test",
+ "path": "commands/test"
+ }
+ ]
+ },
+ {
+ "title": "Writing Policy",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "writing"
+ },
+ {
+ "title": "Basics",
+ "path": "writing/basic"
+ },
+ {
+ "title": "Rules",
+ "path": "writing/rules"
+ },
+ {
+ "title": "Traces",
+ "path": "writing/tracing"
+ },
+ {
+ "title": "Testing",
+ "path": "writing/testing"
+ },
+ {
+ "title": "Imports",
+ "path": "writing/imports"
+ },
+ {
+ "title": "Debugging",
+ "path": "writing/debugging"
+ }
+ ]
+ },
+ {
+ "title": "Extending Sentinel",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "extending"
+ },
+ {
+ "title": "Modules",
+ "path": "extending/modules"
+ },
+ {
+ "title": "Plugins",
+ "path": "extending/plugins"
+ },
+ {
+ "title": "Static Imports",
+ "path": "extending/static-imports"
+ },
+ {
+ "title": "Internals",
+ "path": "extending/internals"
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Language",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "language"
+ },
+ {
+ "title": "Variables",
+ "path": "language/variables"
+ },
+ {
+ "title": "Values",
+ "path": "language/values"
+ },
+ {
+ "title": "Lists",
+ "path": "language/lists"
+ },
+ {
+ "title": "Maps",
+ "path": "language/maps"
+ },
+ {
+ "title": "Rules",
+ "path": "language/rules"
+ },
+ {
+ "title": "Imports",
+ "path": "language/imports"
+ },
+ {
+ "title": "Parameters",
+ "path": "language/parameters"
+ },
+ {
+ "title": "Boolean Expressions",
+ "path": "language/boolexpr"
+ },
+ {
+ "title": "Arithmetic",
+ "path": "language/arithmetic"
+ },
+ {
+ "title": "Slices",
+ "path": "language/slices"
+ },
+ {
+ "title": "Conditionals",
+ "path": "language/conditionals"
+ },
+ {
+ "title": "Loops",
+ "path": "language/loops"
+ },
+ {
+ "title": "Collection Operations",
+ "path": "language/collection-operations"
+ },
+ {
+ "title": "Functions",
+ "path": "language/functions"
+ },
+ {
+ "title": "Scope",
+ "path": "language/scope"
+ },
+ {
+ "title": "Undefined",
+ "path": "language/undefined"
+ },
+ {
+ "title": "Logging and Errors",
+ "path": "language/logging-errors"
+ },
+ {
+ "title": "Specification",
+ "path": "language/spec"
+ }
+ ]
+ },
+ {
+ "title": "Builtin Functions",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "functions"
+ },
+ {
+ "title": "append",
+ "path": "functions/append"
+ },
+ {
+ "title": "delete",
+ "path": "functions/delete"
+ },
+ {
+ "title": "error",
+ "path": "functions/error"
+ },
+ {
+ "title": "keys",
+ "path": "functions/keys"
+ },
+ {
+ "title": "length",
+ "path": "functions/length"
+ },
+ {
+ "title": "print",
+ "path": "functions/print"
+ },
+ {
+ "title": "range",
+ "path": "functions/range"
+ },
+ {
+ "title": "values",
+ "path": "functions/values"
+ }
+ ]
+ },
+ {
+ "title": "Standard Imports",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "imports"
+ },
+ {
+ "title": "base64",
+ "path": "imports/base64"
+ },
+ {
+ "title": "decimal",
+ "path": "imports/decimal"
+ },
+ {
+ "title": "http",
+ "path": "imports/http"
+ },
+ {
+ "title": "json",
+ "path": "imports/json"
+ },
+ {
+ "title": "runtime",
+ "path": "imports/runtime"
+ },
+ {
+ "title": "sockaddr",
+ "path": "imports/sockaddr"
+ },
+ {
+ "title": "strings",
+ "path": "imports/strings"
+ },
+ {
+ "title": "time",
+ "path": "imports/time"
+ },
+ {
+ "title": "types",
+ "path": "imports/types"
+ },
+ {
+ "title": "units",
+ "path": "imports/units"
+ },
+ {
+ "title": "version",
+ "path": "imports/version"
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Consul",
+ "path": "consul"
+ },
+ {
+ "title": "Nomad",
+ "path": "nomad"
+ },
+ {
+ "title": "Terraform",
+ "path": "terraform"
+ },
+ {
+ "title": "Vault",
+ "path": "vault"
+ }
+]
diff --git a/content/sentinel/v0.20.x/data/intro-nav-data.json b/content/sentinel/v0.20.x/data/intro-nav-data.json
new file mode 100644
index 0000000000..c95181af70
--- /dev/null
+++ b/content/sentinel/v0.20.x/data/intro-nav-data.json
@@ -0,0 +1,47 @@
+[
+ {
+ "title": "What is Sentinel?",
+ "path": "what"
+ },
+ {
+ "title": "Why Sentinel?",
+ "path": "why"
+ },
+ {
+ "title": "Getting Started",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "getting-started"
+ },
+ {
+ "title": "Installing the CLI",
+ "path": "getting-started/install"
+ },
+ {
+ "title": "Your First Policy",
+ "path": "getting-started/first-policy"
+ },
+ {
+ "title": "Rules",
+ "path": "getting-started/rules"
+ },
+ {
+ "title": "Logic",
+ "path": "getting-started/logic"
+ },
+ {
+ "title": "Imports",
+ "path": "getting-started/imports"
+ },
+ {
+ "title": "Testing",
+ "path": "getting-started/testing"
+ },
+ {
+ "title": "Next Steps",
+ "path": "getting-started/next-steps"
+ }
+ ]
+ }
+]
diff --git a/content/sentinel/v0.20.x/img/sentinel-import-topology.svg b/content/sentinel/v0.20.x/img/sentinel-import-topology.svg
new file mode 100644
index 0000000000..36f7c79bc9
--- /dev/null
+++ b/content/sentinel/v0.20.x/img/sentinel-import-topology.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/content/sentinel/v0.20.x/redirects.jsonc b/content/sentinel/v0.20.x/redirects.jsonc
new file mode 100644
index 0000000000..d9e73f4a49
--- /dev/null
+++ b/content/sentinel/v0.20.x/redirects.jsonc
@@ -0,0 +1,15 @@
+[
+ {
+ "source": "/",
+ "destination": "/sentinel",
+ "permanent": true,
+ },
+ {
+ "source": "/sentinel/commands/config",
+ "destination": "/sentinel/configuration",
+ "permanent": true,
+ },
+ // disallow ".html" or "/index.html" in favor of cleaner, simpler paths
+ { "source": "/:path*/index", "destination": "/:path*", "permanent": true },
+ { "source": "/:path*.html", "destination": "/:path*", "permanent": true },
+]
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/changelog.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/changelog.mdx
new file mode 100644
index 0000000000..60c27d62fa
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/changelog.mdx
@@ -0,0 +1,778 @@
+---
+page_title: Sentinel Runtime Release Notes
+sidebar_current: docs-changelog
+description: >-
+ This are the public release notes for the Sentinel runtime. See this document
+ for the latest updates.
+layout: docs
+---
+
+# Sentinel Runtime Release Notes
+
+These are the release notes for the Sentinel runtime.
+
+Each version of the runtime is released with a corresponding version of
+[Sentinel CLI](/sentinel/commands). To download the CLI, see the [downloads
+page](/sentinel/downloads).
+
+Sentinel integrations and embedded runtimes may not always have the latest
+version installed, depending on the product's individual release cycle. For more
+information, contact the support team for your specific integration.
+
+
+## 0.21.1 (May 8, 2023)
+
+BUG FIXES:
+
+- `config`: An issue with certain identifiers being treated as incorrectly invalid
+ has been resolved.
+
+## 0.21.0 (March 8, 2023)
+
+BREAKING CHANGES:
+
+- `lang/ast`: `is empty` and `is not empty` are now treated as `ast.UnaryExpr`
+ expressions, with `ast.IsEmptyExpr` being removed.
+- `lang/ast`: You can now assert if a value is defined or not using the `is defined`
+ and `is not defined` syntax.
+
+FEATURES:
+
+- `config`: Parameter values can now be provided for individual policies within
+ a policy block.
+
+## 0.20.0 (February 16, 2023)
+
+ENHANCEMENTS:
+
+- `cmd/testcmd`: Allows sentinel tests to run concurrently by default. Sequential style testing can
+ be enabled by running with the `-maxConcurrency=1` option.
+- `cmd/testcmd`: Allows sentinel test command to timeout after a certain duration. This can be provided
+ by the user or will default to 5 minutes.
+- `cmd/apply`: The policy enforcement level is now included in the JSON output.
+- `lang/ast`: Functions can now be declared as named statements, providing
+ a safer function declaration.
+
+BREAKING CHANGES:
+
+- `cmd/apply`: Policies provided directly to the apply command will now default their enforcement
+ level to `advisory`, aligning with the `policy` configuration block.
+- `sentinel`: JSON results will no longer return `allowed_failure` or `can_override` fields.
+- `sentinel/result`: A new package has been added which provides additional methods to return
+ supplemental data about the evaluation result.
+
+## 0.19.5 (February 9, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `SelectorExpr` will correctly rewrite `X`.
+
+## 0.19.4 (February 8, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `Compile` function will correctly set the `Original`
+ field when rewriting a `CallExpr` as an `ImportExpr`.
+
+## 0.19.3 (February 3, 2023)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.19.2 (January 17, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `Compile` function will no longer modify `ast.CallExpr`
+ arguments in place.
+
+## 0.19.1 (December 14, 2022)
+
+BUG FIXES:
+
+- `lang/ast`: The `Rewrite` function will no longer modify the provided Node
+ in place.
+
+ENHANCEMENTS:
+
+- `lang/semantic`: The Break semantic check uses the AST walker.
+
+## 0.19.0 (December 6, 2022)
+
+BREAKING CHANGES:
+
+- `config`: Attempts to override a standard import with a custom import will
+ now be met with an error.
+- `imports`: All plugin imports now return `sdk.Plugin` instead of `sdk.Import`,
+ as per the SDK update to v0.4.0.
+
+ENHANCEMENTS:
+
+- `runtime/importer`: An `Import` and `ImportInstance` interface have been
+ added to improve the import capabilities.
+- `runtime/importer`: The `Importer` interface now returns a `Import` interface
+ instead of a `sdk.Import`.
+- `lang/semantic`: The Import Assignment semantic check uses the AST walker.
+- `lang/ast`: Added an AST Walker so the AST rewriter does not need to be used for basic AST tasks.
+
+FEATURES:
+
+- `config`: Configuration syntax now allows for configuration of imports within
+ the standard library.
+- `config`: A new configuration syntax for defining modules and plugins is now
+ available.
+- Static data can now be added via `import` configuration.
+- `cmd/apply`: The `apply` command now supports multiple primary configuration
+ files by default, loading all configuration files within the working
+ directory.
+- `cmd/apply`: The `apply` command now accepts applying configuration overrides
+ to primary configuration.
+
+## 0.18.13 (October 31, 2022)
+
+BUG FIXES:
+
+- `cmd/apply`: `sentinel apply` no longer aborts on advisory policy failure.
+
+## 0.18.12 (September 15, 2022)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where nested `CallExpr` were not rewritten.
+
+## 0.18.11 (June 8, 2022)
+
+NOTES:
+
+- `config`: The `enforcement_level` key for policies is now optional, defaulting to `"advisory"` when not supplied.
+
+## 0.18.10 (May 20, 2022)
+
+NOTES:
+
+- `runtime/initwd`: Improvements to the installer, including timeouts and filesize limits, as well as disabling support
+ for symlinks within `git` or `hg` repositories.
+
+## 0.18.9 (March 23, 2022)
+
+NOTES:
+
+- This release fixes an issue with the Docker container build pipeline. There are no changes to the
+Sentinel runtime or CLI.
+
+## 0.18.8 (March 22, 2022)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue in the installer for Windows paths.
+
+## 0.18.7 (February 24, 2022)
+
+NOTES:
+
+- **Darwin arm64**. This release will be our first to include Darwin arm64 architecture.
+
+BUG FIXES:
+
+- `lang/ast`: Fixed issues where `IndexSpec` and `RuleLit` nodes where returning incorrect end positions.
+- `lang/ast`: Fixed an issue where `ParamSpec` nodes where returning incorrect end positions.
+
+## 0.18.6 (February 2, 2022)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.18.5 (January 14, 2022)
+
+IMPROVEMENTS:
+
+- `runtime/initwd`: Changed the sum algorithm used during remote installation from `md5` to `sha256` to reduce chance of collision.
+
+## 0.18.4 (July 20, 2021)
+
+FEATURES:
+
+- `imports/static`: Improved handling of struct tags when performing encoding, allowing `-` to mark as ignored.
+
+## 0.18.3 (June 1, 2021)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue for remote source installation where duplicate sub-directories resulted in incorrect installation.
+- `imports/static`: Fixed an issue where global configuration that contained an empty map could not be accessed without raising selector issues.
+
+## 0.18.2 (May 18, 2021)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where import expressions inside lists and maps were not being evaluated correctly.
+
+## 0.18.1 (May 11, 2021)
+
+BUG FIXES:
+
+- `imports/json`: Fixed an issue where empty maps where failing when passed at any level to `marshal`.
+
+## 0.18.0 (March 25, 2021)
+
+FEATURES:
+
+- `imports/http`: Added support for POST actions within the `http` import
+ through addition of the `post` function. Additionally, support for adding
+ a body to the `request` object has been added via `with_body`.
+
+## 0.17.4 (February 2, 2021)
+
+BUG FIXES:
+
+- `runtime/format`: Fixed a nesting issue with the rule printer where nesting
+ state leaks were leading to all collection values eventually being truncated,
+ regardless of their nesting level.
+
+## 0.17.3 (January 15, 2021)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue where local file installation failed on Windows
+ when using `sentinel test`.
+
+## 0.17.2 (January 13, 2021)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where duplicate log messages were being printed with
+ `sentinel test`.
+
+## 0.17.1 (January 7, 2021)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where hard-coded path separator caused `sentinel
+ test` issues in Windows.
+
+## 0.17.0 (January 5, 2021)
+
+LANGUAGE CHANGES:
+
+- **Map Expression** `map`. A new quantifier expression, `map` has been added.
+ `map` takes a collection and applies an operation to each element in the
+ collection, returning a list of results with the resulting values.
+- **Emptiness checks** `is empty` and `is not empty`. Two new expressions,
+ `is empty` and `is not empty` have been added. As they are aliases to the
+ `length` built-in, only `string`, `map`, `list` or `undefined` is
+ supported.
+- **Comparable maps** Maps (the data type) are now comparable. Maps are equal if
+ they are of equal length and both their corresponding keys and values are
+ comparable and equal.
+- **Rich Return Types** Rules can now process and return non-boolean data.
+ Scalar, list, and map types are supported. When non-boolean values are used,
+ the presence of a non-zero or non-zero-length `main` rule determines policy
+ failure. More details can be found on
+ https://docs.hashicorp.com/sentinel/language#main.
+
+FEATURES:
+
+- **CLI**: `sentinel apply` and `sentinel test` now have options for JSON
+ output. For more details, see the CLI documentation.
+- `imports/base64`: Added a new import, `base64`, to assist with encoding and
+ decoding base64 strings.
+
+## 0.16.1 (October 21, 2020)
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where `contains` to ensure any instance of `undefined` would correctly result in `undefined`.
+- `runtime/eval`: Fixed an issue in `any` and `all` expressions to ensure correct boolean logic when dealing with a collection containing `undefined`.
+- `imports`: Fixed an issue where import processes were not killed, which caused non-graceful closures for the clients.
+- `lang/scanner`: Fixed an issue where inline `#` style comments were not being consumed.
+
+## 0.16.0 (October 14, 2020)
+
+BREAKING CHANGES:
+
+- `cmd/doc`: Removal of the deprecated `sentinel doc` command.
+- `sentinel`: Replace `whitelist` and `blacklist` with `allowlist` and `denylist`, as per inclusive language changes.
+
+FEATURES:
+
+- `imports/version`: Added a new import, `version`, which supplies helpers for dealing with semantic versioning.
+- `cmd/apply`: Added an `HCL` based configuration file, which will eventually deprecate the legacy `JSON` configuration.
+- `cmd/apply`: Added support for download remote policies and modules.
+- `cmd/test`: Added support for downloading remote test modules.
+- `cmd/apply`: Added support for evaluating a policy based on it's key within the configuration.
+- `cmd/apply`: Added support for running all policies in a configuration by default.
+
+## 0.15.6 (July 7, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.15.5 (May 20, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel integrations. There are no user-facing changes.
+
+## 0.15.4 (May 13, 2020)
+
+BUG FIXES:
+
+- `imports`: Fixed an issue with the standard imports that was affecting the handling of null data within lists.
+
+## 0.15.3 (April 16, 2020)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where `IndexExpr` were not rewritten, as well as ensuring `SelectorExpr` uses any nested rewrites.
+- `lang/printer`: Fixed issue printing deeply nested structures and/or loops.
+
+IMPROVEMENTS:
+
+- `imports/decimal`: Added alias methods to improve consistency of method names within the `decimal` import. The alias methods are `is_nan` for `isnan`, as well as both `is_inf` and `is_infinite` for `isinfinite`.
+- `command/apply`: `sentinel apply` will now output the policy description when a trace is forced via `-trace`.
+- `sentinel`: Improved `String` output formatting of Policy docstring for `EvalPolicyResult`.
+
+## 0.15.2 (April 2, 2020)
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue with the `printer` that was causing a panic
+ when a `#` line comment was used with no text following it.
+- `imports/types`: Fixed an issue with the `types` import where calls to
+ `type_of` for undefined types were incorrectly returning as map types. These
+ will now be correctly be identified as undefined as expected.
+
+## 0.15.1 (March 6, 2020)
+
+BUG FIXES:
+
+- `sentinel`: Fixed how modules are managed internally in the runtime to correct
+ race conditions in concurrent scenarios and to allow for per-evaluation module
+ restrictions. This functionality is only of interest to embedded applications
+ with long-lived runtimes and is currently not implemented in any HashiCorp
+ integration.
+
+## 0.15.0 (March 5, 2020)
+
+NOTES:
+
+- **Windows Digital Signature**. Our windows releases for this version and up will be signed
+ and verified according to Microsoft's requirements.
+
+FEATURES:
+
+- `cmd/config`: Modules can now be loaded off of local disk using the Sentinel
+ CLI. For information on how to do so, see the Sentinel documentation.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: Changed modules so that they now have singleton scope when
+ loaded. Importing a module under two different aliases, or within a nested
+ module, will now share scopes - modification of state in one will alter the
+ other.
+- `lang/object`: Introduced an interface to allow the duplication of various
+ types, like collections.
+- `runtime/eval`: Changed how collection data is returned from modules. This
+ data is now duplicated to prevent accidental or deliberate circumvention of
+ the prohibition on assignment to import data, and brings behavior to parity
+ with binary imports.
+
+## 0.14.4 (February 6, 2020)
+
+IMPROVEMENTS:
+
+- `imports/time`: Changed the `add` timespace function in the `time` import to
+ allow it to take float types as durations. These values will be truncated to
+ the appropriate integer-value duration.
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where out-of-place right-hand braces were being
+ parsed as empty statements, instead of raising syntax errors.
+
+## 0.14.3 (January 22, 2020)
+
+IMPROVEMENTS:
+
+- `cmd/test`: Fail when an assertion is not found in a trace.
+- `cmd/test`: Added a message to notify when no rules were evaluated in a test.
+
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue where import aliases (the `as baz` in `import
+ "foo/bar" as baz`) were being removed when formatting with `sentinel fmt`.
+
+- `imports/http`: Fixed an issue with the http import where the client library's
+ debug logs were being printed to standard error.
+
+## 0.14.2 (January 15, 2020)
+
+NOTES:
+
+With this release, we are discontinuing support for MacOS 32-bit. 64-bit builds
+are now the only builds available for MacOS.
+
+IMPROVEMENTS:
+
+- `lang/printer`: A newline is now added to files formatted via `sentinel fmt`.
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue in printing where multi-line expressions would
+ indent infinitely. They will now only indent once at the most. This is mostly
+ seen when using `sentinel fmt`.
+- `runtime/encoding`: Fixed an issue where if a plugin returned a deeply
+ embedded undefined value, the value was instead decoded into a map.
+
+## 0.14.1 (January 6, 2020)
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where reserved words could not be used as
+ selectors when the selector expression was the last lexeme on a line.
+
+## 0.14.0 (December 18, 2019)
+
+LANGUAGE CHANGES:
+
+- **Filter Expression** `filter`. A new quantifier expression, `filter` has been added.
+ `filter` returns a subset of the provided collection based on a boolean condition that
+ is asserted for each element.
+
+NOTES:
+
+- **Apple Notarization**. Our darwin releases for this version and up will be signed
+ and notarized according to Apple's requirements.
+
+## 0.13.1 (November 25, 2019)
+
+BUG FIXES:
+
+- `runtime/eval`: Parameters are no longer allowed in mock files. Adding one to
+ a mock will result in a runtime error.
+- `runtime/eval`: Fixed an issue where import calls from within mocks were
+ failing under certain circumstances.
+- `imports/decimal`: `int` should now correctly provide the truncated integer
+ representation of a decimal number, not the rounded one.
+
+## 0.13.0 (November 15, 2019)
+
+LANGUAGE CHANGES:
+
+- **Policy Parameters**. The new [policy
+ parameters](https://docs.hashicorp.com/sentinel/language/parameters) feature
+ allows authors to use a `param` declaration at the top of a policy to supply
+ values that are expected to come from outside of a policy. See the linked
+ documentation for more details.
+
+FEATURES:
+
+- `imports/http`: The [`http`
+ import](https://docs.hashicorp.com/sentinel/imports/http) is a new addition
+ to the standard library enabling the use of HTTP-accessible data from outside
+ the runtime in policy rules.
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where loading other imports before a mocked
+ import would cause those imports to no longer be visible from within the
+ policy.
+
+## 0.12.0 (October 7, 2019)
+
+LANGUAGE CHANGES:
+
+- **New `case` statement**. This statement is a selection control mechanism to
+ conditionally execute a branch based on expression equality, allowing
+ simplification of complex conditional chains that may otherwise need to be
+ written with `else if`. See the [Case
+ Statements](https://docs.hashicorp.com/sentinel/language/spec#case-statements)
+ section of the Sentinel language specification for more details.
+
+IMPROVEMENTS:
+
+- `lang/semantic`: Added a semantic check to ensure usage of append is not using
+ a return value.
+
+BUG FIXES:
+
+- `runtime/eval`: Corrected an issue where calling a method on an import object
+ value that was the result of a method call on another import object value
+ would have erroneously tried to call an import of the name of the "parent"
+ import object value. Example: in `a = subject.new(); b = a.call(); c =
+ b.call()`, `b.call()` would attempt to call a method named `call` on the root
+ namespace for an import named `a`. This has now been corrected so that
+ `b.call()` will now correctly call the `call` method for the respective object
+ namespace residing in the import named `subject`.
+- `imports`: Some standard imports may have been returning null for some unknown
+ keys in objects when they should have been returning undefined. This was due
+ to an SDK issue which was corrected in SDK version 0.3.2, which has now been
+ corrected.
+- `cmd/test`: `sentinel test` will now correctly fail a policy if it encounters
+ an error.
+- `cmd/test`: `sentinel test` will now correctly display errors and other output
+ that were missing due to a formatting issue.
+- `runtime/eval`: The `append` builtin now correctly returns undefined for all
+ calls, as called for by the Specification. Note that in most cases, the
+ semantic check outlined above will trigger an error if the return value is
+ used.
+
+## 0.11.0 (September 5, 2019)
+
+LANGUAGE CHANGES:
+
+- **New builtin function:** `range()`. This function existed in the spec in
+ earlier versions but was removed as it lacked an implementation. This has now
+ been implemented and re-added to the spec. See the
+ [Range](https://docs.hashicorp.com/sentinel/language/spec#range) section of
+ the Sentinel Specification for more details.
+- Lists are now comparable. Lists are equal if their corresponding elements are
+ comparable and equal.
+- Method calls on values returned by imports are now supported. See the
+ [Imports](https://docs.hashicorp.com/sentinel/language/spec#imports) section
+ of the Sentinel Specification for more details.
+
+FEATURES:
+
+- `imports/decimal`: This is a new import designed to do exact precision
+ mathematical calculations.
+- `runtime/eval`: Compound call expressions that refer to imports (example:
+ foo.bar().baz() when `foo` is a loaded import) will now function as expected.
+ Previously, this was only supported up to the first call (example:
+ `foo.bar()`).
+- `imports/time`: Timespaces returned by calls such as `time.now` are now
+ callable. Example: `t = time.now; t.after(some_previous_time)` will now
+ function.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: The implementation of comparison of non-comparable types has
+ now changed. Rather than triggering a runtime error, non-comparable types will
+ now return false when attempting to compare.
+
+## 0.10.4 (August 15, 2019)
+
+BUG FIXES:
+
+- `runtime/encoding`: Fixed an issue with conversion of null values that could
+ lead to crashes in imports.
+
+## 0.10.3 (July 15, 2019)
+
+BUG FIXES:
+
+- `command/config`: Parsing a configuration file where malformed data is
+ encountered after apparently properly-formed data will now report an error. An
+ example would be a situation where misplaced braces would cause a JSON object
+ to only parse part of the configuration file.
+
+## 0.10.2 (June 25, 2019)
+
+BUG FIXES:
+
+- `lang/parser`: Corrected an issue where parsing certain compound binary
+ expressions where the negation predicate (`not`) was in use would cause the
+ negation to have no effect. Example: `foo else "bar" not in "baz"` would have
+ been parsed and evaluated as `foo else "bar" in "baz"`, effectively producing
+ the opposite result.
+
+## 0.10.1 (May 9, 2019)
+
+BUG FIXES:
+
+- `command/test`: `sentinel test` now displays policies without
+tests as an unknown result with [no test files], instead of the somewhat
+erroneous behavior of displaying it as a PASS. A full test run with a mixture
+of passing tests and no tests still results in an overall successful result.
+
+## 0.10.0 (April 18, 2019)
+
+LANGUAGE CHANGES:
+
+- Mixed-number arithmetic operations are now allowed. Addition (`+`),
+ subtraction (`-`), multiplication (`*`), division (`/`), and remainder
+ operations (`%`) are no longer restricted to number values of the same
+ type, and can mix integer and floating point. The result of these operations
+ is always a floating-point number.
+- Remainder (modulo, `%`) operations are now allowed on floating-point numbers.
+
+
+BUG FIXES:
+
+- `lang/parser`: Fixed a bug that affected the use of `not` with `contains`,
+ `matches`, or `in` in a compound expression.
+- `runtime/eval`: Fixed error messages on evaluation errors with `contains` and
+ `in` to indicate that strings are an allowed type along with lists and maps.
+
+
+## 0.9.2 (March 15, 2019)
+
+This is a dependency update related to the changes mentioned in 0.9.1. No other
+changes have been made.
+
+## 0.9.1 (March 14, 2019)
+
+This is a patch release that is required to integrate with the latest versions
+of the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). No other
+changes have been made.
+
+## 0.9.0 (January 28, 2019)
+
+FEATURES:
+
+- `imports/strings`: Added a `join` function to the `strings` import. This can
+ be used to join a list into a string with a specific separator.
+ Multi-dimensional lists and all Sentinel primitives are supported.
+
+## 0.8.1 (January 17, 2019)
+
+BUG FIXES:
+
+- `lang/eval`: Fixed a bug that prevents the effective use of `return`
+ statements in `for` loops.
+
+## 0.8.0 (January 14, 2019)
+
+FEATURES:
+
+- Mocks can now be represented by Sentinel code. This allows for the mocking of
+ functions and other complex data structures that cannot be represented in
+ JSON. For more information on using this feature via mocks, click
+ [here](https://docs.hashicorp.com/sentinel/commands/config#mocking-with-sentinel-code).
+
+
+IMPROVEMENTS:
+
+- Import validation has now been moved to the semantic checking phase. This
+ should result in better reporting of validation errors. In addition, import
+ validation will now enforce the use of an `as` identifier when an import path
+ is not a valid identifier on its own (example: `import "foo/bar"`).
+
+## 0.7.0 (December 12, 2018)
+
+BUG FIXES:
+
+- There have been changes to the runtime in how scope is handled over multiple
+ policy executions. Scope is now correctly unique per single policy execution,
+ and values set or builtins that are overridden in one policy will no longer
+ affect those values within another.
+
+## 0.6.0 (November 30, 2018)
+
+FEATURES:
+
+- `imports/runtime`: This new import allows for one to check various aspects of
+ the Sentinel runtime as it may be embedded in the simulator or a specific
+ implementation. For now, it allows the version to be checked.
+
+## 0.5.1 (November 28, 2018)
+
+IMPROVEMENTS:
+
+- `imports/time`: Added the `zone` and `zone_string` attributes to assist with
+ validation of a timespace's zone.
+- `command/fmt`: Added a new -check flag. This option does not commit changes,
+ but instead checks to see what files need formatting and outputs them on
+ stdout.
+
+BUG FIXES:
+
+- `command/test`: Ensure that passing test results are correctly output one per
+ line. Tests are also now run in a deterministic fashion based on
+ lexicographical (alphabetical) order.
+- `imports/time`: `month_name` and `weekday_name` will now show up correctly in
+ a returned timespace result.
+
+
+## 0.5.0 (November 5, 2018)
+
+IMPROVEMENTS:
+
+- `spec`: Selectors can now contain any reserved word (example: `rule`) or
+ keyword operator (example: `any`, `all`, `is`, `not`). This only works for the
+ _selector_ part of the expression (after the first period) - the first primary
+ expression (before the first period) still needs to be an identifier that does
+ not conflict with reserved words.
+
+BUG FIXES:
+
+- The simulator should now display import function call names correctly in
+ import errors.
+
+## 0.4.0 (October 1, 2018)
+
+FEATURES:
+
+- `builtin`: Added the `bool` built-in type conversion function. Booleans will
+ also now accepted as conversion into other values as well, with the full list
+ of behaviors available in the spec.
+
+## 0.3.2 (September 27, 2018)
+
+FEATURES:
+
+- `command/apply`: `sentinel apply` now prints out messages output by the
+ `print()` function when a trace is output on policy failure, or when a trace
+ is forced with `-trace`.
+- `imports/time`: Added the `month_name` and `weekday_name` keys to the
+ timespace, which return full-English names for the month and day of the week.
+
+
+BUG FIXES:
+
+- `command/fmt`: `sentinel fmt -` Will no longer print out the filter status
+ message on the output stream when `-write=false` Is not explicitly stated.
+ This brings the behavior of the command in line with the help text.
+- `runtime`: Index operations on the right-hand-side that have negative indexes
+ that go out of range (example: `length(list) * -1 - 1`) now correctly return
+ `undefined`. left-hand-side index assignments with a out-of-range negative
+ index still return runtime errors.
+
+## 0.3.1 (August 3, 2018)
+
+BUG FIXES:
+
+- `runtime`: Basic index assignment has been implemented as per the spec.
+- `runtime`: Index expressions for lists with negative indexes will no longer panic if the
+ list index is less than `length(list) * -1`.
+
+## 0.3.0 (July 20, 2018)
+
+FEATURES:
+
+- **New standard import: `types`.** This can be used to dynamicaly detect the
+ type of some value.
+
+## 0.2.0 (April 11, 2018)
+
+FEATURES:
+
+- **New standard import: `json`.** Marshal and unmarshal JSON documents and
+ access their contents as native Sentinel values.
+- **`break` and `continue`.** These are now both specified and implemented.
+ `break` allows loop exiting and `continue` allows immediate execution of the
+ next iteration.
+
+IMPROVEMENTS:
+
+- runtime: `print()` map values are now ordered alphabetically by keys.
+
+BUG FIXES:
+
+- command/test: If no `test` block exists, test behaves like it is asserting
+ `main: true`.
+- runtime: default maximum stack depth to 500
+- runtime: `print()` map values now appear like more typical maps.
+- runtime: division by zero is an error, not a crash
+- runtime: plugins that send map values with `null` values now decode properly
+ into native Sentinel values.
+
+## 0.1.0 (September 19, 2017)
+
+Initial release.
+
+
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/commands/apply.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/commands/apply.mdx
new file mode 100644
index 0000000000..c5e81d6d65
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/commands/apply.mdx
@@ -0,0 +1,63 @@
+---
+page_title: 'Command: apply'
+sidebar_current: docs-commands-apply
+description: >-
+ The `sentinel apply` command is used to execute a policy locally for
+ development purposes.
+layout: docs
+---
+
+# Command: `apply`
+
+The `sentinel apply` command is used to execute a policy locally for development
+purposes.
+
+## Usage
+
+Usage: `sentinel apply [options] POLICY`
+
+This command executes the policy file at the path specified by POLICY. In
+addition to a path to a policy, POLICY can reference a label of a
+[policy](/sentinel/configuration#policies) entry in the configuration.
+
+Use the exit code of this command to determine the exact status of the policy
+evaluation. `0` is pass, `1` is fail, `2` is undefined (fail, but because the
+result was undefined), and `3` is a runtime error. Errors unrelated to the
+policy status itself are returned with an exit status of `9`.
+
+To control the behavior of the `apply` command, create a [configuration
+file](/sentinel/configuration). With this, you can define available
+[import plugins](/sentinel/concepts/imports), mock data, and global values.
+This can help you simulate a policy embedded within an application.
+
+As of the 0.16 release, POLICY is optional. When it is not supplied,
+`sentinel apply` will run **all** policies in the supplied configuration.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-config=path` - Path to a configuration file specifying available imports,
+ mock data, globals, etc. The default is `sentinel.[hcl|json]`.
+
+- `-json` Enable JSON output. Using this mode, all other output will be
+ suppressed and the full detail of the apply will be output in a
+ machine-readable format. Enabling this implies `-trace`. See the section on
+ [tracing JSON output](/sentinel/writing/tracing#json-output) for more details.
+
+- `-json-rule=RULE` Enable JSON output, outputting only the value of the
+ specified rule. When running against a policy set, the policy must be supplied
+ to enable per-rule filtering. Implies `-json`.
+
+- `-global key=value` - Set global values. This is the same as setting `global`
+ in the configuration file, and will override any of these respective values
+ set in the configuration. The value is either a string, or a JSON number,
+ array, or object. To force strings, use quotes.
+
+- `-param key=value` - Set parameters, the same as setting `param` in the
+ configuration file. Values are handled in the same way they are with the
+ `-global` flag.
+
+- `-trace` - Always show the execution trace. This shows intermediate
+ boolean expression values. This always shows for failed policies.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/commands/fmt.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/commands/fmt.mdx
new file mode 100644
index 0000000000..8297316d8b
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/commands/fmt.mdx
@@ -0,0 +1,33 @@
+---
+page_title: 'Command: fmt'
+sidebar_current: docs-commands-fmt
+description: The `sentinel fmt` command formats a policy source to a canonical format.
+layout: docs
+---
+
+# Command: `fmt`
+
+The `sentinel fmt` command formats a policy source to a canonical format.
+
+## Usage
+
+Usage: `sentinel fmt [options] FILE ...`
+
+This command formats all the specified policy files to a canonical format.
+
+By default, policy files are overwritten in place. This behavior can be
+changed with the `-write` flag. If a specified FILE is `-` then stdin is
+read and the output is always written to stdout.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-write=true` - Write formatted policy to the named source file. If false,
+ output will go to stdout. If multiple files are specified, the output will
+ be concatenated directly.
+
+- `-check=false` - Don't format, only check if formatting is necessary. Files
+ that require formatting are printed, and a non-zero exit code is returned if
+ changes are required.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/commands/index.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/commands/index.mdx
new file mode 100644
index 0000000000..7177093cec
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/commands/index.mdx
@@ -0,0 +1,23 @@
+---
+page_title: Commands (CLI)
+sidebar_current: docs-commands
+description: >-
+ Sentinel provides a `sentinel` command-line interface (CLI) for developing and
+ testing policies.
+layout: docs
+---
+
+# Sentinel CLI Commands
+
+The Sentinel command-line interface (CLI) allows for the developing and testing
+of policies outside of a particular Sentinel implementation. Having a standard
+workflow to develop policies is critical for our mission of [policy as
+code](/sentinel/concepts/policy-as-code).
+
+The CLI takes a subcommand to execute. The complete list of subcommands is in
+the navigation to the left.
+
+The Sentinel CLI is a well-behaved command line application. In erroneous cases,
+a non-zero exit status will be returned. It also responds to -h and --help as
+you'd expect. To view a list of the available commands at any time, just run
+`sentinel` with no arguments.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/commands/test.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/commands/test.mdx
new file mode 100644
index 0000000000..bc785c3725
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/commands/test.mdx
@@ -0,0 +1,52 @@
+---
+page_title: 'Command: test'
+sidebar_current: docs-commands-test
+description: The `sentinel test` command runs policies against the Sentinel test framework.
+layout: docs
+---
+
+# Command: `test`
+
+The `sentinel test` command runs policies against the Sentinel test framework.
+
+To learn more about testing, see the [testing
+policies](/sentinel/writing/testing) page.
+
+## Usage
+
+Usage: `sentinel test [options] [PATH] ...`
+
+This command runs the defined test cases against a set of policies.
+
+The test cases are expected to live at the path `test//*.[hcl|json]` relative
+to the policy being tested. `` should be the name of the policy
+excluding the file extension. The files should be in the [configuration
+file structure](/sentinel/configuration). The `test` key is used for
+assertions.
+
+The PATH arguments specified may be a list of zero or more files or directories.
+When no arguments are given, it defaults to the current directory. When a
+directory is given, all policies ending with the extension `.sentinel` are
+tested.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-json` - Suppress normal output and output test results as a JSON document.
+ See the [testing JSON output](/sentinel/writing/testing#test-json-output)
+ section for more details.
+
+- `-run=regexp` - Run only tests matching the regular expression pattern.
+ A `/` splits policy name and test case name.
+
+- `-verbose` - Show tracing and log output for tests that succeed in addition
+ to tests that fail. By default, traces and logs are only shown for tests that
+ fail.
+
+- `-maxConcurrency` - Allows users to specify the number of tests to be run concurrently.
+ Defaults to the number of logical CPUs if not provided. To run tests sequentially, use `-maxConcurrency=1`
+
+- `-timeout` - Allows users to specify a timeout after which the test command will stop running.
+Defaults to 5 minutes if not provided. The timeout needs to be specified as a Duration type, eg `100ms, 5s etc`
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/concepts/enforcement-levels.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/concepts/enforcement-levels.mdx
new file mode 100644
index 0000000000..fc4b8062d0
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/concepts/enforcement-levels.mdx
@@ -0,0 +1,43 @@
+---
+page_title: Enforcement Levels
+sidebar_current: docs-concepts-levels
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Enforcement Levels
+
+Enforcement levels are a first class concept in Sentinel allowing pass/fail
+behavior to be associated separately from the policy logic. This enables
+any policy to be a warning, allow overrides, or be absolutely mandatory.
+Because this level is not part of the policy body itself, different uses of
+the same policy can have different enforcement levels.
+
+Sentinel has three enforcement levels:
+
+- **Advisory:** The policy is allowed to fail. However, a warning should be
+ shown to the user or logged. Advisory is the default enforcement level.
+
+- **Soft Mandatory:** The policy must pass unless an override is specified.
+ The semantics of "override" are specific to each Sentinel-enabled application.
+ The purpose of this level is to provide a level of privilege separation
+ for a behavior. Additionally, the override provides non-repudiation since
+ at least the primary actor was explicitly overriding a failed policy.
+
+- **Hard Mandatory:** The policy must pass no matter what. The only way to
+ override a hard mandatory policy is to explicitly remove the policy.
+ It should be used in situations where an override is not possible.
+
+## Configuring Enforcement Levels
+
+Enforcement levels are configured when a policy is deployed to a
+Sentinel-enabled application. The exact mechanism that the level is specified
+is determined by each application. Please reference the documentation for
+your Sentinel-enabled application for more information.
+
+Enforcement levels are not configured and are not known by the policy body
+itself. All policies should be written to describe exactly the behavior
+they're attempting to control. For example, a policy that restricts deploys
+to business hours should be written exactly like so. When that policy is
+configured on an application, the operator may specify that it is advisory,
+soft, or hard mandatory.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/concepts/imports.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/concepts/imports.mdx
new file mode 100644
index 0000000000..1306e1512e
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/concepts/imports.mdx
@@ -0,0 +1,32 @@
+---
+page_title: Imports
+sidebar_current: docs-concepts-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom imports](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only
+local context for making policy decisions.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that the
+import calendar allows access to engineer calendars. This import only exists
+for the purpose of this example and at the time of writing is not a real
+available import.
+
+```json sentinel
+{
+ "policy": "import \"calendar\"\n// Get the calendar for Bob for today\nbob_calendar = calendar.of(\"bob\").today\n\n// Allow this policy to pass if Bob is not on vacation.\nmain = rule { not bob_calendar.has_event(\"vacation\") }",
+ "mocks": {
+ "calendar": "has_event = func(event) {\n\treturn true\n}\nof = func(name) {\n\treturn { \"today\": { \"has_event\": has_event } }\n}"
+ }
+}
+```
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/concepts/index.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/concepts/index.mdx
new file mode 100644
index 0000000000..ce7ac51454
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/concepts/index.mdx
@@ -0,0 +1,15 @@
+---
+page_title: Basic Concepts
+sidebar_current: docs-concepts
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Basic Concepts
+
+This section covers some high level basic concepts that are important
+to understand for day to day Sentinel usage. Every page in
+this section is recommended reading for anyone consuming
+or extending Sentinel.
+
+Please use the navigation to the left to learn more about a topic.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/concepts/language.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/concepts/language.mdx
new file mode 100644
index 0000000000..ce91f3eb8f
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/concepts/language.mdx
@@ -0,0 +1,91 @@
+---
+page_title: Policy Language
+sidebar_current: docs-concepts-language
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Policy Language
+
+Sentinel defines and uses its own [policy language](/sentinel/language).
+
+The language was designed to be approachable by non-programmers, since
+there are many use cases where the individual defining policy may not
+be a developer. However, the language includes constructs that
+are familiar to developers to enable powerful policies.
+
+To learn more about the language, please see the
+[writing policy section](/sentinel/writing)
+or the [language reference](/sentinel/language).
+
+An example of the policy language is shown below:
+
+```sentinel playground
+import "time"
+
+# Validate time is between 8 AM and 4 PM
+valid_time = rule { time.now.hour >= 8 and time.now.hour < 16 }
+
+# Validate day is M - Th
+valid_day = rule {
+ time.now.weekday_name in ["Monday", "Tuesday", "Wednesday", "Thursday"]
+}
+
+main = rule { valid_time and valid_day }
+```
+
+## Why?
+
+To explain why Sentinel defines and uses its own language, the question
+can be more easily split into roughly two types of languages: _configuration_ languages
+and _programming_ languages.
+
+### Configuration Languages
+
+Configuration languages are formats such as JSON, YAML, XML, etc. Many applications
+use configuration languages as their ACL format. Configuration languages
+are good for static, declarative information. ACL systems are a good fit
+for this. ACL systems are focused, providing the limiting options necessary
+to restrict access to a system.
+
+Configuration languages are not good for dynamic or logical rules. They
+typically only contain limited conditions, loops, or functions. Further
+configuration is often declarative, which can introduce complexities to
+logical statements that depend on ordering.
+
+The use cases that Sentinel was built for require the ability to perform
+complex behavior that didn't model well into a configuration format or a
+declarative form.
+
+### Programming Languages
+
+The Sentinel language was designed with the following goals:
+
+- **Non-programmer friendly.** Sentinel was built to be used by non-programmers.
+ For the use cases we discovered, non-programmers needed the ability to
+ enforce certain rules within a system. For example, a person responsible for
+ compliance may need to insert rules into a system.
+
+- **Programmer friendly.** At the same time, the language needed to support
+ programmer-friendly constructs such as conditionals, loops, and functions
+ for complex policies that a programmer may be writing.
+
+- **Embeddable.** Sentinel is embedded in existing software. The language
+ itself needs to be easily embeddedable. Further, Sentinel was designed
+ to be embedded in [HashiCorp](https://www.hashicorp.com) software
+ written in Go, so it needed to be easily embeddable in Go.
+
+- **Safe.** The language is used in highly security-sensitive environments.
+ It must not be able to crash its host system or access system resources
+ without explicit approval.
+
+Prior to building our own language, Sentinel evaluated other languages.
+Some languages worked quite well. As we continued to develop Sentinel, we
+found that the flexibility and control over designing our own language
+outweighed the costs.
+
+Because the language itself doesn't need to be general purpose, building
+a language focused on policy proved to be the best route for Sentinel.
+It allows us to build powerful tracing capabilities for debugging, make
+interesting performance choices, and extend the language as needed in the
+future.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/concepts/policy-as-code.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/concepts/policy-as-code.mdx
new file mode 100644
index 0000000000..6dac593e18
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/concepts/policy-as-code.mdx
@@ -0,0 +1,72 @@
+---
+page_title: Policy as Code
+sidebar_current: docs-concepts-pac
+description: >-
+ Policy as code is the idea of writing code in a high-level language to manage
+ and automate policies. By representing policies as code in text files, proven
+ software development best practices can be adopted such as version control,
+ automated testing, and automated deployment.
+layout: docs
+---
+
+# Policy as Code
+
+Policy as code is the idea of writing code in a high-level language to
+manage and automate policies. By representing policies as code in text files,
+proven software development best practices can be adopted such as version
+control, automated testing, and automated deployment.
+
+Many existing policy or ACL systems do not practice policy as code. Many
+policies are set by clicking in a GUI, which isn't easily repeatable nor
+versionable. They usually don't provide any system for testing policies
+other than testing an action that would violate the policy. This makes it
+difficult for automated testing. And the policy language itself varies by
+product.
+
+Sentinel is built around the idea and provides all the benefits of policy as code.
+
+## Benefits
+
+Policy as code provides a number of benefits:
+
+- **Sandboxing.** Policies provide the guardrails for other automated systems.
+ As the number of automated systems grow, there is also a growing need to
+ protect those automated systems from performing dangerous actions. Manual
+ verification is too slow; policies need to be represented as code to
+ keep up with other automated systems.
+
+- **Codification.** By representing policy logic as code, the information
+ and logic about a policy is directly represented in code and can be augmented
+ with comments rather than relying on oral tradition to learn about the
+ reason for policies.
+
+- **Version Control.** Policies are encouraged to be stored as simple text
+ files managed by a version control system. This lets you gain all the
+ benefits of a modern VCS such as history, diffs, pull requests, and more.
+
+- **Testing.** Policies are just code. Their syntax and behavior can be
+ [easily validated with Sentinel](/sentinel/commands/test). This also encourages
+ automated testing such as through a CI. Paired with a VCS system, this
+ allows a pull request workflow to verify that a policy keeps the system
+ behavior as expected before merging.
+
+- **Automation.** With all policies as code in simple text files, various
+ automation tools can be used. For example, it is trivial to create tools
+ to automatically deploy the policies into a system.
+
+## Sentinel and Policy as Code
+
+Sentinel fully embraces policy as code in a number of ways:
+
+- **Language.** All Sentinel policies are written using the
+ [Sentinel language](/sentinel/concepts/language). This language is
+ made to be inputted directly to text files. As an additional benefit,
+ all Sentinel-enabled applications share the same policy language.
+
+- **Development.** Sentinel provides a [CLI](/sentinel/commands)
+ for development and testing. This local CLI can be used to verify policies
+ before deploying them to a system.
+
+- **Testing.** Sentinel provides a [test framework](/sentinel/commands/test)
+ designed specifically for automation. This allows developers and CI systems
+ to further verify policies.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/configuration/index.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/configuration/index.mdx
new file mode 100644
index 0000000000..248945b3b7
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/configuration/index.mdx
@@ -0,0 +1,406 @@
+---
+page_title: Sentinel CLI Configuration File Syntax
+sidebar_current: docs-configuration
+description: >-
+ The Sentinel CLI's configuration file can be used to control the
+ behavior of the simulator during apply and test operations.
+layout: docs
+---
+
+# Sentinel CLI Configuration File Syntax
+
+The Sentinel CLI's configuration file can be used to control the behavior
+of the simulator during [`apply`](/sentinel/commands/apply) and
+[`test`](/sentinel/commands/test) operations.
+
+## Usage
+
+The configuration file is used in different ways depending on what operation you
+are trying to execute.
+
+### Apply
+
+When using `sentinel apply`, configuration files that have the `.hcl`
+extension are loaded into a single configuration. This allows for configuration
+to be split across multiple files, which is useful for large policy sets. To
+supply a single configuration file, the `-config=FILE` flag can be used, where
+`FILE` is the path to the configuration file. This argument also allows for a
+`.json` configuration file to be supplied. The default is `sentinel.[hcl|json]`.
+
+See the [apply command](/sentinel/commands/apply) reference for more details.
+
+### Test
+
+When using `sentinel test`, each file matching `test//*.[hcl|json]` is a
+configuration file representing a single test case, where `` is the name
+of the policy being tested. Configure assertions for each [test
+case](#test-cases) using the test section.
+
+See the [test command](/sentinel/commands/test) reference for more details.
+
+#### Remote sources
+
+When declaring a `policy` or `module` block within the configuration, a
+`source` attribute must be supplied. This `source` should declare the location
+of the resource, which can be either a local file or a URL pointing to a remote
+file. Examples of local `source` attributes are detailed both in the
+[Policies](#policies) and [Modules](#modules) sections of this page. Below
+are a few examples of remote `source` attributes.
+
+Example:
+
+```hcl
+policy "foo" {
+ source = "git::https://github.com/hashicorp/sentinel-example.git//main.sentinel"
+ enforcement_level = "hard-mandatory"
+}
+```
+
+The above example will fetch the policy from the provided URL and place it into
+the cache ready for use. Be sure to read the
+[Remote Sources](/sentinel/configuration/remote-sources) page for an
+in-depth overview.
+
+## Configuration File Reference
+
+The format of the configuration file is either JSON or HCL. The available
+blocks are:
+
+- [`mock`](#mock-imports) - A mock import specification.
+- [`policy`](#policies) - Configuration for a [policy](/sentinel/writing).
+- [`import`](#imports) - Configuration for a [module](/sentinel/extending/modules), [standard import](/sentinel/imports), [plugin](/sentinel/extending/plugins) or
+ [static import](/sentinel/extending/static-imports).
+- [`global`](#globals) - Data that is inserted into the global scope.
+- [`param`](#parameters) - Values for [parameters](/sentinel/language/parameters) defined in the policy.
+- [`test`](#test-cases) - Test cases for the [test command](/sentinel/commands/test).
+
+### Mock Imports
+
+Mock imports allow running a policy with an
+[import](/sentinel/concepts/imports) that you may not have access to or is
+too difficult to run. For example, it can be easier to mock data for [Terraform
+Enterprise](https://www.terraform.io/docs/enterprise/sentinel/index.html)
+locally instead of having to run a workspace through a plan/policy check cycle.
+
+Mock imports are specified using the `mock` block, labelled by the import
+name that you want to mock. For example, if you wanted to mock the `time`
+import, you could create an entry with the `time` label pointing to the data
+you want to mock.
+
+The data can take one of two forms:
+
+#### Mocking static data
+
+Static data can be mocked directly via HCl using the `data` attribute on the
+`mock` block.
+
+Example:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ hour = 9
+ minute = 42
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"time\"\n\nmain = rule { time.now.hour is 9 }",
+ "mocks": {
+ "time": "now = { \"hour\": 9, \"minute\": 42 }"
+ }
+}
+```
+
+#### Mocking with Sentinel code
+
+There are some Sentinel types that raw data cannot mock, such as functions,
+and maps that don't have string keys. To mock this data, you can use
+a Sentinel file itself. In this case, you can set the `module` attribute to
+a file with the Sentinel code in it, relative to the current working directory in
+the event of `apply`, or relative to the configuration file location in the
+event of `test`.
+
+Note that `main` is not required in a mock data file.
+
+Example:
+
+```hcl
+mock "foo" {
+ module {
+ source = "mock-foo.sentinel"
+ }
+}
+```
+
+`mock-foo.sentinel` would contain:
+
+```sentinel
+bar = func() {
+ return "baz"
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"foo\"\n\nmain = rule { foo.bar() is \"baz\" }",
+ "mocks": {
+ "foo": "bar = func() { return \"baz\" }"
+ }
+}
+```
+
+### Policies
+
+The `policy` block allows for the the configuration of policies to assist
+with integrating into other commands.
+
+Each `policy` block has a label to identify the policy, with the block
+providing configuration of the policy. The available configuration options for
+policy are `source`, `enforcement_level` and an optional `params` attribute. The
+`source` key provides the location of the policy, while `enforcement_level` is
+currently used by integrations such as [Terraform Cloud](https://www.terraform.io/docs/cloud/sentinel/manage-policies.html#enforcement-levels).
+For more information on the `source` value, see [Policy and Module Sources](#policy-and-module-sources).
+
+The optional `params` attribute is used to provide values to [parameters](/sentinel/language/parameters)
+defined within the policy file.
+
+```hcl
+policy "foo" {
+ source = "foo.sentinel"
+ enforcement_level = "hard-mandatory"
+ params = {
+ "name" = "Sample"
+ }
+}
+```
+
+If `enforcement_level` is not supplied, it will fallback to the `advisory` level.
+
+### Imports
+
+Import configuration allows you to configure [modules](/sentinel/extending/modules),
+[standard imports](/sentinel/imports) and [import plugins](/sentinel/extending/plugins).
+
+The `import` is a multi-label block, with the first label determining the kind of import
+being configured. Currently the supported values for this label are:
+
+* [`"module"`](#import-modules) for configuring import modules
+* [`"plugin"`](#import-plugins) for configuring standard imports and import plugins
+* [`"static"`](#static-imports) for configuring static data files as imports
+
+#### Import Modules
+
+The `import "module"` block allows you to map a Sentinel import to a
+[module](/sentinel/extending/modules). The Sentinel CLI will parse the module
+source and make it available for evaluation with the policy.
+
+Each block has a label aligning to the name of the import, with
+the block set to the configuration object for the module. Currently, the only
+value within the configuration object is `source`, which points to the
+location of the module. For more information on the `source` value, see
+[Policy and Module Sources](#policy-and-module-sources).
+
+```hcl
+import "module" "foo" {
+ source = "modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "modules/bar.sentinel"
+}
+```
+
+Note when using [`sentinel test`](/sentinel/commands/test), module paths must be
+specified relative to the location of the configuration file.
+
+```hcl
+import "module" "foo" {
+ source = "../../modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "../../modules/bar/sentinel"
+}
+```
+
+See [Writing and Using a
+Module](/sentinel/extending/modules#writing-and-using-a-module) for more details
+on using a module with this configuration.
+
+### Import Plugins
+
+Import `"plugin"` configuration allows you to configure both [standard imports](/sentinel/imports)
+as well as [import plugins](/sentinel/extending/plugins) that can be used within
+a policy. If defining a custom import plugin, the Sentinel CLI will launch the
+plugin, connect to it, configure it, and execute it as needed by the policy.
+
+The available configuration attributes for an `import "plugin"` are:
+
+- `source` (string, required) - Path to the import plugin executable.
+- `args` (list of string, optional) - A list of arguments to pass to the
+ executable when starting it.
+- `env` (map of string to string, optional) - A set of environmental variables
+ to set when launching the plugin.
+- `config` (map, optional) - Configuration for the plugin itself. This is
+ specific to each individual plugin. Please reference the documentation for
+ the plugin for more information.
+
+Standard import example:
+
+```hcl
+import "plugin" "time" {
+ config = { "timezone": "Australia/Brisbane" }
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"time\"\n\nmain = time.now.zone_string is \"+10:00\"",
+ "mocks": {
+ "time": "now = { \"zone_string\": \"+10:00\" }"
+ }
+}
+```
+
+Custom plugin example:
+
+```hcl
+# flipper receives a boolean and returns the opposite
+import "plugin" "flipper" {
+ source = "/path/to/flipper"
+}
+```
+
+```json sentinel
+{
+ "policy": "import \"flipper\"\n\nmain = flipper.flip(false)",
+ "mocks": {
+ "flipper": "flip = func(input) { return not input }"
+ }
+}
+```
+
+#### Static Imports
+
+Static import configuration allows you to supply a data file and make
+it available to a policy via an import statement.
+
+The available configuration attributes for an `import "static"` are:
+
+- `source` (string, required) - Path to the static data file.
+- `format` (string, required) - The format of the static data. Currently, only
+ the "json" value is supported.
+
+Static import example:
+
+```hcl
+import "static" "people" {
+ source = "./data/people.json"
+ format = "json"
+}
+```
+
+```json sentinel
+{
+ "policy": "import \"people\"\n\nmain = length(people.names) > 0",
+ "mocks": {
+ "people": "names = [\"John Smith\", \"Jane Smith\"]"
+ }
+}
+```
+
+### Globals
+
+Global data is injected directly into the global scope of the policy.
+This can be used to simulate global data that may be injected by a
+Sentinel-enabled application.
+
+Global data is specified by the `global` key. The value is a map of
+variables to inject directly into the running policy.
+
+Example:
+
+```hcl
+global "time" {
+ value = {
+ now = {
+ day = 31
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass. Notice
+that we don't have to do any `import`. The values of `global` are
+injected directly into the global scope.
+
+```json sentinel
+{
+ "policy": "main = time.now.day == 31",
+ "globals": {
+ "time": {
+ "now": {
+ "day": 31
+ }
+ }
+ }
+}
+```
+
+### Parameters
+
+The `param` section allows you to supply values for the
+[parameters](/sentinel/language/parameters) found in a policy. Values
+entered here will satisfy required parameters in a policy, in addition to
+override any defaults. Note that any of the manual parameter value methods
+supported by `sentinel apply` will override the values set here.
+
+```hcl
+param "foo" {
+ value = "bar"
+}
+```
+
+### Test Cases
+
+Test cases specify the test cases for the [test command](/sentinel/commands/test).
+
+Tests are specified by the `test` key. The value of this is a map of
+string to boolean. The key is the name of a rule and the value is the
+expected value of that rule. If a rule is not specified, than any value is
+allowed.
+
+Example:
+
+```hcl
+test {
+ rules = {
+ main = true
+ days = []
+ }
+}
+```
+
+For the policy:
+
+```json sentinel
+{
+ "policy": "valid_days = rule {\n\tfilter days as d {\n\td is \"monday\"\n\t}\n}\n\nmain = rule { valid_days is empty }",
+ "globals": {
+ "days": []
+ }
+}
+```
+
+For more information, read about the [test command](/sentinel/commands/test).
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/configuration/overrides.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/configuration/overrides.mdx
new file mode 100644
index 0000000000..ca1a6842b1
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/configuration/overrides.mdx
@@ -0,0 +1,80 @@
+---
+page_title: Override Files
+sidebar_current: docs-configuration-overrides
+description: >-
+ The Sentinel CLI's configuration can be overriden using override files.
+ Override files merge additional settings into existing configuration.
+layout: docs
+---
+
+# Override Files
+
+As outlined in the configuration [overview](/sentinel/configuration#Apply),
+the normal behavior of Sentinel is to load all `.hcl` files into a single
+configuration object.
+
+In addition to appending multiple configuration files, Sentinel allows for the
+rare case of overriding configuration through override files. Override files
+are any files that match either `_override.{hcl,json}` or `override.{hcl,json}`.
+
+Sentinel will parse override files after it has loaded the primary
+configuration, and then process each override file in turn (in lexicographical
+order). All top level blocks other than [test](/sentinel/configuration#test-cases)
+require the block to already be defined in the primary configuration. It will
+then attempt to merge the override block into the existing configuration.
+
+## Example
+
+If you had a Sentinel configuration `sentinel.hcl` that contained the following:
+
+```hcl
+policy "main" {
+ source = "./main.sentinel"
+ enforcement_level = "advisory"
+}
+```
+
+And you created a file `override.hcl` that contained the following:
+
+```hcl
+policy "main" {
+ enforcement_level = "soft-mandatory"
+}
+```
+
+Sentinel will merge the contents of the override into the former, providing the
+following configuration:
+
+```hcl
+policy "main" {
+ source = "./main.sentinel"
+ enforcement_level = "soft-mandatory"
+}
+```
+
+## Merging Behavior
+
+The general rule, which applies in most cases, is:
+
+- A top-level block in an override file merges with a block in a normal
+ configuration file that has the same block header. The block header is the
+ block type and any quoted labels that follow it.
+- Within a top-level block, an attribute argument within an override block
+ replaces any argument of the same name in the original block.
+- Within a top-level block, any nested blocks within an override block replace
+ all blocks of the same type in the original block. Any block types that do not
+ appear in the override block remain from the original block.
+- The contents of nested configuration blocks are not merged.
+- The resulting merged block must still comply with any validation rules that
+ apply to the given block type.
+
+If more than one override file defines the same top-level block, the overriding
+effect is compounded, with later blocks taking precedence over earlier blocks.
+Overrides are processed in order first by filename (in lexicographical order)
+and then by position in each file.
+
+## Required Attributes
+
+It is important to note that all attributes inside an override file are
+considered to be optional, meaning that an override file can itself fail
+validation rules for block types.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/configuration/remote-sources.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/configuration/remote-sources.mdx
new file mode 100644
index 0000000000..b64e4d3abc
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/configuration/remote-sources.mdx
@@ -0,0 +1,165 @@
+---
+page_title: Remote Sources
+sidebar_current: docs-configuration-remote-sources
+description: >-
+ There are a number of options for formatting the URL provided to the
+ source attribute on policy and module blocks.
+layout: docs
+---
+
+# Remote Sources
+
+There are a number of options for formatting the URL provided to the
+`source` attribute on `policy` and `import "module"` blocks. This flexibility
+should solve most use cases and allow for reusable policies and modules.
+
+### Supported Protocols
+
+-> **NOTE:** All protocols are supported on the CLI, however each production
+Sentinel integration may not. Be sure to read the appropriate integration
+documentation to check the supported protocols.
+
+- Local filesystem
+- Git
+- Mercurial
+- HTTP
+- Amazon S3
+- Google GCP
+
+### Forced Protocols
+
+Due to the ambiguous nature of URLs, it is possible to force a particular
+protocol to be used during the fetch. To achieve this, simply prefix the url
+with the appropriate protocol as listed below:
+
+- `file` - Local filesystem
+- `git` - Git
+- `hg` - Mercurial
+- `http` or `https` - HTTP
+- `s3` - Amazon S3
+- `gcp` - Google GCP
+
+Example:
+
+```
+git::http://github.com/hashicorp/sentinel.git
+```
+
+The above would download the provided HTTP URL using the Git protocol.
+
+### Protocol Options
+
+In addition to some global options, there are options available to specific
+protocols to perform features such as authentication.
+
+#### Subdirectories / File Pathing
+
+When supplying URLs that point to a directory (eg. `git`), a subdirectory and
+file can be provided by suffixing the URL with `//` and the path. For example,
+if we have a git repository that has a policy, `main.sentinel` in the root, we
+could directly fetch the file by using the following:
+
+```
+git::https://github.com/hashicorp/sentinel-docs-example.git//main.sentinel
+```
+
+Or, if the file was nested in the `policies` folder:
+
+```
+git::https://github.com/hashicorp/sentinel-docs-example.git//policies/main.sentinel
+```
+
+#### Checksumming
+
+For downloads of any protocol, you can automatically verify a checksum. To
+checksum a file, append a `checksum` query parameter to the URL. The parameter
+value can be in the format of type:value or just value, where type is "md5",
+"sha1", "sha256", "sha512" or "file" . The "value" should be the actual
+checksum value or download URL for "file". When type part is omitted, type will
+be guessed based on the length of the checksum string.
+
+```
+./foo.sentinel?checksum=md5:b7d96c89d09d9e204f5fedc4d5d55b21
+
+./foo.sentinel?checksum=b7d96c89d09d9e204f5fedc4d5d55b21
+
+./foo.sentinel?checksum=file:./foo.txt.sha256sum
+```
+
+#### Unarchiving
+
+An `archive` query parameter can be supplied to explicitly state what format
+to attempt to extract, if required. The supported formats are:
+
+- `tar.gz` and `tgz`
+- `tar.bz2` and `tbz2`
+- `tar.xz` and `txz`
+- `zip`
+- `gz`
+- `bz2`
+- `xz`
+
+If a URL has an extension that matches one of the supported formats, it will
+use that format to unarchive.
+
+```
+./file.zip
+```
+
+The above would use the `zip` format to unarchive.
+
+```
+./path/to/file?archive=zip
+```
+
+Would force the `zip` format. If for some reason you required archiving to be
+disabled, this can also be achieved by using the `false` value.
+
+```
+./path/to/file?archive=false
+```
+
+#### Git (`git`)
+
+- `ref` - The Git ref to checkout. This is a ref, so it can point to a commit
+ SHA, a branch name, etc. If it is a named ref such as a branch name, it will
+ be updated to the latest on each get.
+- `sshkey` - An SSH private key to use during clones. The provided key must be
+ a base64-encoded string. For example, to generate a suitable sshkey from a
+ private key file on disk, you would run base64 -w0 \.
+ **Note:** Git 2.3+ is required to use this feature.
+- `depth` - The Git clone depth. The provided number specifies the last `n`
+ revisions to clone from the repository.
+
+The git getter accepts both URL-style SSH addresses like
+`git::ssh://git@example.com/foo/bar`, and "scp-style" addresses like
+`git::git@example.com/foo/bar`. In the latter case, omitting the `git::` forced
+prefix is allowed if the username prefix is exactly git@.
+
+The "scp-style" addresses cannot be used in conjunction with the `ssh://`
+scheme prefix, because in that case the colon is used to mark an optional port
+number to connect on, rather than to delimit the path from the host.
+
+#### Mercurial (`hg`)
+
+- `rev` - The Mercurial revision to checkout.
+
+#### HTTP (`http` or `https`)
+
+Basic Authentication
+
+To use HTTP basic authentication, simply prepend `username:password@` to the
+hostname in the URL such as `https://Aladdin:OpenSesame@www.example.com/index.html`.
+All special characters, including the username and password, must be URL
+encoded.
+
+#### S3 (`s3`)
+
+The following query parameters are available and will take priority when
+authenticating against an S3 bucket.
+
+- `aws_access_key_id` - AWS access key.
+- `aws_access_key_secret` - AWS access key secret.
+- `aws_access_token` - AWS access token if this is being used.
+- `aws_profile` - Use this profile from local ~/.aws/ config. Takes priority
+ over the other three.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/consul.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/consul.mdx
new file mode 100644
index 0000000000..7a3aaaa697
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/consul.mdx
@@ -0,0 +1,47 @@
+---
+page_title: Consul and Sentinel
+sidebar_current: docs-app-consul
+description: >-
+ Consul Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies are applied during writes to
+ the KV Store.
+layout: docs
+---
+
+# Consul
+
+[Consul Enterprise](https://www.hashicorp.com/products/consul/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies are applied during writes to the KV Store.
+
+Sentinel policies have access to the key/value being written. They can be used to
+allow or deny the modification. The information that Sentinel policies have access
+to will expand over time.
+
+The Consul integration with Sentinel is documented in depth in the
+[Consul Enterprise documentation](https://www.consul.io/docs/guides/sentinel.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Input validation depending on the name of the key.**
+
+```sentinel
+main = rule { valid_key() }
+
+required = [
+ ["port", "\\d+"], # ports must be integers
+ ["name", "\\w+"], # name must be a word
+]
+
+valid_key = func() {
+ for required as v {
+ if key is v[0] {
+ return value matches v[1]
+ }
+ }
+
+ return false
+}
+```
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/extending/index.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/extending/index.mdx
new file mode 100644
index 0000000000..a39cb0384d
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/extending/index.mdx
@@ -0,0 +1,52 @@
+---
+page_title: Extending Sentinel
+sidebar_current: docs-extending
+description: Learn how to create your own Sentinel imports to extend Sentinel's functionality.
+layout: docs
+---
+
+# Extending Sentinel
+
+-> **NOTE:** Sentinel's extensibility is currently undergoing large
+improvements - watch this space for future updates!
+
+Sentinel can be extended by writing additional
+[imports](/sentinel/language/imports). These imports can bring new functionality
+to Sentinel by allowing access to new data or by the addition of new functions.
+This section is designed to show you how to accomplish this extensibility and
+describe how it works.
+
+Currently, the only usable way to add additional imports is via
+[modules](#modules). While [plugins](#plugins) currently work under the Sentinel
+CLI, no Sentinel integration currently supports their use.
+
+-> You may also be interested in advanced details on [import
+internals](/sentinel/extending/internals).
+
+## Modules
+
+Modules allow you to re-use Sentinel code as an import. Modules are loaded
+external of policies and mapped to imports. This is usually configured via a
+file such as the [CLI configuration file](/sentinel/configuration).
+
+See the [modules](/sentinel/extending/modules) section for more details.
+
+## Plugins
+
+-> **NOTE:** Plugins are currently only supported on the CLI and not in any
+production Sentinel integration, with the exception of existing configuration
+in [Nomad](https://www.nomadproject.io/docs/configuration/sentinel).
+
+Imports can also be implemented as plugins when Sentinel code itself is too
+restrictive to represent the import, or if you need access to features not
+normally available to the Sentinel runtime.
+
+See the [plugins](/sentinel/extending/plugins) section for more details.
+
+## Static Imports
+
+Static imports provide support for loading data files into the Sentinel runtime,
+making them available as an import.
+
+See the [static imports](/sentinel/extending/static-imports) section for more
+details.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/extending/internals.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/extending/internals.mdx
new file mode 100644
index 0000000000..11be9d3d67
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/extending/internals.mdx
@@ -0,0 +1,252 @@
+---
+page_title: >-
+ Extending Sentinel: Internals
+sidebar_current: docs-extending-internals
+description: This Section covers some details about Sentinel's import internals.
+layout: docs
+---
+
+# Extending Sentinel: Internals
+
+-> **Advanced Topic!** This page covers low-level technical details of Sentinel.
+You don't need to understand these details to effectively use Sentinel. The
+details are documented here for those who wish to learn about them.
+
+This section covers some of the internal details of Sentinel's import system.
+The goal of this section is to help remove notion of "magic" from Sentinel, to
+allow you to trust and understand what Sentinel is doing with imports, and also
+to help practitioners and developers alike understand the differences between
+the ways that imports can be loaded into Sentinel.
+
+## Language and Runtime
+
+Understanding the import system in Sentinel generally requires understanding of
+two key concepts within Sentinel: the _language_, and the _runtime
+implementation_ of the language.
+
+The Sentinel language and the rules governing it are detailed in the [Sentinel
+Language Specification](/sentinel/language/spec). A runtime implementation must
+conform to the rules laid out in the specification at a minimum. However, to
+meet the design goals of a particular implementation, the runtime may implement
+features on top of the language. The subtle implementation details within the
+runtime may also vary while still being compliant with the specification.
+
+The core runtime included within the [Sentinel CLI](/sentinel/commands) is
+sometimes referred to as the _reference implementation_ of the Sentinel
+language. This runtime is also the main runtime embedded within each
+Sentinel-enabled HashiCorp product such as Terraform Cloud and Enterprise, Vault
+Enterprise, Nomad Enterprise, and Consul Enterprise. The runtime includes an API
+to allow these integrations implement Sentinel in as robust or as restrictive of
+a way as possible, so depending on the implementation, your experience may vary.
+
+## Sentinel's Import Model
+
+The concept of Imports within Sentinel is a _language_ feature. You can see the
+exact rules governing imports within the
+[Imports](/sentinel/language/spec#imports) section of the specification.
+
+Within the runtime, there are currently two major classifications of imports:
+
+- **Modules:** The mapping of Sentinel code to an import, essentially mapping a
+ scope of values to a particular package.
+- **Binary Imports:** A grouping of embedded and external plugins written using
+ the Sentinel SDK. These are comprised of internal or embedded imports (usually
+ the [standard imports](/sentinel/imports) and imports included by an
+ integration) and import plugins.
+
+Below is a diagram explaining in brief the relationship between the langauge
+and runtime features.
+
+
+
+This kind of topology ultimately minimizes the visibility of implementation
+internals to the actual policy language. This allows us to keep the Sentinel
+language itself simple and keep concerns such as module or plugin management,
+validation, and configuration out of the language. These kinds of things would
+impact the readability of a policy, possibly present security challenges, and
+would ultimately be of no use to more minimal embedded applications.
+
+### Implementation Differences Between Import Types
+
+As one would imagine, both modules and binary imports are loaded in much
+different ways, and the methods that they expose data to Sentinel differ as
+well. The effect is generally the same however across both, and we take care to
+ensure that both modules and binary imports behave as close as possible to each
+other, however there are some subtle differences:
+
+- Modules currently support the exporting of rules, but do not support function
+ calls with object receiver data (as seen in some standard imports such as
+ [`decimal`](/sentinel/imports/decimal), [`http`](/sentinel/imports/http), and
+ [`time`](/sentinel/imports/time)). This will be coming in a later release.
+- Binary imports cannot export rules. However, they do support object receiver
+ data (see [`framework.New`](/sentinel/extending/plugins#framework-new) in
+ [Extending Sentinel: Plugins](/sentinel/extending/plugins)).
+
+## Modules
+
+_Modules_ are essentially Sentinel code that is intended to be re-used in multiple
+policies as an import.
+
+The mechanism of module loading is fairly straightforward: during initialization
+of the Sentinel runtime, modules are parsed along with policies. The parsed
+_abstract syntax tree_ (AST) for each module is loaded into the runtime under
+the specified _import path_. These are then passed to the lower-level
+interpreter during the evaluation of each policy.
+
+As with policy code, during evaluation, a module's AST is walked to execute the
+code within that specific module. The module data is then stored within an
+specific scope mapped to the import name. The module data can then be accessed
+through the import via selector expressions and function calls (e.g.,
+`foo.some_map` or `foo.some_func()` for a module loaded via `import "foo"`).
+
+The AST for a module is only walked if a policy imports it, and it is _only
+walked once_. That means if a policy and a module import the same module, or a
+policy imports the same module twice (using [import
+aliases](/sentinel/language/imports#aliases)), those instances will share state.
+If you call a function that alters a singleton variable within the module, that
+singleton will be modified for all instances of the import.
+
+-> **Fun fact!** When you [mock an import with Sentinel
+code](/sentinel/configuration#mocking-with-sentinel-code), you are actually
+using a module. The module is loaded with a flag that allows it to override an
+existing import, which would normally give a runtime error.
+
+### Map and List Cloning for Module Calls
+
+It's also worthwhile noting that map and list values are cloned for modules.
+
+Consider the case where you have a module with a map singleton.
+
+```sentinel
+// modules/foo.sentinel
+a_map = {"a": "b"}
+```
+
+Normally, assignment to the singleton, even when using an index expression, is
+blocked, so `foo.a_map["c"] = "d"` would not pass semantic checking.
+
+However, even when you try to do assignment via an intermediary, you will still
+be only modifying the copy, and the original will not be affected:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+a_map_copy = foo.a_map
+a_map_copy["c"] = "d" // Only modifies copy, does not modify module singleton
+print(foo.a_map) // Still only prints {"a": "b"}
+```
+
+This is to ensure that modules are consistent with binary imports in how return
+data is handled, and the expectation that most non-object data within an import
+is read-only, with the exception of modification of singleton data via
+functions. As return of complex object and collection data in a binary import is
+always via copy, it's not possible to modify any value elements within the
+import in a similar fashion.
+
+There are also some other implications of this cloning that are of note:
+
+- Rules are not cloned, either within a list or map, or by themselves. This is
+ to ensure that [memoization](/sentinel/language/rules#lazy-and-memoized)
+ functions correctly. As only a module can export rules at this time, there is no
+ parity for this in binary imports.
+- Functions are _not_ cloned. This mainly has implications for scope - for
+ example, if you assign a function to another value, or assign an object to a
+ value that has a method that utilizes a global module variable, those calls will
+ reference and/or modify those values.
+
+Functions, while represented differently in binary imports, generally follow the
+same logic here - as they would be invoked in the same package within the binary
+import, any singleton values they accessed there would be affected in a similar
+fashion. As rules are pseudo-functions, this generally makes sense here too.
+
+## Binary Imports
+
+_Binary imports_ is a grouping referring to all imports that are designed with
+the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). These can be
+either internally embedded, or executed via a plugin.
+
+All imports found in the [standard library](/sentinel/imports) are binary
+imports, embedded internally.
+
+The main difference between internally embedded imports and external plugins is
+whether or not the runtime needs to execute a plugin binary as part of
+[lifecycle management](#lifecycle), and whether or not it needs to
+[communicate](#communication) over RPC. Internal binary imports do not require
+these steps, so their execution model is somewhat simpler; however, technically,
+they still are the same as external plugins in terms of design model and value
+conversion. Most standard imports are even tested using the [SDK test
+suite](/sentinel/extending/plugins#testing), which builds the import as an
+external plugin.
+
+The remainder of this section discusses topics mostly relevant to external
+plugins. A large amount of technical detail regarding binary import development
+(also applicable to embedded binary imports) can be seen on the
+[Plugins](/sentinel/extending/plugins) page.
+
+-> **NOTE:** At this time, plugins can only be written for the Sentinel CLI, and
+cannot be used with any of Sentinel's integrations.
+
+### Plugin Basics
+
+Sentinel plugins are built on top of the HashiCorp [go-plugin
+system](https://github.com/hashicorp/go-plugin). This is the same plugin system
+powering all pluggable HashiCorp tools such as
+[Terraform](https://www.terraform.io), [Vault](https://www.vaultproject.io), and
+more. go-plugin is a system that has been used in production for millions of
+users for over 5 years.
+
+Plugins are executable binaries. Sentinel is responsible for launching and
+managing the lifecycle of plugins. Once a plugin is running, Sentinel
+communicates with the plugin via [gRPC](https://grpc.io/). The [protocol is open
+source](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/import.proto)
+to allow anyone to write a Sentinel plugin.
+
+### Lifecycle
+
+Sentinel launches plugins and is responsible for plugin lifecycle.
+
+The runtime optimizes for latency by launching the plugin as soon as it is
+configured, rather than when a policy requires it. This ensures that the plugin
+is ready to be used immediately. A plugin is only closed when Sentinel is
+reconfigured to no longer allow that plugin or if the Sentinel-enabled
+application is closing.
+
+When a plugin is removed from the configuration, Sentinel may wait to shut down
+the plugin until all currently executing policies that are using that plugin
+complete. New policy executions will not be allowed to use the old plugins.
+
+Plugins are automatically restarted if they shut down unexpectedly. Policies
+that were executing while this happens may fail. The Sentinel system is
+improving to more gracefully understand locations where it is safe to retry an
+execution.
+
+### Communication
+
+An [initial
+handshake](https://github.com/hashicorp/go-plugin/blob/master/docs/internals.md)
+is done via stdout to determine the main communication location. Following the
+handshake, communication will either occur on a local Unix domain socket or via
+a local-only TCP socket. This communication is done mostly over the low-level
+[`Get`](https://github.com/hashicorp/sentinel-sdk/blob/2b4db07dd12b55142f0c016f301af80aa4422520/proto/import.proto#L12)
+RPC call.
+
+#### Value Conversion
+
+As can generally be seen in [Developing an Import
+Plugin](/sentinel/extending/plugins#developing-an-import-plugin), the Sentinel
+type system is not exposed to binary imports. While explicit values for null and
+undefined exist, values are mostly converted over the wire from their Go values
+to their Sentinel equivalents.
+
+This has a few implications:
+
+- As discussed in [Map and List Cloning for Module
+ Calls](#map-and-list-cloning-for-module-calls), Map and list data returned from
+ binary import value lookups or function calls is always cloned. These can be
+ freely modified without affecting anything within the binary import.
+- It's currently impossible to represent certain Sentinel types such as
+ [rules](/sentinel/language/rules) within a binary import. This is not a major
+ issue at this point in time as practitioners generally do not look to binary
+ imports for rules; we may examine this as a later time if this situation
+ changes.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/extending/modules.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/extending/modules.mdx
new file mode 100644
index 0000000000..9846d263e5
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/extending/modules.mdx
@@ -0,0 +1,135 @@
+---
+page_title: >-
+ Extending Sentinel: Modules
+sidebar_current: docs-extending-modules
+description: >-
+ Modules allow you to re-use Sentinel code as an import.
+layout: docs
+---
+
+# Extending Sentinel: Modules
+
+Modules allow you to re-use Sentinel code as an import. This allows for the
+export of any general construct that Sentinel supports, including values,
+functions, and even rules. As such, it's a great way to package code that is
+useful across multiple policies, reducing boilerplate and making final policy
+code simpler.
+
+You may want to use modules for:
+
+- **Writing a simple re-usable helper library.** If you mostly know Sentinel or
+ have helpers that you have written in Sentinel, moving these helpers to a module
+ will offer a low-friction way of facilitating their re-use.
+- **Writing re-usable rules.** If you have a set of
+ [rules](/sentinel/language/rules) you know you want to use across multiple
+ policies, bundling them into a module is a great way of abstracting the logic
+ away.
+- **Abstracting existing Sentinel imports.** Using a module is a great way to
+ extend existing Sentinel imports by abstracting the common functionality that
+ you use within an import to your own workflow.
+
+This page describes how you can use modules within the context of the Sentinel
+CLI. For instructions for a particular integration, see the documentation for
+that product.
+
+-> **Not all Sentinel-enabled applications support modules.** Please refer
+to the documentation of the specific Sentinel-enabled application. Some
+applications disable modules for security reasons.
+
+## Configuring a Module
+
+A module is configured within the Sentinel CLI by adding an entry for it in
+within the `imports` section of the [CLI configuration
+file](/sentinel/configuration). The key for each entry specifies the path that
+the module is imported as.
+
+```hcl
+import "module" "foo" {
+ source = "modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "modules/bar.sentinel"
+}
+```
+
+In this example, we have two modules:
+
+- Import path `foo`, being loaded from the code within `modules/foo.sentinel`.
+- Import path `bar`, being loaded from the code within `modules/bar.sentinel`.
+
+See the [Import Modules](/sentinel/configuration#import-modules) section within
+the CLI configuration file syntax page for more details.
+
+### Remote Modules
+
+In addition to loading from a local source, modules can be loaded from a remote
+location. This can increase reusability and allow for sharing modules across
+multiple configurations.
+
+### Testing Using Modules
+
+As with [mocks](/sentinel/configuration#mocking-with-sentinel-code), modules
+are loaded relative to the configuration file location when using `sentinel test`. As such, you need to account for this in your test configuration files:
+
+```hcl
+import "module" "foo" {
+ source = "../../modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "../../modules/bar.sentinel"
+}
+```
+
+This could be a test file for a policy (example: `policy.sentinel`), residing
+under the correct test file directory for this policy (example:
+`test/policy/pass.json`).
+
+## Writing and Using a Module
+
+Writing a module is nearly the same as writing a Sentinel policy, except for the
+following two exceptions:
+
+- Modules do not require [`main`](/sentinel/writing/basic#the-simplest-policy)
+ to be present. It can be, but it will not carry any extra significance as it
+ does within a policy.
+- [`param`](/sentinel/language/parameters) declarations cannot be present in a module. If they are encountered,
+ a runtime error is given.
+
+Once you have a complete module ready for use and configured, using the module
+is as simple as importing it.
+
+Building on the above [configuration example](#configuring-a-module), we can
+export a simple test function in the module `foo` by adding this code to
+`modules/foo.sentinel`:
+
+```sentinel
+// modules/foo.sentinel
+hello = func() {
+ print("hello world!")
+ return undefined
+}
+```
+
+We can then import it within our policy and use it:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+// prints "hello world" in the trace
+foo.hello()
+
+main = true
+```
+
+Note that modules are considered [imports](/sentinel/language/spec#imports) by
+the Sentinel runtime and as such conform to the specification. This means that
+you cannot directly assign values to anything behind a module scope. You can,
+however, use functions to change state.
+
+-> **NOTE:** At this time, modules do not support object receiver data in the
+way that some imports do, like [`time`](/sentinel/imports/time),
+[`decimal`](/sentinel/imports/decimal), and [`http`](/sentinel/imports/http).
+Support for this will be coming in later versions of Sentinel.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/extending/plugins.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/extending/plugins.mdx
new file mode 100644
index 0000000000..e80de5d4b1
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/extending/plugins.mdx
@@ -0,0 +1,521 @@
+---
+page_title: >-
+ Extending Sentinel: Plugins
+sidebar_current: docs-extending-plugins
+description: >-
+ Plugins allow you to write imports as standalone executables, allowing you to
+ extend Sentinel in ways not normally possible with policy code alone.
+layout: docs
+---
+
+# Extending Sentinel: Plugins
+
+-> **NOTE:** Plugins are currently only supported on the CLI and not in any
+production Sentinel integration, with the exception of existing configuration
+in [Nomad](https://www.nomadproject.io/docs/configuration/sentinel).
+
+Plugins allow you to write imports as standalone executables, allowing you to
+extend Sentinel in ways not normally possible with policy code alone.
+
+You might want to write or use a plugin when you need to:
+
+- **Use a provider or API integration for Sentinel.** In this case, you will
+ probably be working with a provider SDK, or other libraries that will be making
+ complex network requests to external services. Sentinel only provides limited
+ capabilities for these kinds of operations in the standard library, so writing
+ this kind of import as a module is not optimal.
+- **Introduce novel functionality not currently supported by Sentinel.**
+ Sentinel's policy language is limited by design to encourage simple policies,
+ reduce its runtime footprint, and to keep it efficient and safe. This will
+ mean that some functionality may be difficult or impossible to express as
+ Sentinel code, and would require a plugin to write.
+- **Extend a complex module.** Additionally, modules are restricted to a single
+ file of Sentinel code, so these will naturally become more and more unwieldy as
+ you continue to add to them. Eventually, there might be a time where a plugin is
+ better suited for the functionality, especially if it's a general-purpose
+ library.
+
+This section details how import plugins are currently configured in the Sentinel
+CLI, and some detail on how they are written. As we continue to build on the
+ability to use import plugins, we will extend this section with more details.
+
+-> **Not all Sentinel-enabled applications will support plugins.** Some
+applications will disable plugins for security reasons. For those that do enable
+plugins, the method to configure plugins will vary.
+
+## Installing Plugins
+
+Sentinel plugins are standalone executables. Sentinel-enabled applications such
+as the CLI launch these plugins and communicate with them via
+[RPC](https://en.wikipedia.org/wiki/Remote_procedure_call). To install a plugin,
+the first step is to download the plugin executable.
+
+After downloading the plugin, you must add it in the CLI configuration file,
+setting its name, path, and any arguments, environment variables, or
+configuration. An example is below:
+
+```hcl
+import "plugin" "custom_time" {
+ source = "/path/to/sentinel-import-time"
+ config = { "fixed_time": 1504155600 }
+}
+```
+
+-> **NOTE:** We are using "custom_time" as standard import paths cannot be
+overriden. See [Configuration File Syntax](/sentinel/configuration#imports) for
+more details.
+
+After configuring the plugin, it will be available on the next `sentinel apply`
+or `sentinel test`.
+
+For more details on configuration, see the
+[plugins](/sentinel/configuration#plugins) section of the [CLI configuration
+file syntax](/sentinel/configuration).
+
+## Debugging Plugins
+
+The Sentinel CLI logs when it launches, configures, and closes a plugin. The
+plugin may also log additional information when it is running.
+
+To debug issues with a plugin, you can usually refer to these logs. Depending on
+the plugin configuration, the logs you see may vary - refer to the plugin
+documentation on how to enable logs for a particular plugin.
+
+## Developing an Import Plugin
+
+Anyone can develop a Sentinel import plugin using the [Sentinel
+SDK](https://github.com/hashicorp/sentinel-sdk). The SDK contains a high-level
+framework for writing plugins in [Go](https://golang.org) including a test
+framework. Additionally, the SDK contains the low-level [gRPC protocol buffers
+definition](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/plugin.proto)
+for writing plugins in other languages.
+
+The rest of this page will document how to write a new Sentinel plugin in Go
+using the Sentinel SDK and the high-level framework provided. You are expected
+to already know the basics of Go and have a properly configured Go installation.
+
+Throughout this page, we'll be developing a simple plugin to access time values.
+
+-> **NOTE:** The example here is not reflective of the actual implementation of
+the [`time`](/sentinel/imports/time) standard import, so make sure to not
+get the two confused.
+
+### Plugin Framework
+
+The Sentinel SDK provides a high-level
+[framework](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework)
+for writing plugins. We recommend using this framework.
+
+#### Basic Concepts
+
+This framework works by implementing the correct Go interfaces.
+
+As a general overview:
+[`framework.Plugin`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Plugin)
+implements the
+[`sdk.Plugin`](https://godoc.org/github.com/hashicorp/sentinel-sdk#Plugin)
+interface and therefore is a valid import plugin. You must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface to configure the plugin. The root may then delegate nested access to
+various
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+implementations. Other interfaces are implemented to provide functionality past
+what is provided by the basic `framework.Namespace` implementation - these are
+documented below.
+
+This all may sound like a lot of interfaces, but each interface typically only
+requires a single function implementation and you're only required to implement
+a single namespace (`framework.Namespace`).
+
+As we move on, we'll use real examples to make this clear.
+
+#### Implementing framework.Root
+
+To begin, you must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface. This is the interface representing the root of your plugin. The root
+itself must be implement
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace),
+which allows values to be accessed. Note that the root can optionally implement
+other interfaces, but they're more advanced and omitted here for simplicity.
+
+For our custom time example, our root may look like this:
+
+```sentinel
+type root struct {
+ time time.Time
+}
+
+// framework.Root impl.
+func (m *root) Configure(raw map[string]interface{}) error {
+ if _, ok := raw["timestamp"]; !ok {
+ raw["timestamp"] = time.Now().Unix()
+ }
+
+ v := raw["timestamp"]
+ timestamp, ok := v.(int)
+ if !ok {
+ return fmt.Errorf("invalid timestamp type %T", v)
+ }
+
+ m.time = time.Unix(timestamp, 0).UTC()
+ return nil
+}
+
+// framework.Namespace impl.
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "minute":
+ return m.time.Minute(), nil
+ }
+
+ return nil, nil
+}
+```
+
+This example implements `framework.Root` and `framework.Namespace` and would
+enable the following within a Sentinel policy once the completed plugin is
+installed.
+
+```sentinel
+import "custom_time"
+
+print(custom_time.minute)
+main = true
+```
+
+#### framework.Namespace
+
+The
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+interface implements a namespace of values. You saw above that
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+must itself be a namespace. However, a namespace `Get` implementation may
+further return namespaces to access nested values.
+
+This enables behavior such as `custom_time.month.string` vs. `custom_time.month.index`.
+Notice each of these examples accesses a nested value within `month`. This can
+be modeled as namespaces within the framework.
+
+In the example below, we return a new namespace for `month` to do just this:
+
+```sentinel
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "month":
+ return &namespaceMonth{Month: m.time.Month()}, nil
+ }
+
+ // ...
+}
+
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil nil
+}
+```
+
+#### Primitive Types and Structs
+
+A namespace may also return any primitive Go type as well as structs. The
+framework automatically exposes primitive values as you would expect. For
+structs, exported fields are lowercased and exposed to the policies. The
+`sentinel` struct tag can be used to control how Sentinel can access the field.
+
+In the example below, we expose a struct directly from the root namespace:
+
+```sentinel
+type Location struct {
+ Name string
+ TimeZone string `sentinel:"time_zone"`
+}
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "location":
+ return &Location{Name: "somewhere", TimeZone: "some zone"}, nil
+ }
+
+ // ...
+}
+```
+
+This makes the following values work within a Sentinel policy:
+`time.location.name`, `time.location.time_zone`.
+
+If a field has an empty `sentinel` struct tag (example: `sentinel:""`),
+then that field will not be accessible from a policy.
+
+#### Optional Interfaces
+
+The following are optional interfaces that can be implemented on top of
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace).
+All of these interfaces can be implemented at any level, except for
+[`framework.New`](#framework-new), which only works at the root level.
+
+##### `framework.Call`
+
+The
+[`framework.Call`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Call)
+interface can be implemented to support function calls.
+
+Implementing this interface is very similar to attribute access, except instead
+of returning the attribute value, you return a function. The framework uses Go
+reflection to determine the argument and result types and calls it. If the
+argument types do not match or the signature is otherwise invalid, an error is
+returned.
+
+For example, let's implement a function to add months to the current month:
+
+```sentinel
+func (m *root) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *root) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.time.AddDate(0, n, 0).Month()}
+}
+```
+
+You can now call the function. If today was in the month of September,
+the policy below would pass:
+
+```sentinel
+import "custom_time"
+
+main = custom_time.add_month(4).string == "January"
+```
+
+##### `framework.Map`
+
+The optional
+[`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+interface allows an alternative method to to present a namespace back to a
+Sentinel policy as a map.
+
+`framework.Map` is best paired with
+[`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys),
+which allows you to quickly fetch the necessary data via `Get` calls on the
+namespace:
+
+```
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil, nil
+}
+
+func (m *namespaceMonth) Map() (map[string]interface{}, error) {
+ return framework.MapFromKeys(m, []string{"string", "index"})
+}
+```
+
+##### `framework.New`
+
+The optional
+[`framework.New`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#New)
+interface can be implemented on a root namespace to add _methods_ to your
+namespaces.
+
+Data returned from a namespace is memoized and returned as a map, and is
+normally not callable - to call a function to operate on the data, you would
+need to create a new namespace from the top-level of the plugin, and then make a
+function call on the result within the same expression.
+
+Let's consider the [root example](#implementing-framework-root). Let's say,
+instead of hard-coding the time value at configuration, we wanted to load it
+on-demand using a `time.now` key, and allow `add_month` to be callable on that
+value. Our root and de-coupled time namespace would now look like:
+
+```
+type root struct{}
+
+func (m *root) Configure(raw map[string]interface{}) error { return nil }
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "now":
+ return &namespaceTime{Time: time.Now()}
+ }
+
+ return nil
+}
+
+func (m *root) New(data map[string]interface{}) (framework.Namespace, error) {
+ if v, ok := data["unix"]; ok {
+ if t, ok := v.(int64); !ok {
+ return &namespaceTime{Time: time.Unix(t, 0)}
+ }
+
+ return nil, fmt.Errorf("expected timestamp to be int64, got %T", v)
+ }
+
+ return nil, nil
+}
+
+type namespaceTime struct {
+ Time time.Time
+}
+
+func (m *namespaceTime) Get(key string) interface{} {
+ switch key {
+ case "unix":
+ return m.Time.Unix()
+
+ // case ...
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) Get(key string) (interface{}, error) {
+ return framework.MapFromKeys(m, []string{"unix", "..."})
+}
+
+func (m *namespaceTime) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.Time.AddDate(0, n, 0).Month()}
+}
+```
+
+Via this example, we can now create and assign a `namespaceTime` using `t = custom_time.now`, and then call `t.add_month(months_to_add)` to return a
+`namespaceMonth` for the corresponding month.
+
+Methods on a namespace can also _modfiy_ the receiver data. So you can, for
+example, add a method named `increment_month` that takes no arguments, but
+increments the time stored in `namespaceTime.Time` by a month. Subsequent
+statements using the receiver would see the new value.
+
+Note that there are some restrictions and guidelines that you should take into
+account when using `framework.New`:
+
+- Only data that makes it back to a policy can be used as receiver data to
+ instantiate new namespaces. As such it's recommended to make use of
+ [`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+ and
+ [`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys)
+ to return all of the attribute data you need to construct new objects.
+- `framework.New` is only supported on the root namespace and has no effect on
+ namespaces below the root. Check all possible cases of receiver data within
+ the top-level `New` function and return the appropriate namespace based on the
+ input data.
+- Do not return general errors from your `New` method. When an unknown lookup is
+ made off of memoized data, it will hit your `New` method for possible
+ instantiation and key calls. This will ensure `undefined` is correctly
+ returned for these cases.
+- `framework.New` is designed to add utility to plugins where calling methods on
+ a value is more intuitive than just making a function call, or just returning
+ all of a data set as a memoized map. Use it sparingly and avoid using it on
+ recursively complex data sets.
+
+### Testing
+
+The SDK exposes a testing framework to verify your plugin works as
+expected. This test framework integrates directly into `go test` so it
+is part of a familiar workflow.
+
+The test framework works by dynamically building your plugin and
+running it against the `sentinel` CLI to verify it behaves as expected.
+This ensures that your plugin builds, your plugin communicates via RPC
+correctly, and that the behavior within a policy is also correct.
+
+The example below shows a complete ready table-driven test for our time plugin.
+You can run this with a normal `go test`.
+
+```sentinel
+package main
+
+import (
+ "os"
+ "testing"
+
+ "github.com/hashicorp/sentinel-sdk"
+ plugintesting "github.com/hashicorp/sentinel-sdk/testing"
+)
+
+func TestMain(m *testing.M) {
+ exitCode := m.Run()
+ plugintesting.Clean()
+ os.Exit(exitCode)
+}
+
+func TestPlugin(t *testing.T) {
+ cases := []struct {
+ Name string
+ Data map[string]interface{}
+ Source string
+ }{
+ {
+ "month",
+ map[string]interface{}{"timestamp": 1495483674},
+ `main = subject.month.string == "September"`,
+ },
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.Name, func(t *testing.T) {
+ plugintesting.TestPlugin(t, plugintesting.TestPluginCase{
+ Config: tc.Data,
+ Source: tc.Source,
+ })
+ })
+ }
+}
+```
+
+### Building Your Plugin
+
+To build your plugin, you must first implement the `main` function.
+The main function should just use the `rpc.Serve` method to serve the
+plugin over the Sentinel RPC layer:
+
+```sentinel
+package main
+
+import (
+ "github.com/hashicorp/sentinel-sdk"
+ "github.com/hashicorp/sentinel-sdk/rpc"
+)
+
+func main() {
+ rpc.Serve(&rpc.ServeOpts{
+ PluginFunc: func() sdk.Plugin {
+ return &framework.Plugin{Root: &root{}}
+ },
+ })
+}
+```
+
+You can then build this using `go build`. The output can be named
+anything. Once your plugin is built, [install it](#installing-plugins)
+and try it out!
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/extending/static-imports.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/extending/static-imports.mdx
new file mode 100644
index 0000000000..cb51beb5d8
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/extending/static-imports.mdx
@@ -0,0 +1,48 @@
+---
+page_title: >-
+ Extending Sentinel: Static Imports
+sidebar_current: docs-extending-static-imports
+description: >-
+ Static imports allow you to write imports that use static data files.
+layout: docs
+---
+
+# Extending Sentinel: Static Imports
+
+Static imports allow you to write imports that use static data files. This can
+provide methods of consuming data in ways that are not available via
+[modules](./modules) or [plugins](./plugins).
+
+### Configuring a Static Import
+
+The [configuration page](/sentinel/configuration#static-imports) details how to
+configure static imports.
+
+### Usage
+
+Using a static import is not that different to using a plugin or module. The
+import must first be introduced to the policy via an import statement.
+
+```sentinel
+import "people"
+```
+
+From here, the static data can be used throughout the policy as you would any
+normal Sentinel value.
+
+```sentinel
+admins = filter people as person {
+ person.role is "Admin"
+}
+```
+
+One differentiator for static imports is that you can directly reference the
+import without the use of selectors. For example, to print the value;
+
+```sentinel
+print(people)
+```
+
+This is due to the static data being directly loaded and converted into a
+Sentinel value, ready for use. Static imports, like modules and plugins, cannot
+be assigned a new value.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/functions/append.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/functions/append.mdx
new file mode 100644
index 0000000000..81395154f6
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/functions/append.mdx
@@ -0,0 +1,46 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Append'
+sidebar_current: docs-funcs-append
+description: The built-in function `append` appends a value to the end of a list.
+layout: docs
+---
+
+# Builtin Function: append
+
+The built-in function `append` appends a value to the end of a list. The list
+is modified in-place. The return value will always be [undefined](/sentinel/language/undefined).
+
+`append` called on any value other than a list or undefined will result
+in an immediate error.
+
+Examples:
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+### Why does this function return undefined?
+
+This function returns `undefined` to avoid unintended side effects of the function in the context
+of a [rule](/sentinel/language/rules).
+
+For example, if `append` returned the list value as a result, the following policy would depend
+on the order in which rules are referenced:
+
+```sentinel
+list = []
+a = rule { append(list, 1) contains 1 }
+b = rule { append(list, 2) contains 2 }
+
+// Scenario 1: list becomes [1, 2]
+main = rule { a and b }
+
+// Scenario 2: list becomes [2, 1]
+main = rule { b and a }
+```
+
+This sort of side effect behavior introduces complex and difficult to track logical errors in programs.
+As a result, functions like this return `undefined` and modify values in-place.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/functions/delete.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/functions/delete.mdx
new file mode 100644
index 0000000000..3b60803997
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/functions/delete.mdx
@@ -0,0 +1,25 @@
+---
+page_title: 'Sentinel Language - Builtin Function: delete'
+sidebar_current: docs-funcs-delete
+description: The built-in function `delete` deletes an element from a map.
+layout: docs
+---
+
+# Builtin Function: delete
+
+The built-in function `delete` deletes an element from a map.
+
+If the element to delete does not exist, the map is left unchanged.
+Calling `delete` on a value that is not a map or
+[undefined](/sentinel/language/undefined)
+is an immediate error. The result of `delete` is always `undefined`.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/functions/error.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/functions/error.mdx
new file mode 100644
index 0000000000..96d253b662
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/functions/error.mdx
@@ -0,0 +1,15 @@
+---
+page_title: 'Sentinel Language - Builtin Function: error'
+sidebar_current: docs-funcs-error
+description: >-
+ The built-in function `error` is used to exit immediately with an error
+ message.
+layout: docs
+---
+
+# Builtin Function: error
+
+The built-in function `error` is used to exit immediately with an error message.
+
+Please see the [page on errors](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/functions/index.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/functions/index.mdx
new file mode 100644
index 0000000000..4e12143ad0
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/functions/index.mdx
@@ -0,0 +1,13 @@
+---
+page_title: Sentinel Language - Builtin Functions
+sidebar_current: docs-funcs
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Language: Builtin Functions
+
+Builtin functions are
+[functions](/sentinel/language/functions)
+that are available in all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in function.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/functions/keys.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/functions/keys.mdx
new file mode 100644
index 0000000000..a96cafe897
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/functions/keys.mdx
@@ -0,0 +1,22 @@
+---
+page_title: 'Sentinel Language - Builtin Function: keys'
+sidebar_current: docs-funcs-keys
+description: The built-in function `keys` returns the keys of a map.
+layout: docs
+---
+
+# Builtin Function: keys
+
+The built-in function `keys` returns the keys of a map.
+
+`keys` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned keys are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+```
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/functions/length.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/functions/length.mdx
new file mode 100644
index 0000000000..8b874b14c4
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/functions/length.mdx
@@ -0,0 +1,23 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Length'
+sidebar_current: docs-funcs-length
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Builtin Function: length
+
+The built-in function `length` returns the length of a collection or string.
+
+The length of a string is the number of bytes in the string. It is not
+necessarilly the number of characters. The length of a collection is the
+number of elements in that collection. The length of
+[undefined](/sentinel/language/undefined) is `undefined`.
+
+Examples:
+
+```sentinel
+length("foo") // 3
+length([1, 2, 3, 4]) // 4
+length({ "key": "value" }) // 1
+```
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/functions/print.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/functions/print.mdx
new file mode 100644
index 0000000000..bcf441d09d
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/functions/print.mdx
@@ -0,0 +1,13 @@
+---
+page_title: 'Sentinel Language - Builtin Function: print'
+sidebar_current: docs-funcs-print
+description: The built-in function `print` is used for logging and debugging.
+layout: docs
+---
+
+# Builtin Function: print
+
+The built-in function `print` is used for logging and debugging.
+
+Please see the [page on logging](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/functions/range.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/functions/range.mdx
new file mode 100644
index 0000000000..0f86bfc402
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/functions/range.mdx
@@ -0,0 +1,49 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Range'
+sidebar_current: docs-funcs-range
+description: The built-in function `range` returns a list of numbers in a range.
+layout: docs
+---
+
+# Builtin Function: range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it
+defaults to `1`.
+
+Negative steps are permitted and count down from `start` towards `end`, instead
+of up.
+
+The range ends when the next value given by the sum of the last value and `step`
+would exceed `end`, regardless of if it has been reached.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4] - starts at 1 instead of 0
+range(1, 5, 2) // [1,3] - 3+2 does not satisfy r[-1] < end
+range(0, -3, -1) // [0,-1,-2] - example of negative range
+```
+
+Impossible ranges, or ranges where `start` will never reach `end` given `step`,
+yield an empty list.
+
+```
+range(1, 1) // [] - already starting at 1
+range(0, 1, -1) // [] - negative step will never reach 1
+```
+
+Supplying an undefined value to any argument of `range` yields an undefined
+value back.
+
+A range with a zero step is a runtime error.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/functions/values.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/functions/values.mdx
new file mode 100644
index 0000000000..8f98d3ab56
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/functions/values.mdx
@@ -0,0 +1,22 @@
+---
+page_title: 'Sentinel Language - Builtin Function: values'
+sidebar_current: docs-funcs-values
+description: The built-in function `values` returns the values of a map.
+layout: docs
+---
+
+# Builtin Function: values
+
+The built-in function `values` returns the values of a map.
+
+`values` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned values are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+values(data) // [3, 2]
+```
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/imports/base64.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/imports/base64.mdx
new file mode 100644
index 0000000000..4e58429268
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/imports/base64.mdx
@@ -0,0 +1,46 @@
+---
+page_title: 'Import: base64'
+sidebar_current: docs-imports-base64
+description: The base64 import enables a Sentinel policy to encode and decode Base64 values.
+layout: docs
+---
+
+# Import: base64
+
+The base64 import enables a Sentinel policy to encode and decode Base64 values.
+
+### base64.encode(str)
+
+Encodes the string `str` into a Base64 encoded string, using the standard
+encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-4).
+
+```sentinel
+enc = base64.encode("foo") // "Zm9v"
+```
+
+### base64.decode(str)
+
+Decodes the Base64 encoded string `str`, returning the string result,
+using the standard encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-4).
+
+```sentinel
+dec = base64.decode("Zm9v") // "foo"
+```
+
+### base64.urlencode(str)
+
+Encodes the string `str` into a Base64 encoded string, using the alternate
+encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-5).
+
+```sentinel
+enc = base64.urlencode("foo") // "Zm9v"
+```
+
+### base64.urldecode(str)
+
+Decodes the Base64 encoded string `str`, returning the string result, using the
+alternate encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-5).
+
+```sentinel
+dec = base64.urldecode("Zm9v") // "foo"
+```
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/imports/decimal.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/imports/decimal.mdx
new file mode 100644
index 0000000000..a6633a38e0
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/imports/decimal.mdx
@@ -0,0 +1,318 @@
+---
+page_title: 'Import: decimal'
+sidebar_current: docs-imports-decimal
+description: |-
+ The decimal import provides functions for operating on numbers represented
+ as decimals.
+layout: docs
+---
+
+# Import: decimal
+
+The decimal import provides functions for operating on numbers represented
+as decimals.
+
+Binary floating-point numbers provided by the `float` type are unable to
+fully represent numbers like `1.1` and `2.2`. The decimal import can
+represent these numbers exactly, and so should be used when exactness is
+required.
+
+Decimals are created through the `new` function, which takes any type that
+can represent a number. Arguments to the decimal operators can also take
+a value of any of these types. The full list is:
+
+- float
+- int
+- string
+- existing decimal value
+
+### decimal.infinity(sign)
+
+Creates an infinite-value decimal. Infinite-value decimals are always larger
+or smaller, depending on sign, than any non-infinite decimals.
+
+```sentinel
+decimal.infinity(1) // +Infinity
+decimal.infinity(-1) // -Infinity
+```
+
+Also available as `decimal.inf`.
+
+### decimal.nan
+
+A decimal representing a "not-a-number" value. NaNs are not equal to any
+other number, even themselves.
+
+```sentinel
+decimal.new(-1).sqrt() // NaN
+decimal.new(0).mul(decimal.inf(1)) // NaN
+decimal.nan.is(decimal.nan) // false
+```
+
+### decimal.is_infinite(d)
+
+Evaluates to true if the decimal is infinite.
+
+```sentinel
+decimal.is_infinite(100) // false
+decimal.is_infinite("Infinity") // true
+```
+
+Also available as `decimal.is_inf`, `decimal.isinf` and `decimal.isinfinite`.
+
+### decimal.is_nan(d)
+
+Evaluates to true if the decimal is not-a-number.
+
+```sentinel
+decimal.is_nan("NaN") // true
+```
+
+Also available as `decimal.isnan`.
+
+### decimal.new(v)
+
+Constructs a decimal from another value.
+
+```sentinel
+decimal.new("1.1") // Constructs a decimal from a string.
+decimal.new(2.2) // Constructs a decimal from a float.
+decimal.new(3) // Constructs a decimal from an integer.
+decimal.new("1.1234E+400") // Constructs a decimal from a string using E-notation.
+```
+
+## Type: number
+
+Numbers are represented internally by a sign, coefficient, and exponent.
+The value is calculated with the formula `sign * coefficient * 10^exponent`.
+Exponent is limited to 32 bits, and the coefficient is limited by the total
+precision.
+
+The maximum precision a number can represent is 100 digits. This includes
+both before and after the decimal place.
+
+### number.string
+
+A string representation of the decimal.
+
+```sentinel
+decimal.new(1.5).string // 1.5
+```
+
+### number.sign
+
+A number between `-1` and `+1` representing the sign of the decimal.
+
+```sentinel
+decimal.new(1.5).sign // 1
+```
+
+### number.coefficient
+
+The coefficient component of the decimal.
+
+```sentinel
+decimal.new(1.5).coefficient // 15
+```
+
+### number.exponent
+
+The exponent component of the decimal.
+
+```sentinel
+decimal.new(1.5).exponent // -1
+```
+
+### number.float
+
+A floating-point representation of the decimal.
+
+```sentinel
+decimal.new(1.5).float // 1.500000
+```
+
+### number.int
+
+An integer representation of the decimal. This always rounds down to the nearest integer.
+
+```sentinel
+decimal.new(1.5).int // 1
+```
+
+### number.is(v)
+
+Test for equality with another value.
+
+```sentinel
+decimal.new(1.5).is(1) // false
+```
+
+### number.is_not(v)
+
+Test for inequality with another value.
+
+```sentinel
+decimal.new(1.5).is_not(1) // true
+```
+
+### number.less_than(v)
+
+Test that the decimal is less than another value.
+Can also be called as `number.lt(v)`
+
+```sentinel
+decimal.new(1.5).less_than(1) // false
+```
+
+### number.less_than_or_equals(v)
+
+Test that the decimal is less than or equals to another value.
+Can also be called as `number.lte(v)`
+
+```sentinel
+decimal.new(1.5).less_than_or_equals(1) // false
+```
+
+### number.greater_than(v)
+
+Test that the decimal is greater than another value.
+Can also be called as `number.gt(v)`
+
+```sentinel
+decimal.new(1.5).greater_than(1) // true
+```
+
+### number.greater_than_or_equals(v)
+
+Test that the decimal is greater than or equals to another value.
+Can also be called as `number.gte(v)`
+
+```sentinel
+decimal.new(1.5).greater_than_or_equals(1) // true
+```
+
+### number.add(v)
+
+Add another number to the decimal.
+
+```sentinel
+decimal.new(1.5).add(1) // 2.5
+```
+
+### number.subtract(v)
+
+Subtract another number from the decimal.
+Can also be called as `number.sub(v)`
+
+```sentinel
+decimal.new(1.5).subtract(1) // 0.5
+```
+
+### number.multiply(v)
+
+Multiply the decimal by a number.
+Can also be called as `number.mul(v)`
+
+```sentinel
+decimal.new(1.5).multiply(2) // 3.0
+```
+
+### number.divide(v)
+
+Divide the decimal by a number.
+Can also be called as `number.div(v)`
+
+```sentinel
+decimal.new(3.0).divide(1.5) // 2
+```
+
+### number.modulo(v)
+
+Find the remainder after dividing the decimal by a number.
+Can also be called as `mod`, `remainder`, or `rem`.
+
+```sentinel
+decimal.new(1.5).modulo(1) // 0.5
+```
+
+### number.power(v)
+
+Raise the decimal to a power.
+Can also be called as `number.pow(v)`
+
+```sentinel
+decimal.new(1.5).power(2) // 2.25
+```
+
+### number.exp()
+
+The natural exponent of the decimal. This calculates `e^number`.
+
+```sentinel
+decimal.new(1.5).exp() // 4.4816...
+```
+
+### number.loge()
+
+The natural logarithm of the decimal.
+Can also be called as `number.ln()`
+
+```sentinel
+decimal.new(1.5).loge() // 0.4054...
+```
+
+### number.log10()
+
+The base-10 logarithm of the decimal.
+Can also be called as `number.log()`
+
+```sentinel
+decimal.new(1.5).log10() // 0.1760...
+```
+
+### number.square_root()
+
+The square root of the decimal.
+Can also be called as `number.sqrt()`
+
+```sentinel
+decimal.new(1.5).square_root() // 1.2247...
+```
+
+### number.ceiling()
+
+Round the decimal to the smallest integer greater or equal to itself.
+Can also be called as `number.ceil()`
+
+```sentinel
+decimal.new(1.5).ceiling() // 2
+```
+
+### number.floor()
+
+Round the decimal to the largest integer less than or equal to itself.
+
+```sentinel
+decimal.new(1.5).floor() // 1
+```
+
+### number.absolute()
+
+The absolute value of the decimal.
+Can also be called as `number.abs()`
+
+```sentinel
+decimal.new(1.5).absolute() // 1.5
+decimal.new(-1.5).absolute() // 1.5
+```
+
+### number.negate()
+
+The negated value of the decimal. A positive decimal will become negative,
+and a negative decimal will become positive.
+Can also be called as `number.neg()`
+
+```sentinel
+decimal.new(1.5).negate() // -1.5
+decimal.new(-1.5).negate() // 1.5
+```
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/imports/http.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/imports/http.mdx
new file mode 100644
index 0000000000..f94ae98c27
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/imports/http.mdx
@@ -0,0 +1,370 @@
+---
+page_title: 'Import: http'
+sidebar_current: docs-imports-http
+description: The http import enables the use of HTTP-accessible data from outside the runtime in Sentinel policy rules.
+layout: docs
+---
+
+# Import: http
+
+The http import enables the use of HTTP-accessible data from outside
+the runtime in Sentinel policy rules.
+
+The simplest example of the import in use would be:
+
+```sentinel
+import "http"
+
+resp = http.get("https://example.hashicorp.com")
+main = rule { resp.body contains "something" }
+```
+
+By default, any request response that is not a successful "200 OK" HTTP
+response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information and
+how to customize this behavior.
+
+For more advanced requests which require setting request headers, use a
+[`request` type](#type-request):
+
+```sentinel
+import "http"
+import "json"
+
+param token
+
+req = http.request("https://example.hashicorp.com").with_header("Authorization", "token "+token)
+resp = json.unmarshal(http.get(req).body)
+main = rule { resp["some_key"] is true }
+```
+
+The `with_header` function above returns the `request` object with the
+`Authorization` HTTP header set to value of the variable `some_value`. Many
+of the methods within the http import API are designed around this concept
+of method chaining, allowing for complex building of HTTP requests
+encapsulated in functions. The following is an idiomatic example further
+demonstrating this pattern:
+
+```sentinel
+import "http"
+import "json"
+
+param github_user
+param github_token
+
+client = func(req) {
+ return http.with_timeout(5).with_retries(3)
+}
+
+github_request = func(path) {
+ full_url = "https://api.github.com" + path
+ return http.request(full_url).
+ with_basic_auth(github_user, github_token).
+ with_header("Accept", "application/vnd.github.v3+json").
+ with_header("Custom", "foo"),
+}
+
+default_response = client.get(github_request("/example"))
+
+# Override the Custom header for this single request
+custom_header_response = json.unmarshal(
+ client.get(
+ github_request("/example").with_header("Custom", "bar"),
+ ),
+)
+
+# Override the timeout value for a known slower request
+slow_response = json.unmarshal(
+ client.with_timeout(15).get(github_request("/slow-endpoint")),
+)
+
+some_key_is_true = rule { json.unmarshal(default_response.body)["some_key"] is true }
+body_contains_text = rule { custom_header_response.body contains "something" }
+response_is_ok = rule { slow_response.status_code is 200 }
+
+main = rule { some_key_is_true and body_contains_text and response_is_ok }
+```
+
+### http.client
+
+Retrieve the base HTTP client with default settings. The returned client may be
+configured by calling configuration functions on it; all of these configuration
+functions on are also aliased as top-level functions in this namespace. See
+[`client`](#type-client) below.
+
+### http.request(url)
+
+Create a new [`request`](#type-request) to the specified `url` (string). A
+`request` type enables customization of headers and other concerns before then
+providing the constructed `request` to [`get()`](#client-get-url_or_request),
+[`post()`](#client-post-url_or_request) or [`send()`](#client-send-request).
+
+```sentinel
+req = http.request("example.hashicorp.com/foo").with_header("Foo", "bar")
+resp = http.get(req)
+```
+
+### http.methodPost
+
+Returns a string containing the accepted verb for POST requests, `"POST"`.
+
+### http.methodGet
+
+Returns a string containing the accepted verb for GET requests, `"GET"`.
+
+## Type: client
+
+All of the following configuration functions on the default client are also
+aliased as top-level functions in the import. This allows a short-hand (and
+idiomatic) way of configuring a new client without having to directly access
+`http.client` each time:
+
+```sentinel
+# The following two lines are semantically the same:
+resp = http.client.with_timeout(5).get(url)
+resp = http.with_timeout(5).get(url)
+```
+
+### url_or_request
+
+The [`get()`](#client-get-url_or_request) and [`post()`](#client-post-url_or_request)
+functions accept either a URL string or a request object, noted as
+`url_or_request` throughout the documentation.
+
+When `url_or_request` is a string, a request is made with the URL
+specified by the string. To customize HTTP headers and other settings, pass
+a request. By default, a request has a timeout value of 10 seconds and 1
+retry. These settings can be adjusted with [`with_timeout()`](#clientwith_timeoutinteger) and
+[`with_retries()`](#clientwith_retriesinteger), respectively.
+
+### Redirects
+
+If the response is one of the following redirect codes, the client follows the
+redirect, up to a maximum of 10 redirects:
+
+- 301 (Moved Permanently)
+- 302 (Found)
+- 303 (See Other)
+- 307 (Temporary Redirect)
+- 308 (Permanent Redirect)
+
+If the redirect limit is exceeded, the result is a policy error.
+
+By default, after any redirects are followed (up to the maximum), any request
+response that is not a successful "200 OK" response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information
+and how to customize this behavior.
+
+### client.get(url_or_request)
+
+Issue a GET request with a URL string or a `request` type. Returns a `response`
+type.
+
+```sentinel
+import "http"
+
+resp = http.get("https://example.hashicorp.com")
+main = rule { resp.body contains "something" }
+```
+
+### client.post(url_or_request)
+
+Issue a POST request with a URL string or a `request` type. Returns a `response`
+type.
+
+```sentinel
+import "http"
+
+req = http.request("https://example.hashicorp.com")
+ .with_body(json.marshal({"foo": "bar"}))
+resp = http.post(req)
+main = rule { resp.body contains "something" }
+```
+
+### client.send(request)
+
+Make a request using the supplied `request` type. Returns a `response`.
+
+```sentinel
+import "http"
+
+req = http.request("https://example.hashicorp.com")
+resp = http.send(req)
+main = rule { resp.body contains "something" }
+```
+
+### client.accept_status_codes(list)
+
+Configures a client to not automatically cause a policy failure if
+the resulting response's status code is in `list`. Any status code received
+that is not present in this list will trigger a runtime error.
+
+By default, any request response that is not a successful "200 OK" response
+causes a policy error. This is for convenience, as the most common scenario
+by far is to receive a response and immediately signal a policy error if the
+status code is not 200. Use this scoping to specify a list of non-redirect
+HTTP codes that you wish to accept without error and evaluate the response
+yourself.
+
+Redirects are not considered final status codes in this context. That is,
+redirects are always followed up to the maximum allowed value (see [`Redirects`](#redirects))
+and the final response code in the redirect chain is validated against
+`list`.
+
+```sentinel
+resp = http.accept_status_codes([200, 404]).get(url)
+main = rule { resp.status_code is 404 }
+```
+
+### client.accepted_status_codes
+
+Returns a list of the client's accepted status codes.
+
+```sentinel
+client = http.accept_status_codes([200, 404])
+main = rule { client.accepted_status_codes contains 404 }
+```
+
+### client.with_retries(integer)
+
+Sets the maximum number of retries, specified by `integer`, that should be
+attempted on an unsuccessful request. An unsuccessful request is considered
+to be any 5xx response except `501 (Not Implemented)` or a request timeout.
+By default, one retry is attempted.
+
+The maximum number of retries is 10, for a total of 11 request attempts.
+When a request exceeds the maximum number of retries without a response, the
+result is a policy error. Specifying a number larger than this will result
+in a runtime error.
+
+Between each retry, an exponentially increasing delay is observed, allowing
+time for the server to recover. This delay starts at 1 second for the first
+retry and increases each attempt thereafter. The maximum delay for a single
+attempt is 30 seconds.
+
+Retries can be disabled by specifying 0.
+
+### client.retries
+
+Returns the client's configured number of retries as an integer.
+
+### client.with_timeout(integer)
+
+Sets the request timeout to the number of seconds indicated by `integer`.
+
+Each individual request performed by the HTTP client will be limited by the
+given request timeout. This timeout includes connection time, any redirects,
+and reading the response body.
+
+A maximum of 30 seconds is allowed. Specifying a number larger than this
+will result in a runtime error.
+
+By default, requests will time out after 10 seconds.
+
+### client.timeout
+
+Returns the client's configured timeout in whole seconds as an integer.
+
+### client.without_certificate_verification()
+
+Skips verification of TLS certificates during secure HTTP operations,
+allowing for requests to `https://` endpoints which are secured with a TLS
+certificate signed by an untrusted certificate authority. Takes no arguments.
+
+By default, the HTTP client will trigger a runtime error if a TLS certificate
+presented by a server is from an untrusted certificate authority.
+
+Do not change this setting unless you are aware of the risks involved.
+Without verification, TLS accepts any certificate presented by the server
+and any host name in that certificate. In this mode, TLS is susceptible to
+man-in-the-middle attacks. This option should only be used for testing or in
+trusted networks with self-signed certificates.
+
+```sentinel
+http.without_certificate_verification().get(url)
+```
+
+### client.certificate_verification
+
+Returns true if the client requires TLS certificates to be verifiable.
+
+## Type: request
+
+### request.url
+
+Returns the request URL as a string.
+
+### request.headers
+
+Returns the configured request headers as a string-keyed map of strings.
+
+### request.body
+
+Returns the configured body as a string.
+
+### request.with_header(key, value)
+
+Returns a `request` with the header `key` (string) set to `value` (string).
+
+Values are overridden on subsequent calls to the same `key`. To specify
+multiple values, use the existing value when setting the new one.
+
+```sentinel
+original = http.request("http://example.hashicorp.com").with_header("Foo", "bar")
+overridden = original.with_header("Foo", "baz")
+multi_value = original.with_header("Foo", original.headers["Foo"] + ",baz")
+
+main = rule {
+ original.headers["Foo"] is "bar" and
+ overridden.headers["Foo"] is "baz" and
+ multi_value.headers["Foo"] is "bar,baz"
+}
+```
+
+### request.with_basic_auth(username, password)
+
+Returns a `request` with the `Authorization` header set to use HTTP Basic
+Authentication with the provided username and password.
+
+Note that with HTTP Basic Authentication the provided username and password
+are encoded, but not encrypted.
+
+### request.with_body(body)
+
+Returns a `request` with the `body` set. This value will then be sent as a body
+as part of the request. Commonly used along with the [`post()`](#client-post-url_or_request) function.
+
+```sentinel
+req = http.request("http://example.hashicorp.com")
+ .with_header("Content-Type", "application/json")
+ .with_body(json.marshal({ "foo": "bar" }))
+
+resp = http.post(req)
+```
+
+## Type: response
+
+### response.status_code
+
+The response status code as an integer, e.g. `200`.
+
+### response.headers
+
+The response headers as a map.
+
+### response.body
+
+The response body as a string. Callers should unmarshal the content to a useful
+representation as necessary based on the content type. For example, if the
+response is in JSON:
+
+```sentinel
+import "http"
+import "json"
+
+# This example endpoint returns {"some_key": true}
+req = http.request("https://example.hashicorp.com/some-json")
+
+resp = json.unmarshal(http.get(req).body)
+main = rule { keys(resp) contains "some_key" and resp["some_key"] is true }
+```
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/imports/index.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/imports/index.mdx
new file mode 100644
index 0000000000..e45bf81b81
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/imports/index.mdx
@@ -0,0 +1,18 @@
+---
+page_title: Sentinel Language - Standard Imports
+sidebar_current: docs-imports
+description: >-
+ Standard Imports are the built-in imports that are available to all Sentinel
+ policies.
+layout: docs
+---
+
+# Standard Imports
+
+Standard Imports are the built-in [imports](/sentinel/language/imports)
+that are available to all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in import.
+
+Sentinel-embedded applications can choose to allow or deny certain
+standard imports. Please reference the documentation for the Sentinel-enabled
+application you're using to determine if all standard imports are available.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/imports/json.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/imports/json.mdx
new file mode 100644
index 0000000000..3355458f14
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/imports/json.mdx
@@ -0,0 +1,31 @@
+---
+page_title: 'Import: json'
+sidebar_current: docs-imports-json
+description: The json import enables a Sentinel policy to parse and access a JSON document.
+layout: docs
+---
+
+# Import: json
+
+The json import enables a Sentinel policy to parse and access a JSON
+document.
+
+### json.unmarshal(obj)
+
+Unmarshals the JSON object `obj` into a native Sentinel structure.
+
+All native JSON types can be represented perfectly as Sentinel native types.
+
+```sentinel
+// Typically the input for this would come from an external source.
+config = json.unmarshal("{ \"foo\": 42 }")
+config.foo // 42
+config.bar // undefined (as usual for accessing a non-existent map key)
+```
+
+### json.marshal(obj)
+
+Marshals the Sentinel object `obj` into a JSON object encoded as a string.
+
+Functions and `undefined` cannot be natively encoded as JSON. If values of
+either type are found in the structure, this will return undefined.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/imports/runtime.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/imports/runtime.mdx
new file mode 100644
index 0000000000..6493bbbb22
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/imports/runtime.mdx
@@ -0,0 +1,16 @@
+---
+page_title: 'Import: runtime'
+sidebar_current: docs-imports-runtime
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: runtime
+
+The runtime import contains various information about the Sentinel runtime. It
+can be used to get information about the runtime as it's embedded in the CLI or
+a specific integration, such as validating a specific runtime version.
+
+### runtime.version
+
+Returns the version of Sentinel within this implementation.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/imports/sockaddr.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/imports/sockaddr.mdx
new file mode 100644
index 0000000000..a6842ba5f0
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/imports/sockaddr.mdx
@@ -0,0 +1,41 @@
+---
+page_title: 'Import: sockaddr'
+sidebar_current: docs-imports-sockaddr
+description: The sockaddr import makes working with IP addresses easy.
+layout: docs
+---
+
+# Import: sockaddr
+
+The sockaddr import makes working with IP addresses easy.
+
+### sockaddr.is_contained(outer, inner)
+
+The is_contained function returns true if `inner` is an address that
+is contained within `outer`.
+
+```sentinel
+sockaddr.is_contained("192.168.0.0/24", "192.168.0.32") // true
+sockaddr.is_contained("192.168.0.0/24", "192.174.1.1") // false
+```
+
+### sockaddr.is_equal(addr1, addr2)
+
+The is_equal function returns true if addr1 is equal to addr2.
+
+```sentinel
+sockaddr.is_equal("192.168.12.24", "192.168.12.24") // true
+```
+
+### sockaddr.is_ipv4(addr)
+
+The is_ipv4 function returns true if addr is an IPv4 address.
+
+```sentinel
+sockaddr.is_ipv4("192.168.12.24") // true
+sockaddr.is_ipv4("2001:0db8:85a3:0000:0000:8a2e:0370:7334") // false
+```
+
+### sockaddr.is_ipv6(addr)
+
+The is_ipv6 function returns true if addr is an IPv6 address.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/imports/strings.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/imports/strings.mdx
new file mode 100644
index 0000000000..823c5d8f07
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/imports/strings.mdx
@@ -0,0 +1,91 @@
+---
+page_title: 'Import: strings'
+sidebar_current: docs-imports-strings
+description: The strings import exposes common string operations.
+layout: docs
+---
+
+# Import: strings
+
+The strings import exposes common string operations.
+
+### strings.has_prefix(s, prefix)
+
+Returns true if `s` starts with `prefix`.
+
+```sentinel
+strings.has_prefix("billing-id", "billing-") // true
+strings.has_prefix("bill-id", "billing-") // false
+```
+
+### strings.has_suffix(s, suffix)
+
+Returns true if `s` ends with `suffix`.
+
+```sentinel
+strings.has_suffix("billing-id", "id") // true
+strings.has_suffix("billing-name", "id") // false
+```
+
+### strings.join(a, sep)
+
+Joins a list with the string supplied by `sep`.
+
+Multi-dimensional lists are supported, and non-string primitive
+types will be converted to their string equivalents. Maps and
+other complex non-list types are not supported.
+
+```sentinel
+strings.join(["foo", "bar", "baz"], ".") // "foo.bar.baz"
+strings.join([["foo", "bar"], "baz"], ".") // "foo.bar.baz"
+strings.join(["a", 1, 1.01, true], ",") // "a,1,1.01,true"
+```
+
+### strings.trim_prefix(s, prefix)
+
+Trim the prefix from the string `s`. If the string doesn't have the
+prefix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_prefix("billing-id", "billing-") // "id"
+strings.trim_prefix("bill-id", "billing-") // "bill-id"
+```
+
+### strings.trim_suffix(s, suffix)
+
+Trim the suffix from the string `s`. If the string doesn't have the
+suffix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_suffix("billing-id", "-id") // "billing"
+strings.trim_suffix("bill-id", "-foo") // "bill-id"
+```
+
+### strings.to_lower(s)
+
+Lowercase the string s.
+
+```sentinel
+strings.to_lower("FoO") // "foo"
+```
+
+### strings.to_upper(s)
+
+Uppercase the string s.
+
+```sentinel
+strings.to_upper("FoO") // "FOO"
+```
+
+### strings.split(s, sep)
+
+Split the string 's' via a substring 'sep'. If 'sep' is not contained in
+'s', the return value will be a single-element list containing only 's'.
+As a special-case, if the input is already a list, it will be returned
+as-is. This allows calling the split function when not knowing if the input
+is already a list or not.
+
+```sentinel
+strings.split("foo,bar,baz", ",") // ["foo", "bar", "baz"]
+strings.split(["foo", "bar", "baz"], ",") // ["foo", "bar", "baz"]
+```
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/imports/time.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/imports/time.mdx
new file mode 100644
index 0000000000..3a675e2bbd
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/imports/time.mdx
@@ -0,0 +1,257 @@
+---
+page_title: 'Import: time'
+sidebar_current: docs-imports-time
+description: The time import provides access to the execution time and time functions.
+layout: docs
+---
+
+# Import: time
+
+The time import provides access to the execution time and time functions.
+
+During the execution of a policy the time is fixed to the moment of
+execution. This gives the illusion that a policy instantly executes at a
+single moment of time.
+
+The import uses Coordinated Universal Time (UTC).
+
+As an example of this, if a policy accessed `time.now.second` multiple times,
+for each access the same value would be returned even if they are several seconds apart.
+This is the execution `timespace`, which is mapped to `time.now`.
+
+It is also possible to run functions on a custom time by using the
+`time.load()` function to create a new timespace from the specified value.
+See the documentation for available actions on timespaces.
+
+Times specified as the reference time or via the `time.load()` function can
+be specified in a `timeish` fashion. This is either one of:
+
+- The number of seconds since the Unix epoch (Thursday, 1 January 1970,
+ 00:00:00 UTC),
+- A timestamp matching the format outlined in RFC3339, either in the
+ format `2006-01-02T15:04:05+07:00`, which includes a timezone offset, or
+ `2006-01-02T15:04:05Z`, which indicates UTC.
+
+### time.now
+
+Create a `timespace` locked either to execution time or, if set, the
+reference time. In a given execution, this will always produce the same
+value.
+
+```sentinel
+time.now // {"day": 17, "hour": 23, "minute": 19, "month": 11 ... }
+```
+
+### time.load(timeish)
+
+Create a timespace using the given value. Please see the import
+documentation for valid values for "timeish".
+
+```sentinel
+time.load("2019-11-16T01:20:30Z") // {"day": 16, "hour": 1, "minute": 20, "month": 11 ... }
+```
+
+### time.nanosecond
+
+A nanosecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.nanosecond * 1) // 2019-11-16T01:20:30.000000001Z
+```
+
+### time.microsecond
+
+A microsecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.microsecond * 1) // 2019-11-16T01:20:30.000001Z
+```
+
+### time.millisecond
+
+A millisecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.millisecond * 1) // 2019-11-16T01:20:30.001Z
+```
+
+### time.second
+
+A second duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.second * 1) // 2019-11-16T01:20:31Z
+```
+
+### time.minute
+
+A minute for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.minute * 1) // 2019-11-16T01:21:30Z
+```
+
+### time.hour
+
+An hour duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+## Type: timespace
+
+### timespace.hour
+
+The hour from 0 to 23.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").hour // 1
+```
+
+### timespace.minute
+
+The minute from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").minute // 20
+```
+
+### timespace.second
+
+The second from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").second // 30
+```
+
+### timespace.day
+
+The day of the month, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").day // 16
+```
+
+### timespace.month
+
+The month of the year as an integer, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month // 11
+```
+
+### timespace.month_name
+
+The name of the month of the year, in English. Example: `January`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month_name // November
+```
+
+### timespace.weekday
+
+The weekday as an integer, where 0 is Sunday and 6 is Saturday.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday // 6
+```
+
+### timespace.weekday_name
+
+The name of the day of the week, in English. Example: `Sunday`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday_name // Saturday
+```
+
+### timespace.year
+
+The year as an integer.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").year // 2019
+```
+
+### timespace.unix
+
+The number of seconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix // 1573867230
+```
+
+### timespace.unix_nano
+
+The number of nanoseconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix_nano // 1573867230000000000
+```
+
+### timespace.zone
+
+The timezone offset, in seconds. This can be a negative number to indicate
+time west of UTC.
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone // -28800
+```
+
+### timespace.zone_string
+
+The timezone offset, in the format `+HH:MM` (example: `-08:00` for PST).
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone_string // -08:00
+```
+
+### timespace.before(timeish_or_timespace)
+
+Check if the time in the timespace is before the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").before("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.after(timeish_or_timespace)
+
+Check if the time in the timespace is after the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").after("2019-11-15T01:20:30Z") // true
+```
+
+### timespace.equal(timeish_or_timespace)
+
+Check if two times are equivalent
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").equal("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.add(duration)
+
+Add a duration in nanoseconds to the time in the timespace and return a new timespace. The
+duration can be negative. The duration should be specified as an integer
+multiplied with the correct unit.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+### timespace.sub(timeish_or_timespace)
+
+Subtract a time from the time in the timespace and return a duration in nanoseconds.
+
+```sentinel
+// Gives a duration of 3 hours.
+duration = time.load(
+ "2019-11-16T01:20:30Z",
+).sub("2019-11-16T04:20:30Z")
+
+// Returns 2019-11-16T01:20:30Z
+time.load(
+ "2019-11-16T04:20:30Z",
+).add(duration)
+```
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/imports/types.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/imports/types.mdx
new file mode 100644
index 0000000000..fcfdbd2ae5
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/imports/types.mdx
@@ -0,0 +1,35 @@
+---
+page_title: 'Import: types'
+sidebar_current: docs-imports-types
+description: The types import enables a Sentinel policy to parse an object's type.
+layout: docs
+---
+
+# Import: types
+
+The types import enables a Sentinel policy to parse an object's type.
+
+### types.type_of(obj)
+
+Returns the object's type as a string
+
+```sentinel playground
+import "types"
+
+// Typically the inputs would come from an external source.
+is_boolean = rule { types.type_of(true) is "bool" }
+is_string = rule { types.type_of("Hello!") is "string" }
+is_integer = rule { types.type_of(42) is "int" }
+is_float = rule { types.type_of(42.123) is "float" }
+is_null = rule { types.type_of(null) is "null" }
+is_undefined = rule { types.type_of(undefined) is "undefined" }
+
+main = rule {
+ is_boolean and
+ is_string and
+ is_integer and
+ is_float and
+ is_null and
+ is_undefined
+}
+```
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/imports/units.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/imports/units.mdx
new file mode 100644
index 0000000000..e2ad3442f9
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/imports/units.mdx
@@ -0,0 +1,39 @@
+---
+page_title: 'Import: units'
+sidebar_current: docs-imports-units
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: units
+
+The units import provides access to quick calculations for various byte
+units.
+
+Note that this import uses base-2 terminology:
+
+- One kilobyte is 1024 bytes
+- One megabyte is 1024^2 = 1048576 bytes
+- One gigabyte is 1024^3 = 1073741824 bytes
+- One terabyte is 1024^4 = 1099511627776 bytes
+- One petabyte is 1024^5 = 1125899906842624 bytes
+
+### units.kilobyte
+
+The base-2 representation of a kilobyte (1024 bytes).
+
+### units.megabyte
+
+The base-2 representation of a megabyte (1048576 bytes).
+
+### units.gigabyte
+
+The base-2 representation of a gigabyte (1073741824 bytes).
+
+### units.terabyte
+
+The base-2 representation of a terabyte (1099511627776 bytes).
+
+### units.petabyte
+
+The base-2 representation of a petabyte (1125899906842624 bytes).
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/imports/version.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/imports/version.mdx
new file mode 100644
index 0000000000..59366c1087
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/imports/version.mdx
@@ -0,0 +1,151 @@
+---
+page_title: 'Import: version'
+sidebar_current: docs-imports-version
+description: |-
+ The version import provides functions for parsing versions and version constraints,
+ and verifying versions against a set of constraints.
+layout: docs
+---
+
+# Import: version
+
+The version import provides functions for parsing versions and version constraints,
+and verifying versions against a set of constraints.
+
+Versions are created through the `new` function, which accepts a string type in dotted-tri format (i.e. 1.0.0-alpha.1+001) that can represent a version.
+
+### version.new(v)
+
+Constructs a version from string value.
+
+```sentinel
+version.new("1.0.0-alpha.1+001")
+```
+
+### version.major
+
+An integer representation of the Major number for a given version .
+
+```sentinel
+version.new("1.0.0-alpha.1+001").major // 1
+```
+
+### version.minor
+
+An integer representation of the Minor number for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").minor // 0
+```
+
+### version.patch
+
+An integer representation of the Patch number for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").patch // 0
+```
+
+### version.prerelease
+
+A string representation of the Prerelease for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").prerelease // "alpha.1"
+```
+
+### version.metadata
+
+A string representation of the Metadata for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").metadata // "001"
+```
+
+### version.version
+
+A string representation of the version including pre-release and metadata information.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").version // "1.0.0-alpha.1+001"
+```
+
+### version.greater_than(v)
+
+Tests that the version is greater than a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").greater_than("0.12.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").gt("1.0.1-alpha.1+001") // False
+```
+
+### version.greater_than_or_equals(v)
+
+Tests that the version is greater than or equal to a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").greater_than_or_equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").gte("1.0.1-alpha.1+001") // False
+```
+
+### version.less_than(v)
+
+Tests that the version is less than a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").less_than("1.0.1-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").lt("0.12.0-alpha.1+001") // False
+```
+
+### version.less_than_or_equals(v)
+
+Tests that the version is less than or equal to a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").less_than_or_equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").lte("0.12.0-alpha.1+001") // False
+```
+
+### version.less_than_or_equals(v)
+
+Tests that the version equals a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").eq("0.12.0-alpha.1+001") // False
+```
+
+### version.compare(v)
+
+Compares one version with a given version. Compare returns -1, 0, or 1 if the version is less, equal, or greater than the other version, respectively.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").compare("0.12.0-alpha.1+001") // -1
+version.new("1.0.0-alpha.1+001").compare("1.0.0-alpha.1+001") // 0
+version.new("0.12.0-alpha.1+001").cmp("1.0.0-alpha.1+001") // 1
+```
+
+### version.satisfies(v, x)
+
+Tests that a version adheres to one or more version constraints. Satisfies supports the following restriction operators:
+
+- =
+- !=
+- \>
+- <
+- \>=
+- <=
+- ~>
+
+Satisfies supports the following usage scenarios:
+
+- A constraint with a pre-release can only match a pre-release version that contains the same major, minor and patch identifiers.
+- A constraint without a pre-release can only match a version without a pre-release.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").satisfies("> 1.0.0-alpha.1+001") // false
+version.new("1.0.0-alpha.1+001").satisfies(">= 1.0.0-alpha.1+001") // true
+version.new("1.0.0-alpha.1+001").satisfies(">= 1.0.0-alpha.1+001, < 1.0.0-beta+001") // true
+version.new("1.0.0").sat("= 1.0.0") // true
+version.new("0.12.7").sat("~> 0.12.0") // true
+```
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/index.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/index.mdx
new file mode 100644
index 0000000000..4497b40b83
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/index.mdx
@@ -0,0 +1,31 @@
+---
+layout: 'docs'
+page_title: 'Documentation'
+sidebar_current: 'docs-home'
+description: |-
+ Sentinel is a language and framework for policy built to be embedded in existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+[introduction](/sentinel/intro) to understand what Sentinel is, how it
+compares to other software, and more.
+
+
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/language/arithmetic.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/language/arithmetic.mdx
new file mode 100644
index 0000000000..06b1256a1b
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/language/arithmetic.mdx
@@ -0,0 +1,67 @@
+---
+page_title: Sentinel Language - Arithmetic
+sidebar_current: docs-language-arith
+description: >-
+ Sentinel supports arithmetic operators for integers and floats. Sentinel
+ supports sum, difference, product, quotient, and remainder.
+layout: docs
+---
+
+# Language: Arithmetic
+
+Sentinel supports arithmetic operators for integers and floats. Sentinel
+supports sum, difference, product, quotient, and remainder.
+
+```plaintext
++ sum
+* difference
+* product
+/ quotient
+% remainder
+```
+
+These operators work in a typical infix style:
+
+```sentinel
+4 + 8 // 12
+8 * 2 // 16
+8 / 4 // 2
+8 / 5 // 1
+8 % 5 // 3
+```
+
+## Order of Operations
+
+Arithmetic follows a standard mathematical order of operations.
+Grouping with parentheses can be used to affect ordering.
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+(4 + 5) * 2 // 18
+```
+
+A full table of operator precendence can be found on the
+[boolean expressions page](/sentinel/language/boolexpr). This
+shows how arithmetic operators relate to other operators.
+
+## Integer Division
+
+Integer division with a remainder rounds down to the nearest integer.
+Example: `8 / 3 is 2`.
+
+If the divisor is zero, an error occurs.
+
+## Mixed Numeric Operations
+
+Mixed numeric operations between integer and floating-point values are
+permitted. The result is a floating-point operation with the integer converted
+to a floating-point value for purposes of calculation.
+
+```sentinel
+import "types"
+
+a = 1.1 + 1 // 2.1
+types.type_of(a) // "float"
+```
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/language/boolexpr.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/language/boolexpr.mdx
new file mode 100644
index 0000000000..f1c518c3bf
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/language/boolexpr.mdx
@@ -0,0 +1,225 @@
+---
+page_title: Sentinel Language - Boolean Expressions
+sidebar_current: docs-language-boolexpr
+description: >-
+ Boolean expressions are expressions that evaluate to a boolean value from the
+ result of a combination of one or more comparisons and logical operators.
+layout: docs
+---
+
+# Language: Boolean Expressions
+
+Boolean expressions are expressions that evaluate to a boolean value
+from the result of a combination of one or more comparisons and logical
+operators. Boolean expressions form the basis of policy since policy can be broken
+down to a set of logical decisions that turn into true or false.
+
+A single boolean expression is the body of a [rule](/sentinel/language/rules).
+Additionally, boolean expressions are used with [conditionals](/sentinel/language/conditionals),
+and more.
+
+## Order of Operations
+
+The precedence of operators is shown below. Operators with higher
+precedence values (larger numbers) are evaluated first. Operators with
+the same precedence value are evaluated left-to-right.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Examples of this precedence are shown below:
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+```
+
+## Logical Operators
+
+Logical operators are used to change or combine logical expressions.
+There are three binary operators `and`, `or`, and `xor`. There is
+a single unary operator `not` (which can also be specified as `!`).
+
+The binary operators require two boolean operands and the unary
+operator requires a single boolean operand. In both case, the operands
+are values or expressions that are boolean types.
+
+Below is a basic explanation of the logical operators directly from
+the specification:
+
+```sentinel
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+Using parentheses to group boolean expressions, you can combine
+boolean expressions to become more complex or affect ordering:
+
+```sentinel
+(p and q) or r
+p and (q or r)
+```
+
+## Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+For any comparison, the two operands must be equivalent types. The one
+exception are integers and floats. When an integer is compared with
+a float, the integer is promoted to a floating point value.
+
+The available comparison operators are:
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+The behavior of `is` with `==` and `is not` with `!=` is identical.
+
+Example comparisons:
+
+```sentinel
+name is "Mitchell"
+idnumber > 42
+idnumber <= 12
+```
+
+Using the logical operators, these can be combined:
+
+```sentinel
+name is "Mitchell" and idnumber > 42
+```
+
+The language specification provides more detail on exactly
+[how comparison behaves](/sentinel/language/spec#comparison-operators).
+
+## Set Operators
+
+The set operators `contains` and `in` test for inclusion in a collection
+(a list or map).
+
+Set operators may 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.
+
+`contains` tests if the left-hand collection contains the right-hand value.
+`in` tests if the right-hand collection contains the left-hand value. For
+maps, "contains" looks at the keys, not the values.
+
+Examples:
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+```
+
+## Matches Operator
+
+The `matches` operator tests if a string matches a regular expression.
+The `matches` operator can be negated by prefixing the operator with
+`not`, such as `not matches`.
+
+The left-hand value is the string to test. The right-hand value is
+a string value representing a regular expression. The syntax of the regular
+expression is the same general syntax used by Python, Ruby, and other languages.
+The precise syntax accepted is the syntax accepted by RE2.
+
+Regular expressions are not anchored by default; any anchoring must be
+explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+## Any, All Expressions
+
+`any` and `all` expressions allow testing that any or all elements of a
+collection match some boolean expression. The result of an `any` and `all`
+expression is a boolean.
+
+`any` returns the boolean value `true` if any value in the collection expression
+results in the body expression evaluating to `true`. If the body expression
+evalutes to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression
+result in the body expression evaluating to `true`. If any value in the
+collection expression result in the body expression evaluating to `false`,
+the `all` expression returns `false`.
+
+For empty collections, `any` returns `false` and `all` returns `true`.
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" }
+any group.tasks as t { t.driver is "vmware" }
+```
+
+Since `any` and `all` expressions are themselves boolean expressions, they
+can be combined with other operators:
+
+```sentinel
+any ["a", "b"] as char { char is "a" } or
+other_value is "another"
+```
+
+## Emptiness Comparison
+
+The expressions `is empty` and `is not empty` provide a convenience
+method for determining the emptiness of a value. It supports the same types
+as the [built-in length](/sentinel/functions/length), collections and strings.
+
+```sentinel
+[] is empty // true
+[] is not empty // false
+["foo"] is empty // false
+["foo"] is not empty // true
+undefined is empty // undefined
+undefined is not empty // undefined
+```
+
+## Defined Comparison
+
+The expressions `is defined` and `is not defined` provide a convenience
+method for determining if a value has been defined. In other words, any value
+other than `undefined` can be considered as `defined`.
+
+```sentinel
+[] is defined // true
+4 is defined // true
+true is defined // true
+{} is defined // true
+undefined is defined // false
+[] is not defined // false
+4 is not defined // false
+true is not defined // false
+undefined is not defined // true
+```
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/language/collection-operations.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/language/collection-operations.mdx
new file mode 100644
index 0000000000..41da8f3584
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/language/collection-operations.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Sentinel Language - Collection Operations
+sidebar_current: docs-language-collection-operations
+description: |-
+ Operations for interacting with collections (list, map).
+layout: docs
+---
+
+# Language: Collection Operations
+
+Collection operations are expressions that are performed on a list or map to
+return a variation of the initial data.
+
+At the moment, `filter` is the only collection operation available.
+
+## Filter Expression
+
+`filter` is a [quantifier
+expression](/sentinel/language/spec#quantifier-expressions-any-all-filter-map) that
+returns a subset of the provided collection. Only elements whose filter body
+returns true will be returned. If any of the elements filter body returns
+undefined, the final result will be undefined.
+
+Filter uses the same syntax as the `any` and `all` [boolean
+expressions](/sentinel/language/boolexpr#any-all-expressions):
+
+```sentinel
+filter list as value { condition } // Single-iterator, list
+filter list as idx, value { condition } // Double-iterator, list
+
+filter map as key { condition } // Single-iterator, map
+filter map as key, value { condition } // Double-iterator, map
+```
+
+Examples:
+
+```sentinel
+l = [1, 1, 2, 3, 5, 8]
+evens = filter l as v { v % 2 is 0 } // [2, 8]
+
+m = { "a": "foo", "b": "bar" }
+matched_foo = filter m as _, v { v is "foo" } // { "a": "foo" }
+```
+
+## Map Expression
+
+`map` is a [quantifier
+expression](/sentinel/language/spec#quantifier-expressions-any-all-filter-map) that
+returns a list, regardless of the input collection type. Each element within the
+input collection is evaluted according to the map expression body and appended
+to the result.
+
+```sentinel
+l = [1, 2]
+r = map l as v { v % 2 } // [false, true]
+
+m = { "a": "foo", "b": "bar" }
+r = map m as k, v { v } // ["foo", "bar"]
+```
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/language/conditionals.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/language/conditionals.mdx
new file mode 100644
index 0000000000..1736795dbb
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/language/conditionals.mdx
@@ -0,0 +1,161 @@
+---
+page_title: Sentinel Language - Conditionals
+sidebar_current: docs-language-conditional
+description: |-
+ Conditional statements allow your policy to behave differently depending on a condition.
+layout: docs
+---
+
+# Language: Conditionals
+
+Conditional statements allow your policy to behave differently depending
+on a condition.
+
+Conditional statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## If Statements
+
+`if` statements only execute their bodies if a condition is met.
+The syntax of an `if` statement is:
+
+```sentinel
+if condition {
+ // ... this is executed if condition is true
+}
+```
+
+The `condition` must result in a boolean, such as by calling a function
+or evaluating a [boolean expression](/sentinel/language/boolexpr). If
+the `condition` is `true`, the body (within the `{}`) is executed. Otherwise,
+the body is skipped.
+
+Examples:
+
+```sentinel
+// This would execute the body
+value = 12
+if value is 18 {
+ print("condition met")
+}
+
+// Direct boolean values can be used
+value = true
+if value {
+ print("condition met")
+}
+
+// This would not execute the body since the boolean expression will
+// result in undefined.
+value = {}
+if value["key"] > 12 {
+ print("condition met")
+}
+```
+
+### Else, Else If
+
+An `else` clause can be given to an `if` statement to execute a body
+in the case the condition is _not met_. By putting another `if` statement
+directly after the `else`, multiple conditions can be tested for.
+The syntax is:
+
+```sentinel
+if condition {
+ // ...
+} else {
+ // ...
+}
+
+if condition {
+ // ...
+} else if other_condition {
+ // ...
+} else {
+ // ...
+}
+```
+
+### Scoping
+
+The body of an `if` statement does not create a new
+[scope](/sentinel/language/scope). Any variables assigned within the body
+of an if statement will modify the scope that the `if` statement itself
+is in.
+
+Example:
+
+```sentinel
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+```sentinel
+a = 18
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+## Case Statements
+
+`case` statements are a selection control mechanism that execute a clause based
+on matching expressions. It is worth noting that the expression for `case` is
+optional. When no expression is provided, it defaults the expression to `true`.
+Additionally, the order of clauses is important, as they are evaluated from top
+to bottom, executing the first match.
+The syntax of a case statement is:
+
+```sentinel
+case expression {
+ when clause_expression:
+ // executed when clause_expression and expression are equal
+ else:
+ // executed if no clause matches expression
+}
+```
+
+### When Clause
+
+Any clause that has an expression for comparison must use the `when` keyword.
+It accepts a list of expressions, seperated by a `,`.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+}
+```
+
+```sentinel
+case {
+ when x > 40:
+ return true
+}
+```
+
+### Else Clause
+
+The `else` keyword allows for capturing any expressions that have no matching
+`when` clause.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+ else:
+ return false
+}
+```
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/language/functions.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/language/functions.mdx
new file mode 100644
index 0000000000..8214078b6b
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/language/functions.mdx
@@ -0,0 +1,229 @@
+---
+page_title: Sentinel Language - Functions
+sidebar_current: docs-language-functions
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Functions
+
+Functions allow you to create reusable code to perform computations.
+
+Below is a trivial example function that doubles a number and shows
+the main rule using the function:
+
+```sentinel playground
+double = func(x) {
+ return x * 2
+}
+
+main = rule { double(12) is 24 }
+```
+
+Functions may contain any expressions,
+[conditionals](/sentinel/language/conditionals),
+and [loops](/sentinel/language/loops). They must return a value
+at the end of their execution. If no reasonable return value exists,
+the function should return [undefined](/sentinel/language/undefined).
+
+## Function Types
+
+### Named Functions
+
+-> **NOTE:** Named functions must be created within the [package scope](/sentinel/language/scope#package-scope).
+
+Named functions are declared using the `func` keyword as its own statement.
+They provide a safe method of creating functions and have additional
+restrictions that do not apply to anonymous functions.
+
+Firstly, named functions cannot be re-assigned, and also cannot use a name
+that is already used elsewhere. For instance, the below example will error due
+to the attempt to reassign the named function identifier to a new value:
+
+```sentinel
+func sum(a, b) {
+ return a + b
+}
+
+sum = 4
+```
+
+Additionally, the following will error due to the named function attempting
+to make use of an already assigned identifier:
+
+```sentinel
+sum = 4
+
+func sum(a, b) {
+ return a + b
+}
+```
+
+Named functions are helpful for policy authors to declare critical functions
+whose value or implementation should not be changed.
+
+### Anonymous Functions
+
+An anonymous function is created by assigning a variable to a `func`. The
+variable can be reassigned at any time including to different value types.
+Anonymous functions are helpful for use cases like closures, where a function
+can return another function.
+
+```sentinel
+func makeAdder(a) {
+ return func(b) {
+ return a + b
+ }
+}
+```
+
+## Creating a Function
+
+A function can have zero or more parameters. These parameters are specified
+within parentheses `()` separated by commas. If a function has no parameters,
+the parentheses must still be present.
+
+A function must terminate with a `return` statement to return a value back to
+the caller. Only a single value can be returned. The type of a return can
+vary between return statements.
+
+Anonymous function example:
+
+```sentinel
+add1 = func(x) { return x + 1 }
+```
+
+Named function example:
+
+```sentinel
+func add1(x) {
+ return x + 1
+}
+```
+
+Both examples create a function that adds 1 to the parameter `x`.
+
+## Calling a Function
+
+Functions are called by accessing their name followed by parentheses with
+a list of comma-separated values for the parameters. If the function takes no
+parameters, an empty set of parentheses must still be used to denote a
+function call.
+
+To call the function in the example above:
+
+```sentinel
+x = 1
+y = add1(x)
+```
+
+## Scoping
+
+The body of a `func` creates a new [scope](/sentinel/language/scope).
+
+The parent scope is the scope in which the function is created. This allows
+for the creation of functions within functions that can access their outer
+scopes.
+
+Example:
+
+```sentinel
+f = func() {
+ a = 42
+ print(a) // 42
+ return undefined
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+f = func() {
+ a = 42
+ return undefined
+}
+
+print(a) // 18
+```
+
+And below is an example of creating a function in a function which uses
+outer values:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double()
+}
+
+print(f()) // 84
+```
+
+A more complex example below shows how scoping works when passing
+functions around as arguments or results:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double
+}
+
+double = f()
+print(double()) // 84
+```
+
+## Pass By Value
+
+The parameters to a function are passed by value, but not deep copied.
+This means that elements of collections can still be modified but the
+root value cannot be changed.
+
+Example:
+
+```sentinel
+f = func(x) {
+ x = "value"
+ return x
+}
+
+x = "outside"
+f(x)
+print(x) // "outside"
+```
+
+```sentinel
+f = func(x) {
+ append(x, "value")
+ return x
+}
+
+x = []
+f(x)
+print(x) // ["value"]
+```
+
+## Recursion
+
+A function is allowed to call other functions, including itself.
+This can be used to implement recursion. For example, the fibonacci sequence
+is implemented below:
+
+```sentinel
+fib = func(x) {
+ if x <= 0 {
+ return undefined
+ }
+
+ if x == 1 {
+ return 1
+ } else {
+ return x + fib(x - 1)
+ }
+}
+```
+
+Note that this example also shows using
+[undefined](/sentinel/language/undefined)
+as a return value in cases where a function has undefined behavior.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/language/imports.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/language/imports.mdx
new file mode 100644
index 0000000000..0ed1541b85
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/language/imports.mdx
@@ -0,0 +1,116 @@
+---
+page_title: Sentinel Language - Imports
+sidebar_current: docs-language-imports
+description: >-
+ Imports enable a Sentinel policy to access reusable libraries and external
+ data and functions.
+layout: docs
+---
+
+# Language: Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external
+data and functions. The exact list of available imports is dependent on the
+host system. Host systems may also make the available imports configurable
+via plugins.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that
+the import `calendar` allows access to engineer calendars. This import
+only exists for the purpose of this example and at the time of writing is
+not a real available import.
+
+```sentinel
+import "calendar"
+
+// Get the calendar for Bob for today
+bob_calendar = calendar.for("bob").today
+
+// Allow this policy to pass if Bob is not on vacation.
+main = rule { not bob_calendar.has_event("vacation") }
+```
+
+This example shows an import being used to deny a behavior if Bob is on
+vacation. Hopefully real policies do not depend on a single person being
+in the office, but the example shows the power of imports.
+
+In addition to consuming imports, you can also
+[extend Sentinel by writing custom imports](/sentinel/extending)
+This allows you to add any arbitrary behavior to your Sentinel-protected
+systems.
+
+## Importing
+
+Whether accessing a built-in library or an external plugin, the syntax
+for an import is the same:
+
+```sentinel
+import "name"
+```
+
+This adds the import with the name `name`. It can be referenced using
+the identifier `name`. For example, if the import above exposed first
+and last name data, you could access it via `name.first` or `name.last`.
+
+You can shadow imports by assigning a variable. In the example below,
+the import `name` becomes unavailable within the function because it is
+shadowed by a variable of the same name.
+
+Shadowing an import is not the same as overwriting a variable. Imports
+are not part of the [scope](/sentinel/language/scope), meaning that
+the shadow only exists for the scope it is created within. For everything
+else, the import works even after it is shadowed. The example below shows that
+as well.
+
+```sentinel
+import "name"
+
+f = func() {
+ name = "jane"
+ return name
+}
+
+print(f()) // "jane"
+print(name.first) // "bob"
+```
+
+## Aliases
+
+An import can be aliased to another name using the syntax below:
+
+```sentinel
+import "name" as other
+```
+
+This would make the import "name" available as `other`.
+
+An import may only be imported once, regardless of aliases. The following
+would be **invalid** and would result in an error:
+
+```sentinel
+import "name" as one
+import "name" as two
+```
+
+## Accessing Import Data
+
+Imports are accessed with dot-separated identifiers, such as `name.first` or
+`name.stage.first`. These are called _selector expressions_. An import may
+return nested data that further can be accessed via selector expressions.
+
+In the first example on this page with the "calendar" import, we see this:
+
+```sentinel
+import "calendar"
+
+a = calendar.for("bob") // selector expression calendar.for
+b = a.today // selector expression on the result
+c = b.vacation // accessing further nested data
+```
+
+The exact structure of an import is dependent on the import. Please reference
+the documentation for an import to learn more.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/language/index.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/language/index.mdx
new file mode 100644
index 0000000000..7f588c94cd
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/language/index.mdx
@@ -0,0 +1,114 @@
+---
+page_title: Sentinel Language
+sidebar_current: docs-language-basics
+description: |-
+ Sentinel policies are written using the Sentinel language. This language
+ is easy to learn and easy to write. You can learn the Sentinel language
+ and be productive within an hour. Learning Sentinel doesn't require any
+ formal programming experience.
+layout: docs
+---
+
+# Sentinel Language
+
+Sentinel policies are written using the Sentinel language. This language
+is easy to learn and easy to write. You can learn the Sentinel language
+and be productive within an hour. Learning Sentinel doesn't require any
+formal programming experience.
+
+This language guide will contain many details that are not necessary to
+be productive with Sentinel or are targeted to those who are looking for
+exact information about the language (such as what types of line endings are
+allowed). You can safely ignore these sentences.
+
+You may also view the
+[official language specification](/sentinel/language/spec)
+This is a specific and detailed document on the syntax and behavior of
+the language primarily intended for implementation creators and to disambiguate
+the language.
+
+## Simplest Example
+
+The example below is about the simplest practical example of Sentinel.
+It is reasonable to imagine this as a realistic policy. This shows that
+in most cases, Sentinel will be extremely simple:
+
+```sentinel
+main = rule { request.method is "GET" and request.headers contains "X-Key" }
+```
+
+## Files
+
+Sentinel policies are single files that end in the `.sentinel` file extension.
+There is currently no built-in mechanism to Sentinel for merging multiple
+files. This is purposefully done to make Sentinel policies easy to submit
+to systems that support Sentinel policies.
+
+Sentinel policy files must be UTF-8 encoded and can end in both
+Unix (LF) or Windows (CRLF) line breaks. When running the
+[auto-formatter](#),
+line endings will always use Unix line breaks.
+
+## Ordering
+
+Sentinel policies are executed top-down. For example:
+
+```sentinel
+a = 1 // a = 1 here
+b = a + 1 // b = 2 here
+a = 3 // a = 3, b = 2
+```
+
+In this example, the value of `a` and `b` is shown at each line. Since Sentinel
+executes values top-down, the final value of `a` is 3 and `b` is 2. `b` _does
+not_ become 4.
+
+## Main
+
+Sentinel expects there to be a `main` [rule](/sentinel/language/rules).
+The value of this rule is the result of the entire policy.
+
+The result of the policy depends on the evaluated contents of the `main` rule.
+For booleans, a policy passes on a `true` value, and fails on a `false` value.
+Other types generally follow a zero or zero-length pattern for determining
+success.
+
+| Type | Passing Condition | Failing Condition |
+| ------- | ----------------- | -------------------------- |
+| Boolean | `true` | `false` |
+| String | `""` | Any non-zero length string |
+| Integer | `0` | Any non-zero value |
+| Float | `0.0` | Any non-zero value |
+| List | `[]` | Any non-zero length list |
+| Map | `{}` | Any non-zero length map |
+
+A value of main that falls outside of the above types will result in a policy
+error. As a special case, if `main` evaluates to an undefined value, the error
+message will indicate as such, with a reference to where the undefined value was
+encountered.
+
+## More Complex Example
+
+The simple example above is a full working example. In our experience with
+Sentinel, many policies can be representing using this simple form. However,
+to show more features of the language, a more complex example is shown below.
+This example is also a realistic example of what Sentinel may be used for.
+
+```sentinel
+import "units"
+
+memory = func(job) {
+ result = 0
+ for job.groups as g {
+ for g.tasks as t {
+ result += t.resources.memory else 0
+ }
+ }
+
+ return result
+}
+
+main = rule {
+ memory(job) < 1 * units.gigabyte
+}
+```
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/language/lists.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/language/lists.mdx
new file mode 100644
index 0000000000..bc5269d8eb
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/language/lists.mdx
@@ -0,0 +1,135 @@
+---
+page_title: Sentinel Language - Lists
+sidebar_current: docs-language-lists
+description: Lists are a collection of zero or more values.
+layout: docs
+---
+
+# Language: Lists
+
+Lists are a collection of zero or more values.
+
+Lists can be created using by wrapping values in `[]` and separating them
+by commas. An optional trailing comma is allowed. List elements can be
+differing types. Examples:
+
+```sentinel
+[] // An empty list
+["foo"] // Single element list
+["foo", 1, 2, true] // Multi element list with different types
+["foo", [1, 2]] // List containing another list
+```
+
+A list can be [sliced](/sentinel/language/slices) to create sublists.
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for value inclusion in a list.
+
+## Accessing Elements
+
+List elements can be accessed with the syntax `name[index]` where
+`index` is zero-indexed.
+
+A negative index accesses the list in reverse. It is the same as
+reversing a list and then using a positive index. Similar to a positive
+index, it is bounded by the length of the list as a negative value.
+
+Accessing beyond the length of the list results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+a = ["foo", 1, true, [1, 2]]
+
+a[0] // "foo"
+a[2] // true
+a[4] // undefined
+a[-2] // true
+a[-4] // "foo"
+a[-5] // undefined
+a[3][1] // 2
+```
+
+## List Append
+
+Values can be appended to a list using the built-in
+[append](/sentinel/functions/append) function.
+
+This modifies the list in-place and returns
+[undefined](/sentinel/language/undefined). For more information on
+why `append` behaves this way, please read the full documentation for the
+[append](/sentinel/functions/append) function.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append([1,2], "foo") // [1, 2, "foo"]
+append([1,2], [3]) // [1, 2, [3]]
+append(1, 3) // error()
+```
+
+## List Concatenation
+
+Two lists can be concatenated using the `+` operator or the shorthand
+`+=` assignment operator. For the `+` operator, a new list is returned.
+For `+=`, the left-hand list is modified in place.
+
+Examples:
+
+```sentinel
+[1] + [2] // [1, 2]
+[1] + [[1]] // [1, [1]]
+[1] + 1 // error
+
+a = [1]
+a += [2] // a = [1, 2]
+a += 3 // error
+```
+
+## List Length
+
+The length of a list can be retrieved using the
+[length](/sentinel/functions/length) function.
+
+Examples:
+
+```sentinel
+length([]) // 0
+length(["foo"]) // 1
+```
+
+## Removing Items From a List
+
+You can use a combination of [list concatenation](#list-concatenation) and
+[slicing](/sentinel/language/slices) to remove elements from a list.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+a = a[:2] + a[3:] // [1, 2, 4, 5]
+```
+
+The shorthand shown here is effectively the same as `a[0:2] + a[3:length(a)]`,
+which creates a new list out of the concatenation two sub-lists composed of the
+first two elements, and the rest of the list starting at index 3. This
+effectively removes the 3rd element from the list (index 2).
+
+## List Comparison
+
+Lists may be [compared](/sentinel/language/boolexpr#comparison-operators)
+for equality. Lists are equal if they are of equal length and their
+corresponding elements are comparable and equal. Lists with the same elements
+but in a different order are not equal.
+
+```sentinel
+[1, 2] is [1, 2] // true
+[1, 2] is [2, 1] // false
+["a"] is ["a", "b"] // false
+["a", ["b", "c"]] is ["a", ["b", "c"]] // true
+```
+
+List comparison speed is O(N), meaning that the speed of the comparison is
+_linearly_ proportional to the number of elements in the list. The more
+elements, the more iterations that are necessary to verify equality.
+
+-> The N-value quoted above should account for the sum of the elements of _all_
+lists in the subjects of comparison, as list comparison will recurse into these
+lists to check for equality.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/language/logging-errors.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/language/logging-errors.mdx
new file mode 100644
index 0000000000..705a72cbd4
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/language/logging-errors.mdx
@@ -0,0 +1,52 @@
+---
+page_title: Sentinel Language - Logging and Errors
+sidebar_current: docs-language-log
+description: The built-in functions `print` and `error` can be used for logging and errors.
+layout: docs
+---
+
+# Language: Logging and Errors
+
+The built-in functions `print` and `error` can be used for logging
+and errors. Logging is helpful to understand how a policy is executing
+in development. And errors are useful to halting execution immediately in
+certain scenarios.
+
+## Print
+
+The `print` function is used to output debug information. The exact location
+where this print statements go is dependent on the host application and
+the Sentinel runtime itself.
+
+The `print` function takes zero or more Sentinel values as arguments.
+Each value is formatted for human-friendly output and space-separated.
+Example:
+
+```sentinel
+value = 42
+print("the value is", value) // the value is 42
+
+map = { "foo": false }
+print(map) // { "foo": false }
+
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // false
+```
+
+## Errors
+
+Certain actions in Sentinel, such as accessing an undefined variable,
+attempting to add two incompatible types, etc. result in _runtime errors_.
+These errors halt the execution of a policy immediately. The pass/fail result
+of a policy is considered fail.
+
+Runtime errors can be manually triggered using the `error` function.
+The `error` function behaves exactly like the `print` function except that
+execution is halted immediately.
+
+This should only be used in scenarios where the policy _must fail_ due to
+some unexpected or invalid condition. The line between `error` and
+[undefined](/sentinel/language/undefined) can sometimes be unclear. Undefined
+is recommended when a policy can conceivably recover or safely ignore the
+undefined behavior. An error should be used when the policy should fail and
+notify someone regardless.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/language/loops.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/language/loops.mdx
new file mode 100644
index 0000000000..622732bd35
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/language/loops.mdx
@@ -0,0 +1,92 @@
+---
+page_title: Sentinel Language - Loops
+sidebar_current: docs-language-loops
+description: >-
+ Loop statements allow you to execute a body of code for each element in a
+ collection or for some fixed number of times.
+layout: docs
+---
+
+# Language: Loops
+
+Loop statements allow you to execute a body of code for each element in
+a collection or for some fixed number of times.
+
+Loop statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## For Statements
+
+`for` statements allow repeated execution of a block for each
+element in a collection.
+
+Example:
+
+```sentinel
+// A basic sum
+count = 0
+for [1, 2, 3] as num {
+ count += num
+}
+```
+
+The syntax is `for COLLECTION as value`. This will iterate over the
+collection, assigning each element to `value`. In the example above,
+each element is assigned to `num`. The body is executed for each element.
+In the example above, the body adds `num` to the `count` variable. This
+creates a basic sum of all values in the collection.
+
+For a map, the assigned element is the key in the map. In the example
+below, `name` would be assigned map keys.
+
+```sentinel
+list = []
+for { "a": 1, "b": 2 } as name {
+ append(list, name)
+}
+
+print(list) // ["a" "b"]
+```
+
+An alternate syntax is `for COLLECTION as key, value`. This will assign
+both the key and value to a variable. For a list, the key is the element
+index. For a map, it is the key and value is assigned the element value.
+Example:
+
+```sentinel
+count = 0
+for { "a": 1, "b": 2 } as name, num {
+ count += num
+}
+
+print(count) // 3
+```
+
+## Scoping
+
+The body of a `for` statement creates a new
+[scope](/sentinel/language/scope). If a variable is assigned within
+the body of a for statement that isn't assigned in a parent scope,
+that variable will only exist for the duration of the body execution.
+
+Example:
+
+```sentinel
+for list as value {
+ a = 42
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+for list as value {
+ a = 42
+}
+
+print(a) // 18
+```
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/language/maps.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/language/maps.mdx
new file mode 100644
index 0000000000..298bf9a567
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/language/maps.mdx
@@ -0,0 +1,121 @@
+---
+page_title: Sentinel Language - Maps
+sidebar_current: docs-language-maps
+description: >-
+ Maps are a collection of zero or more key/value pairs. These are useful for
+ storing values that are looked up by a unique key.
+layout: docs
+---
+
+# Language: Maps
+
+Maps are a collection of zero or more key/value pairs. These are useful
+for storing values that are looked up by a unique key.
+
+Maps can be created using `{}` and specifying key/value pairs. Keys and
+values can be differing types. An optional trailing comma is allowed.
+Examples:
+
+```sentinel
+// Empty map
+{}
+
+// Map with a single value on one line
+{ "key": "value" }
+
+// Map with multiple values with differing types on multiple lines
+{
+ "key": "value",
+ 42: true,
+}
+```
+
+Maps are **unordered**. When
+[looping](/sentinel/language/loops)
+over a map, the key/value pairs can be returned in any order.
+
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for key inclusion in a map.
+
+## Accessing Elements
+
+Map elements can be accessed with the syntax `name[key]`. This looks up
+a value by a key.
+
+Accessing a key that doesn't exist results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+map = { "key": "value", 42: true, }
+
+map["key"] // "value"
+map[42] // true
+map[0] // undefined
+```
+
+## Modifying or Adding Elements
+
+Elements can be added or modified in a map by assigning to `name[key]`.
+If the key doesn't exist, the value is added. If the key already exists,
+the value is overridden.
+
+```sentinel
+map = { "key": "value" }
+
+map[42] = true // Add a new key/value
+map["key"] = 12 // Modify the value of "key"
+```
+
+## Deleting Elements
+
+An element can be deleted from a map using the
+[delete](/sentinel/functions/delete) function.
+
+Examples:
+
+```sentinel
+map = { "key": "value" }
+delete(map, "key") // map is now empty
+delete(map, "other") // no effect for non-existent key
+```
+
+## Keys and Values
+
+The keys and values of a map can be retrieved as
+[lists](/sentinel/language/lists) using the
+[keys](/sentinel/functions/keys) and
+[values](/sentinel/functions/values) functions.
+
+Because maps are unordered, the keys and values are returned in an
+unspecified order. It should not be assumed that keys and values will be
+returned in a consistent order.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+## Map Comparison
+
+Maps may be [compared](/sentinel/language/boolexpr#comparison-operators) for
+equality. Maps are equal if they are of equal length and both their
+corresponding keys and values are comparable and equal.
+
+```sentinel
+{"foo": "bar"} is {"foo": "bar"} // true
+{"foo": "bar"} is {"baz": "bar"} // false
+{"foo": "bar"} is {"foo": "baz"} // false
+{"foo": "bar"} is {"foo": "bar", "baz": "qux"} // false
+{1: "a"} is {1.0: "a"} // true (int/float comparable)
+
+// also true (maps are not ordered):
+{"m": {"a": "b"}, "l": ["a"]} is {"l": ["a"], "m": {"a": " b"}}
+```
+
+-> The current worst-case comparison speed for maps is O(N²), or _quadratic
+time_. This is the square of the cumulative elements or the parent and all child
+maps contained within the map. Consider this when making comparisons on larger,
+more complex maps. This may be optimized in the future.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/language/parameters.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/language/parameters.mdx
new file mode 100644
index 0000000000..348acf2593
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/language/parameters.mdx
@@ -0,0 +1,133 @@
+---
+page_title: Sentinel Language - Parameters
+sidebar_current: docs-language-parameters
+description: >-
+ Parameteres allow a Sentinel policy to describe variables that are expected
+ to be supplied by the calling application, in addition to any default value.
+layout: docs
+---
+
+# Language: Parameters
+
+Sentinel allows a policy author to supply parameters to help facilitate policy
+reuse and ensure sensitive values do not need to be hard-coded in a policy.
+
+Parameters are supplied by using the `param` keyword, followed by an identifier.
+A default value can also be supplied by using the `default` keyword.
+
+```sentinel
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+```
+
+Once declared, parameters can be used like any other variable, including being
+re-assigned.
+
+```sentinel
+param foo default 1 // 1 (default)
+
+foo = foo + 1 // 2
+```
+
+## Variable Descriptions
+
+You can supply a description to a parameter by adding a comment at the top of
+it. This value can be communicated to a specific implementation of Sentinel to
+provide information about what the parameter is for during configuration.
+
+```sentinel
+// An example parameter. Must be supplied or the policy will fail.
+param foo
+```
+
+## Supplying Parameter Values Using the Sentinel CLI
+
+In a production implementation, supplying parameters to a policy is an
+implementation-specific detail - see the documentation for your particular
+implementation for details.
+
+Using the [Sentinel CLI](/sentinel/commands), you can supply parameters one of
+four ways.
+
+### Supplying Parameter Values Using the Configuration File
+
+You can supply parameters using the
+[`param`](/sentinel/configuration#parameters) section of the
+[configuration file](/sentinel/configuration).
+
+```hcl
+param "foo" {
+ value = "bar"
+}
+```
+
+This method works for both `sentinel apply` and `sentinel test`.
+
+### Supplying Parameter Values Using the Environment
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can supply a value using environment variables - prefix the parameter with
+`SENTINEL_PARAM_`, using the name of the parameter to supply.
+
+```plaintext
+SENTINEL_PARAM_foo=bar sentinel apply policy.sentinel
+```
+
+### Supplying Parameter Values Using the CLI
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can also use the `-param` CLI argument to supply parameter in a `key=value`
+pair.
+
+```plaintext
+sentinel apply -param foo=bar policy.sentinel
+```
+
+### Interactive CLI Prompting
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+If a required value has not been supplied when a policy is run with `sentinel apply`, it will be prompted for, along with its description:
+
+
+
+ $ sentinel apply policy.sentinel
+
+ policy.sentinel:2:7: requires value for parameter foo
+
+ An example parameter. Must be supplied or the policy will fail.
+
+
+ Values can be strings, floats, or JSON array or object values. To
+ force
+
+ strings, use quotes.
+
+
+ Enter a value: bar
+
+
+
+ Pass
+
+
+
+
+### CLI Value Format
+
+-> **NOTE:** This section contains details for the parameter features supported by `sentinel apply`.
+
+The CLI takes either strings, or JSON numbers, arrays, or maps. If you need a
+literal string value, quote the value.
+
+```sentinel
+foo // string
+42 // number (float)
+"42" // string ("42", without quotes)
+[1, 2] // array (list)
+{"a": "b"} // object (map)
+```
+
+-> **NOTE:** Boolean values are not supported by this method.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/language/rules.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/language/rules.mdx
new file mode 100644
index 0000000000..e31924d414
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/language/rules.mdx
@@ -0,0 +1,204 @@
+---
+page_title: Sentinel Language - Rules
+sidebar_current: docs-language-rules
+description: >-
+ Rules form the basis of a policy by representing behavior that is either
+ passing or failing (true or false).
+layout: docs
+---
+
+# Language: Rules
+
+Rules form the basis of a policy by representing behavior that is either passing
+or failing. A policy can be broken down into a set of rules. Breaking down a
+policy into a set of rules can make it more understandable and aids with
+testing.
+
+An example is shown below:
+
+```sentinel playground
+weather = "sunny"
+day = "wednesday"
+is_sunny = rule { weather is "sunny" }
+is_wednesday = rule { day is "wednesday" }
+
+main = rule {
+ is_sunny and
+ is_wednesday
+}
+```
+
+A rule contains a single expression. This can be a simple [boolean
+expression](/sentinel/language/boolexpr), or an expression representing the
+discovery of a set of violations using more in-depth expressions like [`filter`
+and `map`](/sentinel/language/collection-operations). You can split expressions
+into multiple lines for readability, as you would with any other expression
+within Sentinel.
+
+A rule is also _lazy_ and _memoized_. _Lazy_ means that the rule is only
+evaluated when it is actually required, not at the point that it is
+created. This is covered in more detail in the [lazy](#lazy-and-memoized) section.
+_Memoized_ means that the value is only computed once and then saved. Once
+a rule is evaluated, the result of it is reused anytime the rule is referenced
+again.
+
+## Making rules easier to understand
+
+Rules are an abstraction that make policies much more understandable
+by making it easier for humans to see what the policy is trying to do.
+
+For example, consider the policy before which doesn't abstract into rules:
+
+```sentinel
+main = rule {
+ ((day is "saturday" or day is "sunday") and homework is "") or
+ (day in ["monday", "tuesday", "wednesday", "thursday", "friday"] and
+ not school_today and homework is "")
+}
+```
+
+Assume that `day`, `homework`, and `school_day` are available.
+
+In plain English, this policy is trying to say: you can play with your friends
+only on the weekend as long as there is no homework, or during the week if
+there is no school and no homework.
+
+Despite the relatively simple nature of this policy (a few lines, about 5
+logical expressions), it is difficult to read and understand, especially if
+you've not seen it before.
+
+The same policy using rules as an abstraction:
+
+```sentinel
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+is_valid_weekend = rule { is_weekend and homework is "" }
+is_valid_weekday = rule { not is_weekend and not school_today and homework is "" }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+By reading the names of the rules, its much clearer to see what each individual
+part of the policy is trying to achieve. The readability is improved even
+further when adding comments to the rules to explain them further, which is
+a recommended practice:
+
+```sentinel
+// A weekend is Sat or Sun
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+// A valid weekend is a weekend without homework
+is_valid_weekend = rule { is_weekend and homework is "" }
+
+// A valid weekday is a weekday without school
+is_valid_weekday = rule { not is_weekend and not school_today and homework is "" }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+## "When" Predicates
+
+A rule may have an optional `when` predicate attached to it. When this is
+present, the rule is only evaluated when the predicate results in `true`.
+Otherwise, the rule is not evaluated and the result of the rule is always
+`true`.
+
+"When" predicates should be used to define the context in which the rule
+is semantically meaningful. It is common when translating human language
+policy into Sentinel to have scenarios such as "when the key has a prefix
+of `/account/`, the remainder must be numeric." In that example, when the
+key _does not_ have the specified prefix, the policy doesn't apply.
+
+Assume you have rules `is_prefix` and `is_numeric` to check both the
+conditions in the example above. An example is shown with and without
+the "when" predicate:
+
+```sentinel
+example_no_when = rule { (is_prefix and is_numeric) or not is_prefix }
+
+example_when = rule when is_prefix { is_numeric }
+```
+
+The rules are equivalent in behavior for all values of `is_prefix` and
+`is_numeric`, but the second rule is more succint and easier to understand.
+
+## Testing
+
+To verify a policy works correct, the
+[built-in Sentinel test framework](/sentinel/writing/testing)
+uses rules as the point where you can assert behavior. You say that
+you expect certain rules to be true or false. By doing so, you ensure that
+the policy results in the value you expect using the rule flow that you
+expect.
+
+Therefore, in addition to readability, we recommend splitting policies into
+rules to aid testability.
+
+## Non-Boolean Values
+
+Sentinel supports non-boolean values in rules. This can be useful for passing
+more detail along to a calling integration when more detail is required than a
+boolean value would be able to communicate. This data shows up in the [policy
+trace](/sentinel/writing/tracing) and can be utilized in different ways
+depending on the integration you are using Sentinel with.
+
+Consider a different take on our policy above:
+
+```sentinel
+// A policy to check a set of scheduled days to determine what days you can't
+// come out and play, based on the day not being a weekend day and there being
+// homework to do.
+
+param days
+
+main = rule {
+ filter days as d {
+ d.day not in ["saturday", "sunday"] and
+ d.homework is not ""
+ }
+}
+```
+
+When given a value in the set of `days` that has a `day` that is a weekday, and
+`homework` present, the policy will fail. Additional, when tracing was enabled,
+the days that were detected as violating the policy would be given in the trace
+for the `main` rule.
+
+Generally, for non-boolean values, a policy fails on non-zero data. See the
+details on [the `main` rule](/sentinel/language#main) for more details.
+
+The result of a rule must be either a boolean, string, integer, float, list, or
+map. All other types will result in a runtime error.
+
+## Lazy and Memoized
+
+A rule is _lazy_ and _memoized_.
+
+_Lazy_ means that the rule is only evaluated when it is actually required,
+not at the point that it is created. And _memoized_ means that the value is
+only computed once and then saved. Once a rule is evaluated, the result of it
+is reused anytime the rule is referenced again.
+
+Both of these properties have important implications for Sentinel
+policies:
+
+**Performance:** Rules are a way to improve the performance of a Sentinel
+policy. If you have a boolean expression that is reused a lot, a rule will
+only be evaluated once. In the example shown above, `is_weekend` is used
+multiple times, but will only have to be evaluated once.
+
+**Behavior:** Rules accessing variables see the value of those variables
+at _the time they are evaluated_. This can lead to surprising behavior
+in some cases. For example:
+
+```sentinel playground
+a = 1
+b = rule { a == 1 }
+a = 2
+main = b
+```
+
+In this example, `main` will actually result to `false`. It is evaluated
+when it is needed, which is when the policy executes `main`. At this point,
+`a` is now 2 and `b` has not been evaluated yet. Therefore, `b` becomes `false`.
+All future references to `b` will return `false` since a rule is memoized.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/language/scope.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/language/scope.mdx
new file mode 100644
index 0000000000..f498e26dc2
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/language/scope.mdx
@@ -0,0 +1,47 @@
+---
+page_title: Sentinel Language - Scope
+sidebar_current: docs-language-scope
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Scope
+
+Scope is the context in which variables are created and accessed. If a
+variable exists in a parent scope, it is accessed and can be modified.
+Otherwise, the variable is created in your current scope.
+
+Scopes are created with _blocks_. A block is a possibly empty sequence
+of statements within matching brace brackets `{}`. You may nest blocks
+to create nested scopes.
+
+## Package Scope
+
+The package scope is the top level scope within a policy file and encapsulates
+the entire file contents. Imports, parameters and named functions must be
+declared within the package scope.
+
+## Implicit Scopes
+
+Each `any`, `all`, and `for` statement is considered to be in its own block.
+Note that `if` statements _do not_ create their own block.
+
+## Examples
+
+The example policy below shows various effects of scopes:
+
+```sentinel
+a = 1
+print(a) // 1
+
+f = func() {
+ print(a) // 1
+ a = 12
+ b = 1
+ return undefined
+}
+f()
+
+print(a) // 12
+print(b) // undefined
+```
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/language/slices.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/language/slices.mdx
new file mode 100644
index 0000000000..82dc7f4c18
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/language/slices.mdx
@@ -0,0 +1,45 @@
+---
+page_title: Sentinel Language - Slices
+sidebar_current: docs-language-slices
+description: >-
+ Slices are an efficient way to create a substring or sublist from an existing
+ string or list, respectively.
+layout: docs
+---
+
+# Language: Slices
+
+Slices are an efficient way to create a substring or sublist from an
+existing string or list, respectively.
+
+The syntax for creating a slice is:
+
+```sentinel
+a[low : high]
+```
+
+`low` is the lowest index to slice from. The resulting slice includes
+the value at `low`. `high` is the index to slice to. The resulting slice
+will not include the value at `high`. The length of the resulting list
+or string is always `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+
+a = "hello"
+b = a[1:4] // "ell"
+```
+
+## Convenience Shorthands
+
+Some convenience shorthands are available by omitting the value for either the
+low or high index in the slice expression: omitting `low` implies a low index of
+0, and omitting `high` implies a high index of the length of the list.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+
+b = a[:2] // [1, 2] (same as a[0:2])
+b = a[2:] // [3, 4, 5] (same as a[2:length(a)])
+```
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/language/spec.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/language/spec.mdx
new file mode 100644
index 0000000000..0877897422
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/language/spec.mdx
@@ -0,0 +1,1330 @@
+---
+page_title: Sentinel Language Specification
+sidebar_current: docs-language-spec
+description: This is the specification for the Sentinel policy language.
+layout: docs
+---
+
+# Sentinel Language Specification
+
+This is the specification for the Sentinel policy language.
+
+The Sentinel language is designed with policy enforcement in mind. It is dynamically typed and garbage collected and has explicit support for _rule_ construction representing boolean logic.
+
+The language is designed to be easy to learn and use by non-programmers. It is expected to be embedded within applications.
+
+## Table of Contents
+
+
+
+
+- [Source code representation](#source-code-representation)
+- [Declarations and Scope](#declarations-and-scope)
+- [Blocks](#blocks)
+- [Lexical Elements](#lexical-elements)
+ - [Comments](#comments)
+ - [Identifiers](#identifiers)
+ - [Keywords](#keywords)
+ - [Operators and Delimiters](#operators-and-delimiters)
+ - [Integer Literals](#integer-literals)
+ - [Floating-point Literals](#floating-point-literals)
+ - [String Literals](#string-literals)
+ - [Implicit Line Joining](#implicit-line-joining)
+ - [Whitespace](#whitespace)
+ - [Semicolons](#semicolons)
+- [Variables](#variables)
+- [Undefined](#undefined)
+- [Expressions](#expressions)
+ - [Operand](#operand)
+ - [Primary Expressions](#primary-expressions)
+ - [Null](#null)
+ - [Booleans](#booleans)
+ - [Boolean Literals](#boolean-literals)
+ - [Boolean Expressions](#boolean-expressions)
+ - [List Literals](#list-literals)
+ - [Map Literals](#map-literals)
+ - [Function Literals](#function-literals)
+ - [Rule Expressions](#rule-expressions)
+ - [Index Expressions](#index-expressions)
+ - [Selectors](#selectors)
+ - [Slice Expressions](#slice-expressions)
+ - [Calls](#calls)
+ - [Operators](#operators)
+ - [Operator Precedence](#operator-precedence)
+ - [Arithmetic Operators](#arithmetic-operators)
+ - [Integer operators](#integer-operators)
+ - [Integer overflow](#integer-overflow)
+ - [Floating-point operators](#floating-point-operators)
+ - [String Concatenation](#string-concatenation)
+ - [List Concatenation](#list-concatenation)
+ - [Comparison Operators](#comparison-operators)
+ - [Logical Operators](#logical-operators)
+ - [Set Operators](#set-operators)
+ - [Matches Operator](#matches-operator)
+ - [Else Operator](#else-operator)
+ - [Quantifier Expressions (any, all, filter, map)](#quantifier-expressions-any-all-filter-map)
+- [Statements](#statements)
+ - [Expression Statements](#expression-statements)
+ - [Assignments](#assignments)
+ - [If Statements](#if-statements)
+ - [Case Statements](#case-statements)
+ - [For Statements](#for-statements)
+ - [Break Statements](#break-statements)
+ - [Continue Statements](#continue-statements)
+ - [Return Statements](#return-statements)
+- [Imports](#imports)
+- [Parameters](#parameters)
+- [Built-in Functions](#built-in-functions)
+ - [Length](#length)
+ - [Collections](#collections)
+ - [List Append](#list-append)
+ - [Map Delete](#map-delete)
+ - [Keys and Values](#keys-and-values)
+ - [Range](#range)
+ - [Type Conversion](#type-conversion)
+ - [Printing](#printing)
+ - [Errors](#errors)
+- [Program Execution](#program-execution)
+
+
+
+## Source code representation
+
+Source code is Unicode text encoded in UTF-8. A "character" referenced in this document refers to a Unicode code point. Each code point is distinct: upper and lower case letters are different characters.
+
+The underscore character \_ (U+005F) is considered a "letter".
+
+```ebnf
+letter = unicode_letter | "_" .
+decimal_digit = "0" … "9" .
+octal_digit = "0" … "7" .
+hex_digit = "0" … "9" | "A" … "F" | "a" … "f" .
+```
+
+## Declarations and Scope
+
+A declaration binds an identifier to a value. An identifier is declared at the point it is first assigned. An assignment is considered a first assignment if the identifier hasn't already been previously declared in the current scope or any parent scopes.
+
+The scope of an identifier is the extent of source text in which the identifier denotes the specified value. Sentinel is lexically scoped using blocks.
+
+An identifier declared in a block may be redeclared in an inner block. While the identifier of the inner declaration is in scope, it denotes the value assigned in the inner declaration.
+
+## Blocks
+
+A block is a possibly empty sequence of statements within matching brace brackets.
+
+```ebnf
+Block = "{" StatementList "}" .
+StatementList = { Statement ";" } .
+```
+
+Blocks nest and affect scoping.
+
+In addition to explicit blocks in the source code, there are implicit blocks:
+
+1. The universe block encompasses all source text.
+2. Each program has a program block containing all source text for that program.
+3. Each "any", "all", and "for" statements is considered to be in its own implicit block. "if" does not create an implicit block.
+
+## Lexical Elements
+
+### Comments
+
+Comments are sections of source text used for documentation.
+
+```plaintext
+# Single line comment
+// Single line comment
+/* multi
+line
+ comment */
+```
+
+Three forms of comments are supported: two single-line forms and one multi-line form. A single-line comment begins with the token `//` or `#`. Everything between the starting token and the end of the line is ignored.
+
+A multi-line comment begins with the token `/*` and ends with the token `*/`. Everything between `/*` and `*/` is ignored.
+
+A comment may not start inside a string literal or inside another comment.
+
+A multi-line comment containing no newlines acts like a space.
+
+### Identifiers
+
+Identifiers name program entities such as rules, variables, and functions. An identifier is a sequence of one or more letters and digits. The first character in an identifier must be a letter.
+
+```ebnf
+identifier = letter { letter | unicode_digit } .
+```
+
+```sentinel
+a
+_a
+_A
+αβ
+```
+
+#### Pre-Declared Identifiers
+
+The following identifiers are implicitly declared in the [universe block](#blocks):
+
+```plaintext
+Constants:
+ false null true undefined
+
+Functions:
+ append bool delete error float int keys length print range string values
+```
+
+### Keywords
+
+The following keywords are reserved and may not be used as identifiers:
+
+```plaintext
+all
+any
+as
+break
+case
+continue
+default
+else
+empty
+filter
+for
+func
+if
+import
+map
+param
+return
+rule
+when
+```
+
+### Operators and Delimiters
+
+The following character sequences represent operators, delimiters, and other special tokens:
+
+```plaintext
++
+-
+*
+/
+%
++=
+-=
+*=
+/=
+%=
+==
+<
+>
+=
+!
+!=
+<=
+>=
+( )
+[ ]
+{ }
+,
+.
+:
+;
+and
+contains
+else
+in
+is
+matches
+not
+or
+xor
+```
+
+As a special case, `else` is both a keyword and operator, depending on the context. See [Else Operator](#else-operator) for more details.
+
+### Integer Literals
+
+An integer literal is a sequence of digits representing an integer constant. An optional prefix sets a non-decimal base: `0` for octal, `0x` or `0X` for hexadecimal. In hexadecimal literals, letters `a-f` and `A-F` represents values 10 through 15.
+
+Integers are signed 64-bit values (-9223372036854775808 to 9223372036854775807).
+
+```ebnf
+int_lit = decimal_lit | octal_lit | hex_lit .
+decimal_lit = ( "1" … "9" ) { decimal_digit } .
+octal_lit = "0" { octal_digit } .
+hex_lit = "0" ( "x" | "X" ) hex_digit { hex_digit } .
+```
+
+```
+42
+0600
+0xBadFace
+170141183460469231731687303715884105727
+```
+
+### Floating-point Literals
+
+A floating-point literal is a decimal representation of a floating-point constant. It has an integer part, a decimal point, a fractional part, and an exponent part. The integer and fractional part comprise decimal digits; the exponent part is an e or E followed by an optionally signed decimal exponent. One of the integer part or the fractional part may be elided; one of the decimal point or the exponent may be elided.
+
+Floating-point numbers are IEEE-754 64-bit floating numbers.
+
+```ebnf
+float_lit = decimals "." [ decimals ] [ exponent ] |
+ decimals exponent |
+ "." decimals [ exponent ] .
+decimals = decimal_digit { decimal_digit } .
+exponent = ( "e" | "E" ) [ "+" | "-" ] decimals .
+```
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+### String Literals
+
+String literals are character sequences between double quotes, as in "bar". Within the quotes, any character may appear except newline and unescaped double quote. The text between the quotes forms the value of the literal.
+
+A multi-character sequence beginning with a backslash encode values in various formats.
+
+Backslash escapes allow arbitrary values to be encoded as ASCII text. There are four ways to represent the integer value as a numeric constant: `\x` followed by exactly two hexadecimal digits; `\u` followed by exactly four hexadecimal digits; `\U` followed by exactly eight hexadecimal digits, and a plain backslash `\` followed by exactly three octal digits. In each case the value of the literal is the value represented by the digits in the corresponding base.
+
+After a backslash, certain single-character escapes represent special values:
+
+```plaintext
+\a U+0007 alert or bell
+\b U+0008 backspace
+\f U+000C form feed
+\n U+000A line feed or newline
+\r U+000D carriage return
+\t U+0009 horizontal tab
+\v U+000b vertical tab
+\\ U+005c backslash
+\" U+0022 double quote
+```
+
+The three-digit octal (\nnn) and two-digit hexadecimal (\xnn) escapes represent individual bytes of the resulting string; all other escapes represent the (possibly multi-byte) UTF-8 encoding of individual characters. Thus inside a string literal \377 and \xFF represent a single byte of value 0xFF=255, while ÿ, \u00FF, \U000000FF and \xc3\xbf represent the two bytes 0xc3 0xbf of the UTF-8 encoding of character U+00FF.
+
+A string value is a (possibly empty) sequence of bytes. Strings are immutable: once created, it is impossible to change the contents of a string.
+
+The length of a string s (its size in bytes) can be discovered using the built-in function `length`. An individual character (of type string) can be accessed by integer indices 0 through `length(s)-1`.
+
+```ebnf
+string_lit = `"` { unicode_value | byte_value } `"` .
+unicode_value = unicode_char | little_u_value | big_u_value | escaped_char .
+byte_value = octal_byte_value | hex_byte_value .
+octal_byte_value = `\` octal_digit octal_digit octal_digit .
+hex_byte_value = `\` "x" hex_digit hex_digit .
+little_u_value = `\` "u" hex_digit hex_digit hex_digit hex_digit .
+big_u_value = `\` "U" hex_digit hex_digit hex_digit hex_digit
+ hex_digit hex_digit hex_digit hex_digit .
+escaped_char = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | `"` ) .
+```
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+### Implicit Line Joining
+
+Expressions can be split over more than one line. For example:
+
+```sentinel
+a or
+b or # This is a comment
+c
+```
+
+Implicitly continued lines can have trailing comments. Blank continued lines are allowed.
+
+### Whitespace
+
+Whitespace is needed to separate tokens, but no distinction is made between the number and combination of whitespace characters. Whitespace characters can be space (U+0020), horizontal tabs (U+0009), carriage returns (U+000D), and newlines (U+000A).
+
+```sentinel
+a or b
+
+a or b
+
+a or
+ b
+
+rule { a or b }
+
+rule {
+ a or b }
+
+rule {
+ a or
+ b
+}
+```
+
+### Semicolons
+
+The formal grammar uses semicolons ";" as terminators in a number of productions.
+It is idiomatic Sentinel source to omit semicolons in most cases.
+
+A semicolon is automatically inserted into the token stream immediately after
+a line's final token if that token is:
+
+- An identifier
+- An integer, float, or string literal
+- The keyword `break`, `continue`, or `return`
+- The delimiter `)`, `]`, or `}`
+
+## Variables
+
+A variable is a storage location for holding a value.
+
+A variable has a dynamic type, which is the concrete type of the value assigned to the variable at run time. The dynamic type may vary during execution and change as a result of further assignments.
+
+A variable's value is retrieved by referring to the variable in an expression; it is the most recent value assigned to the variable. If a variable has not yet been declared (initially assigned), it is an error.
+
+```sentinel
+x = 7 // x is type int
+x = "foo" // x is now type string
+
+x = y // error if y is not previously assigned
+```
+
+## Undefined
+
+The value denoted by the keyword `undefined` represents undefined behavior or values. It can be created directly using the keyword `undefined`. It is also returned as a result of expressions in specified cases.
+
+`undefined` is a valid operand for any operations. Only `undefined or true` will result in true. All other operations result in `undefined`. An exception is if `undefined` is not reached as a result of short-circuit operations.
+
+```sentinel
+undefined OR true = true
+undefined OR false = undefined
+undefined OR undefined = undefined
+undefined AND true = undefined
+undefined AND false = undefined
+undefined AND undefined = undefined
+undefined XOR true = undefined
+undefined XOR false = undefined
+undefined XOR undefined = undefined
+
+// Short-circuit examples
+false OR true OR undefined = true
+false OR undefined OR true = true
+true AND false AND undefined = false
+true AND undefined AND false = undefined
+
+// Non-logical operators
+undefined + 5 = undefined
+-undefined = undefined
+!undefined = undefined
+```
+
+If the result of the main rule is `undefined`, it is treated as `false` but is indicative of erroneous logic.
+
+## Expressions
+
+### Operand
+
+Operands denote the elementary values in an expression. An operand may be a literal, an identifier denoting a variable, rule, or function, or a parenthesized expression.
+
+```ebnf
+Operand = Literal | OperandName | MethodExpr | "(" Expression ")" .
+Literal = BasicLit | CompositeLit | FunctionLit | RuleLit .
+BasicLit = int_lit | float_lit | string_lit .
+OperandName = identifier | QualifiedIdent.
+```
+
+### Primary Expressions
+
+Primary expressions are the operands for unary and binary expressions.
+
+```ebnf
+PrimaryExpr =
+ Operand |
+ PrimaryExpr Selector |
+ PrimaryExpr Index |
+ PrimaryExpr Slice |
+ PrimaryExpr Arguments .
+
+Selector = "." identifier .
+Index = "[" Expression "]" .
+Slice = "[" [ Expression ] ":" [ Expression ] "]" |
+ "[" [ Expression ] ":" Expression ":" Expression "]" .
+Arguments = "(" [ ( ExpressionList | Type [ "," ExpressionList ] ) [ "..." ] [ "," ] ] ")" .
+```
+
+```sentinel
+x
+2
+(s + ".txt")
+f(3.1415, true)
+m["foo"]
+s[i : j + 1]
+obj.color
+f.p[i].x()
+x is empty
+x is not empty
+```
+
+### Null
+
+The reserved word `null` denote the singleton value `null`. Null represents the explicit absense of a value. Behavior of `null` within expressions is specified explicitly for each expression.
+
+### Booleans
+
+### Boolean Literals
+
+The reserved words `true` and `false` denote objects that represent the boolean values `true` and `false`, respectively. These are boolean literals.
+
+```ebnf
+BoolLit = "true" | "false" .
+```
+
+#### Boolean Expressions
+
+Boolean expressions are expressions that must result in a boolean value. Any other value type becomes the `undefined` value.
+
+```ebnf
+BoolExpr = Expression .
+```
+
+### List Literals
+
+A list literal denotes a list, which is an integer indexed collection of values.
+
+```ebnf
+ListLit = "[" [ ElementList [ "," ] ] "]" .
+ElementList = Element { "," Element } .
+Element = Expression | LiteralValue .
+```
+
+A list may contain zero or more values. The number of values in a list is its length.
+
+A list has a set of indices. An empty list has an empty set of indices. A non-empty list has the index set `{0...n - 1}` where `n` is the length of the list. Attempting to access a list using an index that is not a member of its set of indices results in the `undefined` value.
+
+### Map Literals
+
+A map literal denotes a map, which is an unordered group of elements indexed by a set of unique keys.
+
+```ebnf
+MapLit = "{" [ KeyedElementList [ "," ] ] "}" .
+KeyedElementList = KeyedElement { "," KeyedElement } .
+KeyedElement = Element ":" Element .
+```
+
+Keys can only be a boolean, numeric, or string type.
+
+The value of a non-existent key is the `undefined` value.
+
+### Function Literals
+
+A function literal represents a function.
+
+```ebnf
+FunctionLit = "func" Function .
+Function = Parameters FunctionBody .
+FunctionBody = Block .
+Parameters = "(" [ IdentifierList [ "," ] ] ")" .
+IdentifierList = identifier { "," identifier } .
+```
+
+```sentinel
+func(a, b) { return b }
+```
+
+Function literals are only allowed in the file scope. They may refer to variables defined in surrounding blocks.
+
+A function must terminate with a return statement. If a function has no meaningful return value, it should return `undefined`.
+
+### Rule Expressions
+
+A rule is an expression that is evaluated lazily and the result is memoized.
+
+If the optional "when" predicate is present, the rule is evaluated only when
+the "when" boolean expression results in true. If the predicate is false,
+the rule is not evaluated and returns true. The predicate is evaluated when
+the rule would be evaluated; it is also lazy and memoized in the same way.
+
+```ebnf
+RuleExpr = "rule" [ "when" BoolExpr ] "{" Expr "}" .
+```
+
+```sentinel
+rule { x is y }
+
+rule {
+ x == "value" or
+ y == "other"
+}
+
+rule when x is y { y > 42 }
+
+rule { map ["a", "b", "c"] as _, id {
+ { "id": id }
+}}
+```
+
+### Index Expressions
+
+A primary expression of the form `a[x]` denotes the element of a list or map indexed by x. The value x is called the index or key.
+
+For `a` of type map:
+
+- If `a` contains a key `x`, `a[x]` is the map value with key `x`.
+- If `a` does not contain key `x`, `a[x]` is the `undefined` value.
+
+For `a` of type list:
+
+- `x` must be an integer
+- `x` must be in the range `[-1 * length(a), length(a)-1]`
+- If `x` is contained in the set of indices of `a`, `a[x]` is the list element at index `x`. If `x` is negative, `a[x]` is equivalent to `a[length(a)+x]`.
+- If `x` is not contained in the set of indices of `a`, `a[x]` is the `undefined` value.
+
+For `a` of value `null`:
+
+- `a[x]` is the `undefined` value.
+
+Otherwise `a[x]` is an error.
+
+### Selectors
+
+For a primary expression x, the selector expression `x.f` denotes the field `f` of the value `x`. The identifier `f` is called the selector. The type of the selector expression is the type of the selector.
+
+As a special case, selectors can be [reserved words](#keywords) and keyword [operators](#operators-and-delimiters), but cannot be any other non-identifier element.
+
+Selectors are used to access data from [imports](#imports). The first primary expression `x` in `x.f` denotes the import name. The field `f` is the selector to access data from the import.
+
+Selectors may also be used to access map data with static keys. They are syntactic sugar over index expressions. `math.pi` is equivalent to `math["pi"]` and exhibit the same limitations and behavior as an index expression. Selectors cannot, however, be used to assign values to map data.
+
+Selectors on undefined result in undefined.
+
+```sentinel
+math.pi
+time.pst.hour
+```
+
+### Slice Expressions
+
+Slice expressions construct a substring or list from a string or list.
+
+The primary expression
+
+```sentinel
+a[low : high]
+```
+
+constructs a substring or list. The indices `low` and `high` select which elements of operand `a` appear in the result. The result has indices starting at 0 and length equal to `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+```
+
+For convenience, any of the indices may be omitted. A missing `low` index defaults to zero; a missing `high` index defaults to the length of the sliced operand:
+
+```sentinel
+a[2:] // same as a[2 : length(a)]
+a[:3] // same as a[0 : 3]
+a[:] // same as a[0 : length(a)]
+```
+
+The indices are in range if `0 <= low <= high <= length(a)`, otherwise they are out of range. If the indices are out of range at run time, the result is the `undefined` value.
+
+If `a` is the value `null`, the result is the `undefined` value.
+
+If `a` is any other value type, it is an error.
+
+### Calls
+
+Given an expression `f` where `f` is a function value:
+
+```sentinel
+f(a1, a2, … an)
+```
+
+calls `f` with arguments `a1, a2, ... an`. The type of the expression is the result of `f`. The arguments are evaluated left to right. Arguments are passed by value.
+
+### Operators
+
+```ebnf
+Expression = UnaryExpr | Expression binary_op Expression .
+UnaryExpr = PrimaryExpr | unary_op UnaryExpr .
+
+binary_op = logical_op | set_op | rel_op | add_op | mul_op | else_op .
+logical_op = "and" | "or" | "xor" .
+set_op = ["not"] ( "contains" | "in" ).
+rel_op = "==" | "!=" | "<" | "<=" | ">" | ">=" |
+ "is" | "is not" | "matches" | "not matches" .
+add_op = "+" | "-" .
+mul_op = "*" | "/" | "%" .
+else_op = "else" .
+unary_op = "+" | "-" | "!" | "not" | empty_op | defined_op .
+empty_op = "is empty" | "is not empty" .
+defined_op = "is defined" | "is not defined" .
+```
+
+#### Operator Precedence
+
+Unary operators have the highest precedence.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Binary operators of the same precedence associate from left to right. For instance, x / y _ z is the same as (x / y) _ z.
+
+### Arithmetic Operators
+
+Arithmetic operators apply to numeric values and yield a result of the same type when both operands are the same.
+
+Arithmetic operations between integer and floating-point types result in a floating-point value with the integer operand treated as a floating-point value for purposes of calculation.
+
+Arithmetic operations between numeric values (integer, floating-point) and non-numeric values (strings, lists) are not permitted.
+
+All five supported arithmetic operators (+, -, \*, /, %) apply to both integer and floating-point types; + also applies to strings.
+
+```plaintext
++ sum integers, floats, strings, lists
+* difference integers, floats
+* product integers, floats
+/ quotient integers, floats
+% remainder integers, floats
+```
+
+#### Integer operators
+
+For two integer values x and y, the integer quotient `q = x / y` and remainder `r = x % y` satisfy the following relationships:
+
+```sentinel
+x = q*y + r and |r| < |y|
+```
+
+with x / y truncated towards zero.
+
+```plaintext
+ x y x / y x % y
+ 5 3 1 2
+-5 3 -1 -2
+ 5 -3 -1 2
+-5 -3 1 -2
+```
+
+As an exception to this rule, if the dividend x is the most negative value for the int type of x (-9223372036854775808), the quotient `q = x / -1` is equal to x (and r = 0).
+
+If the divisor is a constant, it must not be zero. If the divisor is zero at run time, an error occurs.
+
+#### Integer overflow
+
+For signed integers, the operations `+`, `-`, and `*` may legally overflow and the resulting value exists and is deterministically defined by the signed integer representation, the operation, and its operands. No exception is raised as a result of overflow.
+
+#### Floating-point operators
+
+For floating-point numbers, +x is the same as x, while -x is the negation of x. The result of a floating-point division by zero is not specified beyond the IEEE-754 standard.
+
+#### String Concatenation
+
+Strings can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = "hi"
+y = "hello"
+x = x + ", " + y // "hi, hello"
+x += " and good bye" // "hi, hello and good bye"
+```
+
+String addition creates a new string by concatenating the operands.
+
+#### List Concatenation
+
+Lists can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = [1, 2]
+x = x + [2, 3] // [1, 2, 2, 3]
+x += [4] // [1, 2, 2, 3, 4]
+```
+
+List addition creates a new list by concatenating the operands.
+
+### Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+In any comparison, the two operands must be equivalent types. The only exception are integers and floats, which are considered numeric and may be compared. Any comparison between other non-matching types results in the `undefined` value.
+
+The equality operators `==`, `!=`, `is`, and `is not` apply to operands that are comparable. The ordering operators `<`, `<=`, `>`, and `>=` apply to operands that are ordered. The behavior of `is` with `==` and `is not` with `!=` is identical. These terms and the result of the comparisons are defined as follows:
+
+- Boolean values are comparable. Two boolean values are equal if they are either both true or both false.
+- Integer values are comparable and ordered, in the usual way.
+- Floating point values are comparable and ordered, as defined by the IEEE-754 standard.
+- An integer compared with floating point value treats the integer as the converted floating point value.
+- String values are comparable and ordered, lexically byte-wise.
+- Lists are comparable. Lists are equal if they are of equal length and their
+ corresponding elements are comparable and equal.
+- Maps are comparable. Maps are equal if they are of equal length and both their
+ corresponding keys and values are comparable and equal.
+
+If either operand is the `undefined` value, the result of the expression is the `undefined` value.
+
+### Emptiness Comparisons
+
+Checking the emptiness of an object can be achieved by using one of the emptiness expressions, `is empty` or `is not empty`. Although similar to [Comparison Operators](#comparison-operators) in that they yield a boolean value and read as though a comparison is taking place, both `is empty` and `is not empty` are evaluated as a multi-word expression.
+
+An emptiness comparison can only be performed against collections, strings or `undefined`, with the same rules as the [built-in length](/sentinel/functions/length) function.
+
+```sentinel
+"" is empty // true
+"foo" is empty // false
+[] is empty // true
+[1] is empty // false
+{} is empty // true
+{"a": "b"} is empty // false
+
+"" is not empty // false
+"foo" is not empty // true
+[] is not empty // false
+[1] is not empty // true
+{} is not empty // false
+{"a": "b"} is not empty // true
+
+undefined is empty // undefined
+undefined is not empty // undefined
+```
+
+### Logical Operators
+
+Logical operators apply to boolean values and yield a boolean result.
+
+Logical operators are evaluated left-to-right and perform short-circuit logic. The right-hand side is not guaranteed to be evaluated if a short-circuit can be performed.
+
+```plaintext
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+### Set Operators
+
+The set operators `contains` and `in` test for set inclusion for lists
+and maps, and substring inclusion for strings.
+
+Set operators may be negated by prefixing the operator with `not`: `not contains` and `not in`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+`contains` tests if the left-hand collection contains the right-hand value. For lists, this tests equality of any value. For maps, this tests equality of any key. For strings, this tests for substring existence.
+
+`in` tests if the right-hand collection contains the left-hand value. The behavior is equivalent to `contains`.
+
+The collection must be a list, map, or string. If it is the `undefined` value, the result is the `undefined` value. For any other value, the result is an error.
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+
+"test" contains "est" // true
+"test" contains "best" // false
+"test" in "testing" // true
+"best" in "testing" // false
+```
+
+### Matches Operator
+
+The matches operator tests if a string matches a regular expression.
+
+The matches operators may be negated by prefixing the operator with `not`: `not matches`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+The left-hand value is the string to test. The right-hand value is a string value representing a regular expression.
+
+The syntax of the regular expression is the same general syntax used by Python, Ruby, and other languages. More precisely, it is the syntax accepted by RE2. The regular expression is not anchored by default; any anchoring must be explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+If either operand is `undefined`, the result is the `undefined` value. For any other non-string value, the result is an error.
+
+### Else Operator
+
+Else operators can be used to provide a default value for an expression that may evaluate to the `undefined` value. Else operators return their left-hand value unless it is `undefined`, otherwise they return their right-hand value.
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else null
+```
+
+### Quantifier Expressions (any, all, filter, map)
+
+Quantifiers are expressions that apply a predicate to a collection (list or map). The purpose of the quantifier is to produce a result based on its particular type.
+
+`any` and `all` expressions are existential and universal quantifiers, respectively. `any` expressions are equivalent to a chain of `or` and `all` expressions are equivalent to a chain of `and`. Both expressions implement short-circuiting equivalent to logical operators.
+
+The `map` expression is an apply-to-all quantifier and returns a new list for all values in the input collection. The output list will be the same length as the input collection.
+
+The `filter` expression is a subset quantifier and returns the subset of all values in the input collection for which the supplied predicate applies. This will be zero or more elements, up to the length of the input.
+
+The body of a quantifier expression is a boolean expression.
+
+`any` returns the boolean value `true` if any value in the collection expression results in the body expression evaluating to `true`. If the body expression evaluates to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression result in the body expression evaluating to `true`. If any value in the collection expression result in the body expression evaluating to `false`, the all expression returns `false`.
+
+For empty collections, `any` returns false and `all` returns `true`.
+
+`map` always returns a list, regardless of if the input collection is a list itself; maps (as in the data type) also return lists when processed through `map`. Each element is the result of the supplied expression body.
+
+`filter` returns a list or map, the same type as its input collection. Only the elements for which the expression body evaluated to `true` will be returned. If an iteration of the expression body results in `undefined`, the entire result set is `undefined`.
+
+When the collection is a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When the collection is a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+QuantExpr = QuantOp Expression "as" [ identifier "," ] identifier "{" BoolExpr "}" .
+QuantOp = "all" | "any" | "filter" | "map" .
+```
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" } // true if every iterations is true
+any group.tasks as t { t.driver is "vmware" } // true if any iteration is true
+map group.tasks as t { { "driver": t.driver } } // [{"driver": "vmware"}, ...]
+filter group.tasks as t { t.driver is "vmware" } // subset of tasks that is true
+```
+
+## Statements
+
+### Expression Statements
+
+Function call expressions may exist in the statement context.
+
+```ebnf
+ExpressionStmt = Expression .
+```
+
+### Assignments
+
+Assignments set the value of an expression to the value of another expression.
+
+```ebnf
+Assignment = AssignExpr assign_op Expression .
+AssignExpr = identifier | IndexExpr .
+assign_op = [ add_op | mul_op ] "=" .
+```
+
+The assignment `x op= y` is equivalent to `x = x op (y)` for supported values of `op`.
+
+```sentinel
+a = 1
+b[12] = 42
+c["key"] = "value"
+
+a *= 12
+b[12] += 8
+```
+
+Assignments to lists and maps can be carried out using [index expressions](#index-expressions), with the operands of the index expression being evaluated alongside the right hand side expression in a right-to-left (RHS, LHS) order.
+
+In addition to the rules detailed in the section describing their use, the following rules apply when assigning to maps or lists using index expressions:
+
+- The [variable](#variables) must exist and be a list or map. Attempts to use an index assignment on an unknown variable, or a variable that not a list or map, results in a runtime error.
+- Assigning to a list or map index that already exists overwrites that index's data with evaluated right hand side's value.
+- Assigning to an unknown key in a map creates a key for that map with the evaluated right hand side's value assigned to that key.
+- Attempting to assign a value to a list index that is out of range results in a runtime error.
+
+### If Statements
+
+If statements specify the conditional execution of two branches according to the value of a boolean expression. If the expression evaluates to true, the "if" branch is executed, otherwise, if present, the "else" branch is executed.
+
+```ebnf
+IfStmt = "if" BoolExpr Block [ "else" ( IfStmt | Block ) ] .
+```
+
+```sentinel
+if x < y {
+ return x
+} else if x > z {
+ return z
+} else {
+ return y
+}
+```
+
+### Case Statements
+
+Case statements specify a selection control mechanism to conditionally execute a branch based on expression equality.
+
+Each clause that wishes to provide an expression must use the "when" keyword. Multiple expressions can be provided, separated with a comma (",").
+
+There can be one, optional, "else" clause within a `case` statement. The "else" clause is executed if all other clauses failed to run.
+
+The clauses are evaluated in the order they are listed, with the first successful evaluation being executed.
+
+A `case` statement can optionally provide an expression. If no expression is provided, it is the equivalent of writing `case true {`.
+
+```ebnf
+CaseStmt = "case" [ Expression ] "{" { CaseWhenClause } "}" .
+CaseWhenClause = CaseWhenCase ":" StatementList .
+CaseWhenCase = "when" Expression { "," Expression } | "else" .
+```
+
+```sentinel
+case x {
+when y, z:
+ return true
+else:
+ return false
+}
+
+case {
+when x > 42:
+ return true
+else:
+ return false
+}
+```
+
+### For Statements
+
+A `for` statement specifies repeated execution of a block.
+
+The expression must evaluate to a list or a map.
+
+When iterating over a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When iterating over a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+ForStmt = "for" Expressions "as" [ identifier "," ] identifier Block .
+```
+
+```sentinel
+for [1, 2, 3] as v { count += v } // basic sum
+
+for [1, 2, 3] as idx, v {
+ if idx > 1 { count += v }
+}
+
+data = { "a": 12, "b": 32 }
+for data as k { count += data[k] } // sum map values
+for data as k, v { count += v }
+```
+
+### Break Statements
+
+A `break` statement terminates execution of the innermost `for` statement
+within the same function.
+
+```ebnf
+BreakStmt = "break" .
+```
+
+```sentinel
+// Will only print "1"
+for [1, 2, 3] as v {
+ print(v)
+ break
+}
+```
+
+### Continue Statements
+
+A `continue` statement begins the next iteration of the innermost `for` loop.
+The `for` loop must be within the same function.
+
+```ebnf
+ContinueStmt = "continue" .
+```
+
+```sentinel
+// Will print 1, 3
+for [1, 2, 3] as v {
+ if v == 2 {
+ continue
+ }
+
+ print(v)
+ break
+}
+```
+
+### Return Statements
+
+A return statement in a function F terminates the execution of F and provides the result value.
+
+```ebnf
+ReturnStmt = "return" Expression .
+```
+
+A function must terminate with a return statement.
+
+The return statement can be invoked at any time during a function to terminate execution.
+
+## Imports
+
+An import adds an assignment to the global scope to external definitions.
+
+The import declarations must only appear at the top of the source file, before any other statements. Comments may appear before imports.
+
+```ebnf
+ImportDecl = "import" Name ["as" identifier] .
+Name = string_lit .
+```
+
+An import name and identifier must be distinct. Two names may not repeated even if they're declared with different identifiers.
+
+If an identifier is not specified, the identifier is the name of the import itself.
+
+An import is not a valid value. The identifier representing the import may not be used as an expression, such as in assignment statements or call expressions.
+
+Values in imports are not assignable directly via their selectors. Function calls may be used to alter the state of the external data represented by the import. Whether or not this is supported is specific to the import implementation. The exception to this assignment restriction is the memoized data returned by a call to an import (see below).
+
+Data returned by imports can be memoized, meaning that data is returned by the import in the form of a map which is then used as much as possible without having to return to the import for more data. For example, `t = time.now` returns a map so that `t.hour` will be able to be looked up without having to go back to the import for that data.
+
+Data returned by imports may be callable, meaning that methods may be called on the memoized data returned by an import. For example, given `t = time.now`, `t.after(some_previous_time)` is valid, with the receiver data being set to the local memoized data stored in `t`. It is a valid case to accept modifications to the receiver data (example: assignment to the memoized data via index expressions), as long as all data in in the receiver continues to be string-keyed. It is an invalid case to accept receiver data with non-string-typed keys. Methods may also alter the contents of receiver data so that it is different after the call is finished.
+
+As with methods, imports may also have callable keys where the data may not be memoized. Example: in the event of `v = foo.bar`, if `v.baz` is not included in the memoized data, a call will be made to the import to fetch it. The same rules and restrictions to receiver data apply to callable keys as do to method calls.
+
+The support for callable methods and non-memoized keys on data returned by imports is specific to the import implementation.
+
+The handling of import loading is specific to the runtime implementation.
+
+Implicit imports may be added by the runtime, for example for standard library definitions. An explicit import of the same name should override a matching implicit import. For example, if "time" is an implicit import and the program specifies `import "time" as cal`, then only `cal` is defined.
+
+```sentinel
+import "time"
+import "math" as m
+```
+
+## Parameters
+
+```ebnf
+ParamDecl = "param" identifier [ "default" Expression ]
+```
+
+A parameter is a way for a policy to describe variables that are expected to be supplied by the calling application, in addition to any default value.
+
+Parameter declarations must appear at the top of the source file, following imports.
+
+Like imports, comments may appear before parameters; this is mainly pertinent when the policy is not importing anything, which would make parameters the first declarations that appear in a policy.
+
+A parameter declaration describes a valid variable that is added to the scope of a policy at runtime. This variable has the same properties and rules as other variables assigned during the course of policy evaluation as defined by the specification. This includes having a dynamic type and being able to be re-assigned during execution.
+
+Parameters may only be strings, integers, floating point numbers, booleans, lists, or maps. More complex types such as rules or functions are not allowed.
+
+A parameter can specify a default value by adding one in the declaration via use of the "default" keyword, and supplying a literal for the default value. The literal for a default is a very limited expression, comprising of:
+
+- For strings, a simple string literal;
+- For integers and floating-point numbers, either a literal denoting the value, or a unary expression with the literal, and the operators - or +;
+- For booleans, the pre-declared identifiers true or false;
+- For lists and maps, their respective literals composed of values and keys (for maps) of the above values only.
+
+Any other type of expression or identifier is not allowed.
+
+A parameter that does not have a default value defined is required by the policy. Evaluating a policy with a required parameter that has not been supplied at execution results in a runtime error.
+
+Parameters must not conflict with imports or any other identifiers currently defined in the global scope, including any reserved or pre-declared identifiers. Attempting to assign a parameter to an existing value results in a runtime error.
+
+```sentinel
+import "time"
+
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+param time default "11:00" // error, conflicts with "time" import
+param undefined // error, reserved identifier
+```
+
+## Built-in Functions
+
+Built-in functions are predeclared. They are called like any other function.
+
+### Length
+
+The built-in function `length` returns the length of a collection of string.
+
+The length of a string is the number of bytes in the string. It is not necessarilly the number of characters.
+
+The length of a collection is the number of elements in that collection.
+
+The length of `undefined` is `undefined`.
+
+### Collections
+
+#### List Append
+
+The built-in function `append` appends a value to the end of a list in-place.
+
+Appending to a non-list value results in an immediate `fail`.
+
+The return value of `append` is always the `undefined` value.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+#### Map Delete
+
+The built-in function `delete` deletes elements from a map by key. The map is modified in-place.
+
+Deleting a key that does not exist does nothing.
+
+Calling delete for a non-map value results in an immediate `fail`.
+
+The return value of `delete` is always the `undefined` value.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
+
+#### Keys and Values
+
+The built-in function `keys` and `values` return the keys and values of a map, respectively. The return value is a list. Both functions return values in an unspecified order.
+
+The `keys` or `values` of `undefined` is `undefined`.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+### Range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it defaults to `1`.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4]
+range(1, 5, 2) // [1,3]
+range(0, -3, -1) // [0,-1,-2]
+```
+
+### Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a value of that type according to the rules below.
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
+
+### Printing
+
+The built-in function `print` can be used to output formatted debug information. The runtime may omit print function calls depending on its execution mode. The runtime may choose where the output of a print function goes.
+
+`print` is a variadic function, accepting one or more values to be printed; values are separated by a single whitespace character (`" "`).
+
+The return value of `print` is always `true` to allow it to be used in boolean expressions.
+
+```sentinel
+print("hello") // "hello"
+print("hello", "world") // "hello world"
+print("The", "number", "is", 42) // "The number is 42"
+print([1, 2, 3]) // "[1, 2, 3]"
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // "false"
+```
+
+### Errors
+
+The built-in function `error` is equivalent to `print` but immediately causes execution to halt and the main rule to evaluate to `false`. The program execution is considered to have failed.
+
+## Program Execution
+
+Program execution begins by evaluating the source top to bottom. If evaluation is successful, the `main` rule is evaluated and its result is returned. Execution can be interrupted at any time by an error, which can be called explicitly or implicitly through illegal or undefined behavior.
+
+---
+
+> The content of this page is licensed under the [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
+>
+> Based on [The Go Programming Language Specification](https://golang.org/ref/spec) licensed under [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/language/undefined.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/language/undefined.mdx
new file mode 100644
index 0000000000..dc51531783
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/language/undefined.mdx
@@ -0,0 +1,97 @@
+---
+page_title: Sentinel Language - Undefined
+sidebar_current: docs-language-undefined
+description: >-
+ The value `undefined` is a special value representing undefined behavior or an
+ unknown value. Any operation on an `undefined` value results in `undefined`.
+layout: docs
+---
+
+# Language: Undefined
+
+The value `undefined` is a special value representing undefined behavior
+or an unknown value.
+
+Undefined is not an error because there are situations where the undefined
+value can be safely ignored and the language also provides construts for
+recovering from an undefined value.
+
+The undefined value can be created manually with `undefined`. In most cases,
+undefined is created by accessing a non-existent list element or value.
+The undefined value is created in a number of other circumstances. The documentation
+will note when an undefined value may be created.
+
+Almost any operation with `undefined` results in `undefined`. For example,
+performing math on undefined, attempting to call a function that is undefined,
+etc.
+
+## Main and Undefined
+
+If the value `undefined` is returned from the top-level `main` rule, then
+the policy is considered `false`. However, the runtime provides mechanisms
+for the host application to determine the policy failure was caused by
+an undefined value and report that to the user.
+
+The `main` rule returning the undefined value should be considered a logical
+error in most cases and should probably be corrected by the policy writer.
+
+## Debugging Undefined
+
+When an `undefined` value is first created (either explicitly or implicitly),
+the runtime will record the position where the undefined was created. This
+location can be used to find logic that could be erroneous.
+
+## Boolean Logic and Undefined
+
+Boolean logic is one way to safely recover from `undefined`. If boolean
+logic can safely determine a result despite an undefined value, that result
+will be returned.
+
+For example: `undefined or true` will result in `true`, because no matter
+what actual value `undefined` could've been if the policy behaved properly,
+the boolean expression would still be true.
+
+Another scenario where `undefined` can be safely ignored is short-circuit
+logic with `and`. `false and undefined` will result in `false`.
+
+The full table of logical operations on `undefined` is below:
+
+```sentinel
+undefined or true = true
+undefined or false = undefined
+undefined or undefined = undefined
+undefined and true = undefined
+undefined and false = undefined
+undefined and undefined = undefined
+undefined xor true = undefined
+undefined xor false = undefined
+undefined xor undefined = undefined
+
+// Short-circuit examples
+false or true or undefined = true
+false or undefined or true = true
+true and false and undefined = false
+true and undefined and false = undefined
+```
+
+## Else Expressions
+
+The `else` expression allows explicit recovery from undefined and is useful
+for setting default values.
+
+`else` is an operator where the left and right hand side are values. If the
+left-hand value is `undefined`, the right-hand value is returned. Otherwise,
+the left-hand value is returned.
+
+Examples:
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else "default"
+
+// In more complex scenarios
+
+foo() else 42 == 12
+a = config.value else "default"
+```
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/language/values.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/language/values.mdx
new file mode 100644
index 0000000000..f932480340
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/language/values.mdx
@@ -0,0 +1,210 @@
+---
+page_title: Sentinel Language - Values
+sidebar_current: docs-language-values
+description: Values are the data that Sentinel policies operate on.
+layout: docs
+---
+
+# Language: Values
+
+Values are the _data_ that Sentinel policies operate on. You can create this
+data yourself, for example by just typing the number `42`, or you may access
+this data from an external source to make policy decisions.
+
+Values have a _type_, which determines what kind of operations can be performed
+on the data. Example types are booleans (true and false), numbers,
+and strings (text).
+
+This page documents all the available value types. You can also
+[convert between types](#type-conversion).
+
+## Boolean
+
+A boolean is a value that is either true or false.
+
+A boolean value is created literally with `true` or `false`.
+
+As a policy language, booleans are central to the behavior of Sentinel.
+Booleans are used as conditions in [if statements](/sentinel/language/conditionals),
+are the result of [rules](/sentinel/language/rules), and more. The ultimate
+result of a Sentinel policy is true or false.
+
+## Integer
+
+An integer is a whole number.
+
+An integer is a 64-bit value. This means it can represent numbers from
+-9,223,372,036,854,775,808 to 9,223,372,036,854,775,808.
+
+Integers are created by typing them out literally with no
+separators (such as a comma). An optional prefix can set a non-decimal base:
+`0` for octal, and `0x` for hexadecimal. In hexadecimal literals, letters
+`a-f` and `A-F` represent values 10 to 15.
+
+Example integers are shown below:
+
+```sentinel
+42
+0600
+0xBadFace
+170141183460469
+```
+
+Integers are used for math and numerical comparison.
+
+## Float
+
+A float is a number with a decimal point. It has an integer part, a decimal
+point, a fractional part, and optionally an exponent part. Example
+floats are shown below:
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+Floats are IEEE-754 64-bit floating point numbers.
+
+## String
+
+Strings are text values.
+
+Strings are created by wrapping text in double quotes, such as `"hello"`.
+Within the quotes, any character may appear except newline and an unescaped
+double quote. The text between the quotes is the value.
+
+Because Sentinel policies must be UTF-8 encoded text, strings themselves are
+UTF-8 encoded text. This means you can put any value UTF-8 character into
+a string, such as `"日本語"`.
+
+You can have a string with a literal double-quote by _escaping_ it with
+a backslash. For example: `"they said \"hello\""` turns into the value
+`they said "hello"`.
+
+Backslash escapes can be used for much more than only escaping a double quote.
+Newlines are `\n`, a literal backslash is `\\`, and many more. The full list
+of available backslash escapes can be found
+[in the language specification](/sentinel/language/spec#string-literals).
+
+Example strings:
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+Strings do not support indexing, however it is possible to achieve a similar
+result through the combination of the [strings](/sentinel/imports/strings)
+standard import and list indexing.
+
+```sentinel playground
+import "strings"
+
+asciistr = "Hello, world!"
+utf8str = "日本語"
+
+utf8split = rule {
+ strings.split(utf8str, "")[2] == "語"
+}
+
+asciisplit = rule {
+ strings.split(asciistr, "")[5] == ","
+}
+
+main = rule { utf8split and asciisplit }
+```
+
+Strings support [slicing](/sentinel/language/slices) to efficiently create
+substrings.
+
+## List
+
+See [Lists](/sentinel/language/lists).
+
+## Map
+
+See [Maps](/sentinel/language/maps).
+
+## Rule
+
+See [Rules](/sentinel/language/rules).
+
+## Function
+
+See [Functions](/sentinel/language/functions).
+
+## Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a
+value of that type. Some examples are shown below followed by a list of exact
+rules for type conversion.
+
+```sentinel
+int(42) // 42
+int("42") // 42
+int(42.8) // 42
+int(true) // 1
+
+float(1.2) // 1.2
+float(1) // 1.0
+float("4.2") // 4.2
+float(true) // 1.0
+
+string("foo") // "foo"
+string(88) // "88"
+string(0xF) // "15"
+string(true) // "true"
+
+bool("true") // true
+bool(1) // true
+bool(-1) // true
+bool(0.1) // true
+bool("false") // false
+bool(0) // false
+```
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/language/variables.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/language/variables.mdx
new file mode 100644
index 0000000000..7516890212
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/language/variables.mdx
@@ -0,0 +1,99 @@
+---
+page_title: Sentinel Language - Variables
+sidebar_current: docs-language-vars
+description: Variables store values that can be used later.
+layout: docs
+---
+
+# Language: Variables
+
+Variables store values that can be used later.
+
+Most notably, a variable assignment of `main` is required for all
+Sentinel policies. This is the main [rule](/sentinel/language/rules) that
+is executed to determine the result of a policy. The `main` rule may
+use other variables that are assigned in the policy.
+
+Example:
+
+```sentinel
+a = 1
+b = a + 1
+```
+
+In this example, two variables are assigned: `a` and `b`. `a` is given
+the value of "1" and `b` uses the `a` to calculate its own value.
+
+## Syntax
+
+The syntax for assigning a variable is:
+
+```sentinel
+IDENTIFIER = VALUE
+```
+
+On the left of the equal sign is an _identifier_. This is a name for your
+variable and will be how you reference it later. An identifier is any
+combination of letters and digits, but must start with a letter. An
+underscore `_` is also a valid letter.
+
+On the right is any valid Sentinel value or expression. A value is a
+literal value such as a number or string. An expression is some computed
+value such as doing math, calling a function, etc.
+
+## Assignment and Reassignment
+
+A variable is _assigned_ when it is given a value. You can also _reassign_
+variables at any time by setting it to a new value. The new value takes
+effect for any subsequent use of that variable. A variable can be reassigned
+to a different type.
+
+For example:
+
+```sentinel
+a = 1 // a = 1
+b = a // b = 1
+a = "value" // a = "value", b = 1
+c = a // c = "value", b = 1
+```
+
+In the above example you can see that the variables are set and reassigned.
+Notice that the value of a variable is the _current_ value, and that reassigning
+a variable only affects _future_ uses of that variable. You can see this with
+`c` and `b` being different values.
+
+## Unassigned Variables
+
+Using a variable that is _unassigned_ is an error.
+
+In the example below, the first line would result in the policy erroring.
+Sentinel is executed top-down and the value of `c` is not available yet
+on the first line.
+
+```sentinel
+a = c // Error!
+c = 1
+```
+
+## Type Conversion
+
+While a topic more related to [values][lang-values], variables can be assigned
+(or-reassigned) a different value type via type conversion.
+
+[lang-values]: /sentinel/language/values
+
+```sentinel
+s = "1.1"
+a = int(s) // a = 1
+a = float(s) // a = 1.1
+
+a = 1
+s = string(a) // s = "1"
+```
+
+For more details, see the type conversion sections in the
+[values][lang-values-type-conversion] page, or the [Sentinel Language
+Specification][lang-spec-type-conversion].
+
+[lang-values-type-conversion]: /sentinel/language/values#type-conversion
+[lang-spec-type-conversion]: /sentinel/language/spec#type-conversion
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/nomad.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/nomad.mdx
new file mode 100644
index 0000000000..a105b0337d
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/nomad.mdx
@@ -0,0 +1,51 @@
+---
+page_title: Nomad and Sentinel
+sidebar_current: docs-app-nomad
+description: >-
+ Nomad Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies can currently execute on job
+ submission (creation, update).
+layout: docs
+---
+
+# Nomad
+
+Nomad is a simple, flexible scheduler and workload orchestrator.
+[Nomad Enterprise](https://www.hashicorp.com/products/nomad/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies can currently execute on job submission (creation, update).
+
+Sentinel policies have full access to the job structure. This allows the
+Sentinel policy to control behavior based on any attribute within a job,
+such as the driver, resource requests, network configuration, volume
+configuration, and more. The information that Sentinel policies have access
+to will expand over time.
+
+Nomad fully supports all [enforcement levels](/sentinel/concepts/enforcement-levels).
+For soft mandatory policies, the `sentinel-override` capability must be
+available on the user's ACL policy to allow override. Overrides are always
+logged.
+
+The Nomad integration with Sentinel is documented in depth in the
+[Nomad Enterprise documentation](https://www.nomadproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Only allow Docker-based jobs.**
+
+```sentinel
+# Test policy only allows Docker based tasks
+main = rule { all_drivers_docker }
+
+# all_drivers_docker checks that all the drivers in use are Docker
+
+all_drivers_docker = rule {
+ all job.task_groups as tg {
+ all tg.tasks as task {
+ task.driver is "docker"
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/terraform.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/terraform.mdx
new file mode 100644
index 0000000000..5922fd0cfb
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/terraform.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Terraform and Sentinel
+sidebar_current: docs-app-terraform
+description: >-
+ Sentinel can be used with Terraform and Terraform Enterprise to enforce policy
+ on Terraform configurations, states, and plans.
+layout: docs
+---
+
+# Terraform
+
+[Terraform Enterprise](https://www.hashicorp.com/products/terraform/) uses Sentinel to enforce
+policy on [Terraform](https://www.terraform.io) configurations, states, and plans.
+
+The Sentinel integration with Terraform runs within
+[Terraform Enterprise](https://www.hashicorp.com/products/terraform/)
+after a `terraform plan` and before a `terraform apply`. The policies
+have access to the created plan, the state at the time of the plan,
+and the configuration at the time of the plan.
+
+The Terraform integration with Sentinel is documented in depth in the [Terraform Enterprise documentation](https://www.terraform.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show basic examples.
+
+### Examples
+
+**Example: All AWS instances must have a tag**
+
+```sentinel
+import "tfplan"
+
+main = rule {
+ all tfplan.resources.aws*instance as *, instances {
+ all instances as \_, r {
+ (length(r.applied.tags) else 0) > 0
+ }
+ }
+}
+```
+
+**Example: Only allow GCP instance sizes smaller than `n1-standard-16`**
+
+```sentinel
+import "tfplan"
+
+allowed_machine_types = [
+ "n1-standard-1",
+ "n1-standard-2",
+ "n1-standard-4",
+ "n1-standard-8",
+]
+
+main = rule {
+ all tfplan.resources.google_compute_instance as _, instances {
+ all instances as _, r {
+ r.applied.machine_type in allowed_machine_types
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/vault.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/vault.mdx
new file mode 100644
index 0000000000..6db2f8af22
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/vault.mdx
@@ -0,0 +1,64 @@
+---
+page_title: Vault and Sentinel
+sidebar_current: docs-app-vault
+description: >-
+ Vault Enterprise uses Sentinel to augment the built-in policy system to
+ provide Role Governing Policies (RGPs) and Endpoint Governing Policies (EGPs)
+ to enable complex, flexible policies across identities and endpoints.
+layout: docs
+---
+
+# Vault
+
+[Vault Enterprise](https://www.hashicorp.com/products/vault/) uses Sentinel
+to augment the built-in policy system to provide Role Governing Policies (RGPs)
+and Endpoint Governing Policies (EGPs) to enable complex, flexible policies
+across identities and endpoints.
+
+**Role Governing Policies (RGPs)** are Sentinel policies that are tied to
+particular tokens, Identity entities, or Identity groups. They have access to
+a rich set of controls across various aspects of Vault. These are evaluated
+whenever a token they're attached to is used.
+
+**Endpoint Governing Policies (EGPs)** are Sentinel policies that are tied to
+particular paths instead of tokens. They have access to as much request
+information as possible, but they can take effect even on unauthenticated
+paths, such as login paths.
+
+The Vault integration with Sentinel is documented in depth in the
+[Vault Enterprise documentation](https://www.vaultproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Endpoint policy that requires MFA authentication from a corporate network.**
+
+```sentinel
+import "sockaddr"
+
+# We expect logins to come only from our private IP range
+cidrcheck = rule {
+ sockaddr.is_contained(request.connection.remote_addr, "10.20.0.0/16")
+}
+
+# Require Ping MFA validation to succeed
+ping_valid = rule {
+ mfa.methods.ping.valid
+}
+
+main = rule when request.path is "auth/ldap/login" {
+ ping_valid and cidrcheck
+}
+```
+
+**Example: Endpoint policy that disallows tokens created before a certain time.**
+
+```sentinel
+import "time"
+import "strings"
+
+main = rule when not strings.has_prefix(request.path, "auth/ldap/login") {
+ time.load(token.creation_time).unix > time.load("2017-09-17T13:25:29Z").unix
+}
+```
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/writing/basic.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/writing/basic.mdx
new file mode 100644
index 0000000000..b62533466e
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/writing/basic.mdx
@@ -0,0 +1,113 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-basic
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Policy Basics
+
+Sentinel policies are easy to write while still supporting advanced constructs
+for creating complex policies. This page will explain the basics of writing
+Sentinel policies to get started. You don't need any prior Sentinel knowledge,
+but we do recommend reading the
+[getting started guide](/sentinel/intro/getting-started/first-policy) and
+[language guide](/sentinel/language) after this.
+
+Sentinel policies are text files written using the [Sentinel language](/sentinel/language).
+The policies are evaluated top-to-bottom. The value of `main` after execution
+determines whether a policy passes or fails.
+
+## The Simplest Policy
+
+Sentinel only requires that a policy have a `main` variable that evaluates to
+a boolean value.
+
+A valid example is shown below:
+
+```sentinel playground
+main = 10 > 5
+```
+
+This type of minimal policy is not purely academic. In practice, simple
+policies can often be reduced to a single line logical statement resulting
+in `true` or `false`. However, the expression is usually wrapped in a
+[rule](/sentinel/language/rules) for testing reasons.
+
+You can verify Sentinel will execute this minimal policy using the CLI:
+
+```
+$ sentinel apply minimal.sentinel
+Pass
+```
+
+## Logical Expressions
+
+Policy is at its core a set of logic: you can or can not perform some action
+under a certain set of circumstances. Those circumstances are logical
+expressions. Therefore, Sentinel policies primarily translate into logical
+expressions.
+
+Detailed documentation on boolean expressions is
+[available in the language guide](/sentinel/language/boolexpr).
+
+A simple numerical comparison was seen in the first example on this page.
+Sentinel also provides inclusion operators such as `contains`, `any`, `all`, and
+more. Sentinel allows some operators to have aliases to promote readability
+while remaining programmer-familiar, such as `==` which can equivalently be `is`.
+
+The example below verifies that all numbers in a list are even:
+
+```sentinel playground
+numbers = [6, 22, 8, 4, 12]
+main = all numbers as n { n % 2 is 0 }
+```
+
+## Variables
+
+A policy will very often use variables. Applications such as
+[Nomad](https://www.nomadproject.io) inject variables into the global
+scope of a policy for making policy decisions. For example, Nomad injects
+the job that is being run into the policy scope. Knowing how to use
+variables is critical to effectively using Sentinel.
+
+Detailed documentation on how to define and access variables is
+[available in the language guide](/sentinel/language/variables).
+
+Variables can be defined and used explicitly. For example:
+
+```sentinel playground
+value = 10
+main = value > 5
+```
+
+But they may also be introduced implictly by the host system. Nomad
+injects `job` into policies to describe the job that is being run. The
+policy below is a valid policy that requires a job have two task groups.
+Notice that `job` is not defined anywhere. It is implicitly inserted by
+the host application (in this case Nomad). Refer to the application you're
+writing policy for to determine if it implicitly inserts values.
+
+```json sentinel
+{
+ "policy": "main = length(job.task_groups) is 2",
+ "globals": {
+ "job": {
+ "task_groups": ["foo", "bar"]
+ }
+ }
+}
+```
+
+## And more!
+
+The Sentinel language supports many more features such as functions,
+loops, and more. You can learn about all of this in the
+[complete language guide](/sentinel/language).
+
+The other pages in the [writing policy](/sentinel/writing)
+will cover other information you need to know about writing Sentinel
+policies that isn't simply a language reference.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/writing/debugging.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/writing/debugging.mdx
new file mode 100644
index 0000000000..7dc284d222
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/writing/debugging.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-debugging
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Debugging
+
+As policies become more complex, it becomes harder to understand exactly why
+a policy may be behaving differently than you expect.
+
+Prior to actively debugging, there are multiple best practices we recommend
+following. We recommend breaking your policy down into a set of
+[rules](/sentinel/writing/rules). This will make it easier to understand
+[traces](/sentinel/writing/tracing) and make it easier to
+[test](/sentinel/writing/testing) your policies.
+This should help to debug policies up to a significant complexity.
+
+## Print Logging
+
+The built-in [print function](/sentinel/language/logging-errors#print)
+can be used to log data. The print output is only shown during failures
+or when [tracing is explicitly enabled](/sentinel/writing/tracing).
+
+The `print` function outputs each argument, which can be of any type, converted
+to a human-friendly string format. Arguments are separated with a single space.
+
+Example:
+
+```sentinel playground
+value = 42
+print("the number is", value) // the number is 42
+
+object = { "foo": false }
+print(object) // { "foo": false }
+
+main = rule { true }
+```
+
+The `print` function returns the value `true` so that it can also be
+used within rule expressions. Note that the `print` function should be
+used at the beginning of statements otherwise they may never be
+reached. This is due to internal performance techniques within Sentinel
+like [short-circuiting](https://en.wikipedia.org/wiki/Short-circuit_evaluation).
+
+Example:
+
+```sentinel playground
+// rule_a and rule_b will evaluate to false
+rule_a = rule { print("rule_a is printed") and false } // "rule_a is printed"
+rule_b = rule { false and print("rule_b is printed") } // Nothing is printed
+
+// rule_c and rule_d will evaluate to true
+rule_c = rule { print("rule_c is printed") or true } // "rule_c is printed"
+rule_d = rule { true or print("rule_d is printed") } // Nothing is printed
+
+main = rule { rule_a or rule_b or rule_c and rule_d }
+```
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/writing/imports.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/writing/imports.mdx
new file mode 100644
index 0000000000..74ac751ee4
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/writing/imports.mdx
@@ -0,0 +1,117 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom import](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only local
+context for making policy decisions.
+
+Sentinel also comes with a set of [standard imports](/sentinel/imports).
+Standard imports are available to every Sentinel policy to help policy writers
+with common tasks such as working with the time, network addresses, and more.
+
+-> **This page is about writing policies that _use_ imports.** If you're
+interested in _creating_ a new import, please see the section on [extending
+Sentinel](/sentinel/extending) for information on how to write modules and
+import plugins.
+
+## Using Imports
+
+To use an import, you use the `import` keyword at the top of your policy. This
+specifies the name of the import you want to use. The application you're writing
+the policy for must already be
+[configured](/sentinel/configuration#imports) to provide that
+import.
+
+Details on imports can be found in the
+[import section in the language reference](/sentinel/language/imports).
+
+In the example below, we use the [time](/sentinel/imports/time) import:
+
+```sentinel playground
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+There are two options to develop a policy locally when using imports:
+configure Sentinel to launch the import on `apply`, or mock the import
+using `mock`. The former requires access to the import while the
+latter is faster (doesn't have to launch a process for plugins) and
+doesn't require the import.
+
+## Mocking Imports
+
+The first option to developing policies locally is to mock the import values.
+When mocking an import, you don't need the import to be available. This can be
+useful since some imports may not be available as a plugin and may only be
+available to the application the policy runs in.
+
+Mocks are specified via the [configuration
+file](/sentinel/configuration). Mocks can also be used for
+[testing](/sentinel/writing/testing).
+
+You can supply mock configuration one of two ways, depending on your use case:
+
+- **[Using static data](/sentinel/configuration#mocking-static-data):**
+ Use this method when you can accurately represent your mock data in JSON and
+ do not need to mock complex Sentinel features such as functions.
+- **[Using Sentinel code](/sentinel/configuration#mocking-with-sentinel-code):**
+ Use this method when using a static JSON object is insufficient, such as when
+ you need to mock functions or other complex Sentinel features.
+
+Our example does not require complex data to be mocked, so a static object
+is sufficient:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ hour = 12
+ weekday_name = "Tuesday"
+ }
+ }
+}
+```
+
+This can be used via the CLI:
+
+```
+$ sentinel apply -config=config.hcl policy.sentinel
+Pass
+```
+
+## Launching Import Plugins
+
+If you have access to the plugin binary, you can launch the import. The
+benefit of this is that it is really using the import to test your
+policy. If the import changes, your policies may start failing. If you
+only use mock data and the import changes, your policies will still
+appear to work.
+
+Imports are configured in the configuration file:
+
+```hcl
+import "plugin" "custom_time" {
+ source = "/path/to/sentinel-time-import"
+}
+```
+
+This would require the `sentinel-time-import` binary. For this example
+this doesn't currently exist. We plan on writing one to provide for this
+section of the documentation.
+
+## Custom Imports
+
+You can also [create your own imports](/sentinel/extending).
+
+If your policy decisions could benefit from accessing external information,
+then you can use custom imports as a way to do this.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/writing/index.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/writing/index.mdx
new file mode 100644
index 0000000000..064069d389
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/writing/index.mdx
@@ -0,0 +1,35 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing
+description: >-
+ Sentinel provides a language and workflow for building policy across any
+ system that embeds Sentinel. By learning Sentinel once, you are able to
+ effectively control access to many systems using Sentinel's powerful features.
+ Basic concepts that are important to understand for Vault usage.
+layout: docs
+---
+
+# Writing Sentinel Policy
+
+This section covers how to write Sentinel policies.
+
+It is recommended that you complete the
+[getting started guide](/sentinel/intro/getting-started/first-policy) prior to
+reading this section of the documentation. The getting started guide will
+explain all the basics of writing and testing policies. This section of the
+documentation will serve as more of a reference guide to all available
+features of Sentinel.
+
+Sentinel provides a language and workflow for building policy across any system
+that embeds Sentinel. By learning Sentinel once, you are able to effectively
+control access to many systems using Sentinel's powerful features.
+
+Sentinel also provides a [local CLI](/sentinel/commands) for developing and testing
+Sentinel policies. This CLI can be integrated into a daily developer's workflow
+along with CI to treat [policy as code](/sentinel/concepts/policy-as-code).
+
+Sentinel uses its own [language](/sentinel/language) for writing
+policies. You can view a [language reference](/sentinel/language)
+as well as the [specification](/sentinel/language/spec) for details. You
+don't have to read those documents immediately, since the language should
+be easy enough to pick up throughout this section.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/writing/rules.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/writing/rules.mdx
new file mode 100644
index 0000000000..ab4fc69d08
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/writing/rules.mdx
@@ -0,0 +1,65 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-rules
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Rules
+
+Rules form the basis of a policy by representing behavior that is either passing
+or failing. Rules are a first class language construct in Sentinel. A policy can
+and should be broken down into rules to aid with readability, testability, and
+performance.
+
+Rules provide:
+
+- **Readability:** A policy that is broken down into rules can be read
+ more easily. The logic becomes clearer to see for policy writers when
+ they come back to it.
+
+- **Reportability:** When [tracing](/sentinel/writing/tracing) is enabled,
+ rules form the foundation of the data returned. All evaluated rules are added
+ to the trace with the data the rule evaluated to, and their position within
+ policy or module source files.
+
+- **Testability:** [Policy testing](/sentinel/writing/testing) is
+ built on asserting the values of rules. By breaking logic down into
+ rules, you're able to more effectively test your policies.
+
+- **Performance:** Rules are only evaluated on demand, and are also only
+ evaluated once. This means that a rule referenced multiple times only has a
+ one-time performance cost. For complex logic, this could result in improved
+ performance.
+
+An example usage of rules is shown below:
+
+```json sentinel
+{
+ "policy": "is_sunny = rule { weather is \"sunny\" }\nis_wednesday = rule { day is \"wednesday\" }\nmain = rule { is_sunny and is_wednesday }",
+ "globals": {
+ "weather": "sunny",
+ "day": "wednesday"
+ }
+}
+```
+
+Rules can also contain non-boolean data. This can be useful for passing more
+detail to the caller than an opaque boolean value would be able to communicate.
+
+```sentinel playground
+days = [{"weather": "sunny"}]
+main = rule {
+ filter days as d {
+ d.weather is not "sunny"
+ }
+}
+```
+
+Details about rules and their behavior can be found in
+[the language reference](/sentinel/language/rules). Rules have important
+behavior so we recommend reading the language reference on rules.
+Rules will be used throughout the remainder of the documentation.
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/writing/testing.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/writing/testing.mdx
new file mode 100644
index 0000000000..61ab0ea46c
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/writing/testing.mdx
@@ -0,0 +1,491 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-testing
+description: >-
+ Sentinel has a built-in test framework to validate a policy behaves as
+ expected.
+layout: docs
+---
+
+# Testing
+
+Sentinel has a built-in test framework to validate a policy behaves as expected.
+
+With an ever-increasing amount of automation surrounding technology,
+the guardrails provided by policies are a critical piece towards ensuring
+expected behavior. As a reliance on correct policy increases, it is important
+to test and verify policies.
+
+Testing is a necessary step to fully realize
+[policy as code](/sentinel/concepts/policy-as-code). Just as good software
+is well tested, a good set of policies should be equally well tested.
+
+## Test Folder Structure
+
+Policies are tested by asserting that [rules](/sentinel/writing/rules)
+are the expected values given a pre-configured input. Tests are run
+by executing the [test command](/sentinel/commands/test).
+
+Sentinel is opinionated about the folder structure required for tests.
+This opinionated structure allows testing to be as simple as running
+`sentinel test` with no arguments. Additionally, it becomes simple to test
+in a CI or add new policies.
+
+The structure Sentinel expects is `test//*.[hcl|json]` where ``
+is the name of your policy file without the file extension. Within that folder
+is a list of HCL or JSON files. Each file represents a single test case.
+Therefore, each policy can have multiple tests associated with it.
+
+-> Note that the primary configuration file format for Sentinel is HCL. While
+you can write configuration files in a HCL-equivalent JSON, we only discuss the
+use of HCL on this page.
+
+## Test Case Format
+
+Each HCL file within the test folder for a policy is a single test case.
+
+The file is the same configuration format as the [CLI configuration
+file](/sentinel/configuration). The format lets you define mock data, imports to
+use, and more. This mock data is the key piece in being able to test policies:
+you craft a specific scenario and assert your policy behaves as you expect.
+
+Test cases also use the `test` block within the configuration file to assert the
+value of [rules](/sentinel/writing/rules). If the `test` key is omitted, the
+policy is expected to pass. If the test key is specified, only the rules
+specified in the map will be asserted. This means if you omit `main`, then the
+final policy result is not asserted.
+
+Example with assertions:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+
+test {
+ rules = {
+ main = false
+ is_open_hours = false
+ is_weekday = true
+ }
+}
+```
+
+The configuration above specifies some parameter data, and asserts the result of
+some rules. This is the same configuration used in the example section below.
+
+## Example
+
+Lets use the following file as an example. Save this file to a directory
+and name it `policy.sentinel`. It can be named anything with the `sentinel`
+extension, but by naming it `policy.sentinel` your output should match
+the example output on this page.
+
+```sentinel
+// The day of the week.
+param day
+
+// The hour of the day.
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+### A Passing Test
+
+Next, let's define a single test case. Relative to where you saved
+the policy, create a file at the path `test/policy/good.hcl`.
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 14
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/good.hcl
+```
+
+This passed because the policy passed. We didn't assert any specific
+rules. By not specifying any assertions, `test` expects the policy itself
+to fully pass.
+
+### A Failing Test
+
+Define another test case to fail. We want to verify our policy fails when expected, too.
+
+Save the following as `test/policy/7-am.hcl`:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+FAIL - policy.sentinel
+ FAIL - test/policy/7-am.hcl
+ expected "main" to be true, got: false
+
+ trace:
+ policy.sentinel:9:1 - Rule "main"
+ bool: false
+
+ policy.sentinel:8:1 - Rule "is_open_hours"
+ bool: false
+
+ PASS - test/policy/good.hcl
+```
+
+As you can see, the test fails because "main" is false. This is good
+because the policy _should_ have failed since we specified an invalid
+hour. But, we expect main to be false and don't want our test to fail!
+Update `7-am.hcl` to add test assertions:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+
+test {
+ rules = {
+ main = false
+ is_open_hours = false
+ }
+}
+```
+
+And when we run the tests:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/7-am.hcl
+ PASS - test/policy/good.hcl
+```
+
+The test passes. We asserted that we expect the `main` rule to be false,
+the `is_open_hours` rule to be false, and the `is_weekday` rule to be
+true. By asserting some rules are true, we can verify that our policy
+is failing for reasons we expect.
+
+## Mocking
+
+The above example demonstrates how to test by supplying different
+[parameters](/sentinel/language/parameters). Parameters in a policy can be
+specifically useful when you want to control user-defined input values to a
+policy.
+
+However, generally, when testing, you will need mimic the conditions you will
+see in production. Production implementations of Sentinel will supply data using
+one of two methods:
+
+- **Global data:** Data is injected directly into the policy's scope and is
+ accessible using normal identifiers, similar to variables.
+- **Imports:** Data is stored behind an [import](/sentinel/language/imports)
+ and loaded on demand as needed by the policy author.
+
+Proper testing of a policy requires that these values be able to be _mocked_ -
+or, in other words, simulated in a way that allows the accurate testing of the
+scenarios that a policy could reasonably pass or fail under.
+
+Mocking both globals and imports can be done by setting various parts of the
+configuration file.
+
+### Mocking Globals
+
+Demonstrating the mocking of globals can be seen by making a few modifications to
+our example policy, removing the `param` declarations:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Then, change the `param` section in the configuration file to
+[`global`](/sentinel/configuration#global).
+
+```json
+global "day" {
+ value = "monday"
+}
+
+global "hour" {
+ value = 14
+}
+```
+
+This test should still pass, as if nothing had happened, although what we've
+done is shifted our parameters to globals, simulating an environment where `day`
+and `hour` are already defined for us.
+
+### Mocking Imports
+
+To mock imports, we need to use the
+[`mock`](/sentinel/configuration#mock-imports) section of the
+configuration file.
+
+Let's say the above example is behind an import named `time`.
+
+-> **NOTE:** [`time`](/sentinel/imports/time) is a valid standard import.
+This example may not be accurate to the
+import's syntax.
+
+The code now looks like this:
+
+```sentinel
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To mock this import, we can [mock it as static
+data](/sentinel/configuration#mocking-static-data). The configuration
+file now looks like, without assertions:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ weekday_name = "Monday"
+ hour = 14
+ }
+ }
+}
+```
+
+The policy will now pass, with the `time` import mocked.
+
+Data can also be [mocked as Sentinel
+code](/sentinel/configuration#mocking-with-sentinel-code). In this case,
+the above configuration file would look like:
+
+```hcl
+mock "time" {
+ module {
+ source = "mock-time.sentinel"
+ }
+}
+```
+
+And a file named `mock-time.sentinel` would now hold your mock values:
+
+```sentinel
+day = "monday"
+hour = 14
+```
+
+Mocking as Sentinel code allows more complex details to be mocked as well, such
+as functions. Say we wanted to mock the `time.load()` function. To mock this,
+just add it to the `mock-time.sentinel` file:
+
+```sentinel
+load = func(_) {
+ return {
+ "weekday_name": "Monday",
+ "hour": 14,
+ }
+}
+```
+
+Your code can now be written as:
+
+```sentinel
+import "time"
+
+t = time.load("a_mock_timestamp")
+
+is_weekday = rule { t.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { t.hour > 8 and t.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To see more details, see the [Mock
+Imports](/sentinel/configuration#mock-imports) section in the
+configuration file.
+
+## Asserting Non-Boolean Rules
+
+Non-boolean rules can also be asserted by `sentinel test`.
+
+To assert non-boolean values, simply enter the expected value into the rule
+contents:
+
+```hcl
+param "maintenance_days" {
+ value = [
+ {
+ day = "wednesday"
+ hour = 9
+ },
+ {
+ day = "friday"
+ hour = 1
+ },
+ {
+ day = "sunday"
+ hour = 1
+ },
+ ]
+}
+
+test {
+ rules = {
+ main = [
+ {
+ day = "wednesday"
+ hour = 9
+ },
+ ]
+ }
+}
+```
+
+This would assert a policy checking for violations of a maintenance policy,
+saying that maintenance hours should happen before 6AM on any given day:
+
+```sentinel
+param maintenance_days
+
+main = rule {
+ filter maintenance_days as d {
+ d.hour >= 6
+ }
+}
+```
+
+## Test JSON Output
+
+Running `sentinel test` with the `-json` flag will give you the test results in
+a JSON output format, suitable for parsing by reporting software.
+
+-> **Note:** The JSON output format is currently under development and the
+format may change at a later time. Additionally, support for other well-known
+formats, such as JUnit, may become available in the future.
+
+The current format is an object with the only top-level key being `policies`,
+with each test grouped up by policy being run.
+
+The policy result fields are:
+
+- `path`: The path of the policy and the index of the test result in the
+ `policies` field in the root object.
+- `status`: A string representation of the policy's test status as a whole. Can
+ be one of `PASS`, `FAIL`, `ERROR`, or `?`. The final status, `?`, represents a
+ policy that has no tests to process, and acts like a passing test.
+- `errors`: An array of any error messages encountered during processing the
+ policy for testing. Usually reserved for policy file or parser-related errors.
+ For case-specific errors, see the error field for the particular case.
+- `cases`: A map of case results, indexed by case path.
+
+The case result fields are:
+
+- `path`: The path of the test case and the result's index in the `cases` field
+ in the policy object.
+- `status`: A string representation of the case's test status. Can be one of
+ `PASS`, `FAIL` or `ERROR`.
+- `errors`: An array of any error messages encountered during running this test
+ case.
+- `trace`: The trace for this policy in JSON format. See the
+ [tracing](/sentinel/writing/tracing) page for more details.
+- `rule_detail`: When the `status` of this test case is `FAIL`, contains an
+ object of assertion failure detail, indexed by rule. Only failures are counted
+ here; any rules not found here can be assumed to have passed or not asserted.
+- `config_warnings`: An array of strings denoting any configuration warnings
+ found while processing the configuration for this test case.
+- `config_legacy`: Denotes whether or not the configuration is a legacy JSON
+ configuration and needs to be modernized. This field may be removed in future
+ releases.
+
+A passing example is shown below:
+
+```json
+{
+ "policies": {
+ "policy.sentinel": {
+ "path": "policy.sentinel",
+ "status": "PASS",
+ "errors": null,
+ "cases": {
+ "test/policy/pass.hcl": {
+ "path": "test/policy/pass.hcl",
+ "status": "PASS",
+ "errors": null,
+ "trace": {
+ "description": "A very basic policy to determine if a run is within working hours.",
+ "error": null,
+ "print": "",
+ "result": true,
+ "rules": {
+ "is_open_hours": {
+ "desc": "Passes if run during business hours.",
+ "ident": "is_open_hours",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 290,
+ "line": 13,
+ "column": 1
+ },
+ "value": true
+ },
+ "is_weekday": {
+ "desc": "Passes if the day does not fall on the weekend.",
+ "ident": "is_weekday",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 193,
+ "line": 10,
+ "column": 1
+ },
+ "value": true
+ },
+ "main": {
+ "desc": "",
+ "ident": "main",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 338,
+ "line": 14,
+ "column": 1
+ },
+ "value": true
+ }
+ }
+ },
+ "rule_detail": {},
+ "config_warnings": null,
+ "config_legacy": false
+ }
+ }
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.21.x/content/sentinel/docs/writing/tracing.mdx b/content/sentinel/v0.21.x/content/sentinel/docs/writing/tracing.mdx
new file mode 100644
index 0000000000..f2069bd682
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/docs/writing/tracing.mdx
@@ -0,0 +1,401 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-tracing
+description: >-
+ Sentinel provides execution traces to better understand the execution of a
+ policy.
+layout: docs
+---
+
+# Traces
+
+Sentinel provides execution traces to better understand the execution of a policy.
+
+When using the Sentinel [CLI](/sentinel/commands), traces are shown
+on policy failure during `apply` or `test`, or when the `-trace` flag is
+explicitly specified. For Sentinel-enabled applications, traces are optional
+and may require special configuration to enable.
+
+The availability of the trace may vary from application to application. While
+some applications may only log traces on failure, others, such as [Terraform
+Cloud](https://www.terraform.io/cloud), log the trace on every execution. For
+more details, consult your implementation's documentation.
+
+-> **Note:** We're actively improving Sentinel tracing in each release
+to provide better data and improve readability. The exact format of a
+trace may not match the Sentinel version embedded in the application you're
+using, but the data should be similar. The canonical format for a trace should
+get more stable as we approach a 1.0 release.
+
+## Analyzing a Trace
+
+To show traces, let's use the example policy below with the CLI:
+
+```sentinel
+param day
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+
+main = rule { is_open_hours and is_weekday }
+```
+
+Let's cause the policy to fail so the trace is more interesting.
+If you're on a terminal that supports it, the trace output will be colored
+so you can more clearly see passes, failures, and rules.
+
+
+
+ $ sentinel apply main.sentinel{'\n'}
+
+ main.sentinel:1:7: requires value for parameter day
+
+ {'\n'}
+ {'\n'}
+ {'\n'}
+ {' '}Values can be strings, floats, or JSON array or object values. To force
+ {'\n'}
+ {' '}strings, use quotes.{'\n'}
+ {'\n'}
+ {' '}Enter a value: sunday{'\n'}
+ {'\n'}
+
+ main.sentinel:2:7: requires value for parameter hour
+
+ {'\n'}
+ {'\n'}
+ {'\n'}
+ {' '}Values can be strings, floats, or JSON array or object values. To force
+ {'\n'}
+ {' '}strings, use quotes.{'\n'}
+ {'\n'}
+ {' '}Enter a value: 14{'\n'}
+ {'\n'}
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+
+ Fail - main.sentinel
+
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:7:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}
+ false
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:5:1 - Rule "is_open_hours"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}true{'\n'}
+ {'\n'}
+
+ main.sentinel:4:1 - Rule "is_weekday"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}false{'\n'}
+
+
+
+We can see all of our rules neatly and clearly displayed along with the result
+of the policy. If you are getting the trace due to a failed policy and not
+because you explicitly used `-trace`, an informational message is displayed.
+
+As we can see from our basic example, the `main` rule has failed and its result
+is highlighted in red. All peripheral rules that were also evaluated as part of
+the policy are also displayed below the main rule. Source positions are also
+displayed so that you can find the references to the rules in your code.
+
+Via the trace, we can clearly see that while `is_open_hours` returned a true
+result, `is_weekday` did not, and per the logic in our code, the policy is
+rejected.
+
+## Non-Boolean Rules and the Trace
+
+The trace is particularly important when working with rules that return
+non-boolean data, such as rules where you want to see violations instead of a
+simple opaque boolean value. Let's take our example from the [rules](./rules)
+section, and write a small static policy for it:
+
+```sentinel playground
+days = [
+ {"weekday": "Sunday", "weather": "clouds"},
+ {"weekday": "Monday", "weather": "rain"},
+ {"weekday": "Tuesday", "weather": "sunny"},
+ {"weekday": "Wednesday", "weather": "sunny"},
+ {"weekday": "Thursday", "weather": "clouds"},
+ {"weekday": "Friday", "weather": "rain"},
+ {"weekday": "Saturday", "weather": "sunny"},
+]
+
+main = rule {
+ filter days as d {
+ d.weather is not "sunny"
+ }
+}
+```
+
+Here is the output of our policy:
+
+
+
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+ {'\n'}
+
+ Fail - weather.sentinel
+
+ {'\n'}
+ {'\n'}
+
+ weather.sentinel:11:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+
+ {' [\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Sunday"'}
+ {'\n'}
+ {' "weather": "clouds"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Monday"'}
+ {'\n'}
+ {' "weather": "rain"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Thursday"'}
+ {'\n'}
+ {' "weather": "clouds"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Friday"'}
+ {'\n'}
+ {' "weather": "rain"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' ]'}
+
+
+
+
+We can now see all of the weekdays with non-sunny weather.
+
+Note that to prevent formatting issues and excessive output, the human-readable
+trace is limited in the volume of output it will display for collections.
+
+## JSON Output
+
+The trace can also be displayed in JSON by adding `-json` to `sentinel apply`
+and `sentinel test`, and will display the trace in its entirety, unlike the
+standard CLI output.
+
+Here is the result of running `sentinel apply -json` against our weather policy:
+
+```json
+{
+ "can_override": false,
+ "error": null,
+ "policies": [
+ {
+ "allowed_failure": false,
+ "error": null,
+ "policy": "weather.sentinel",
+ "result": false,
+ "trace": {
+ "description": "",
+ "error": null,
+ "print": "",
+ "result": false,
+ "rules": {
+ "main": {
+ "desc": "",
+ "ident": "main",
+ "position": {
+ "filename": "weather.sentinel",
+ "offset": 328,
+ "line": 11,
+ "column": 1
+ },
+ "value": [
+ {
+ "weather": "clouds",
+ "weekday": "Sunday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Monday"
+ },
+ {
+ "weather": "clouds",
+ "weekday": "Thursday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Friday"
+ }
+ ]
+ }
+ }
+ }
+ }
+ ],
+ "result": false
+}
+```
+
+Note that the trace is always included with this output, regardless of whether
+or not the policy passes or fails; using `-trace` is unnecessary.
+
+Additionally, with `sentinel apply`, you can display the value of a single rule
+in JSON, instead of the entire trace. This is done with the `-json-rule` flag.
+
+-> **Note:** `sentinel apply -json-rule` needs to be run either against a single
+on-disk policy, or against a single policy within a policy set. It is ignored
+in full policy set executions and functions exactly like `-json` in these
+scenarios.
+
+Here is the result of executing `sentinel apply -json-rule main weather.sentinel`:
+
+```json
+[
+ {
+ "weather": "clouds",
+ "weekday": "Sunday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Monday"
+ },
+ {
+ "weather": "clouds",
+ "weekday": "Thursday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Friday"
+ }
+]
+```
+
+## Other Trace Details
+
+There are some other details that are good to know when working with the trace:
+
+- Only rules that get executed are added to the trace. Considering our first
+ example where the `main` rule is the expression `is_open_hours and is_weekday`,
+ if our expression was using an `or` operator instead of `and`, in addition to
+ the policy passing, `is_open_hours` would be present in the trace, but
+ `is_weekday` would not be, as the policy would have never evaluated that rule.
+- Descriptions for both rules and policies will show up in the trace as well if
+ present. Here is the output to our first policy with the appropriate
+ annotations:
+
+
+
+ ...{'\n\n'}
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+
+ Fail - main.sentinel
+
+ {'\n'}
+ {'\n'}
+ Description:{'\n'}
+ {' A very basic policy to determine if a run is within working\n'}
+ {' hours.\n'}
+ {'\n'}
+
+ main.sentinel:7:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}
+ false
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:5:1 - Rule "is_open_hours"
+
+ {'\n'}
+ {' '}
+ Description:
+ {'\n'}
+ {' Passes if run during business hours.\n'}
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}true{'\n'}
+ {'\n'}
+
+ main.sentinel:4:1 - Rule "is_weekday"
+
+ {'\n'}
+ {' '}
+ Description:
+ {'\n'}
+ {' Passes if the day does not fall on the weekend.\n'}
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}false{'\n'}
+
+
diff --git a/content/sentinel/v0.21.x/content/sentinel/intro/getting-started/first-policy.mdx b/content/sentinel/v0.21.x/content/sentinel/intro/getting-started/first-policy.mdx
new file mode 100644
index 0000000000..38d7b22c9a
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/intro/getting-started/first-policy.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Your First Sentinel Policy
+sidebar_current: intro-getting-started-first
+description: Let's begin by writing a working Sentinel policy.
+layout: intro
+---
+
+# Your First Sentinel Policy
+
+Sentinel is a system to enforce complex policies on an integrated application.
+
+Writing Sentinel policy requires minimal programming experience. The Sentinel
+language is designed to be approachable and learned quickly and easily. Whether
+you're a professional programmer or someone who uses SQL and Excel, you can
+learn to write Sentinel policies.
+
+Let's begin by writing a simple, working Sentinel policy:
+
+```sentinel playground
+hour = 4
+main = rule { hour >= 0 and hour < 12 }
+```
+
+This is a valid Sentinel policy. It will pass since we hardcoded the
+`hour` to be 4. In a real system, `hour` may be something that is provided
+to us and actually set to the current hour. We'll learn more about that later.
+
+For now, try running this policy locally. Save the above example to a file
+named `policy.sentinel` and execute it. Then, modify the policy to make it
+fail. Play around more if you'd like.
+
+```
+$ sentinel apply policy.sentinel
+Pass
+```
+
+## Main
+
+Every Sentinel policy must have a `main` rule. This is the rule that
+is evaluated to determine the result of a policy.
+
+A rule describes an expression that generally means one of two things:
+
+- Does a policy _pass a condition_ that would authorize an operation? In our
+ above example, describe a policy that checks the supplied hour (4) is within an
+ authorized time window (between 0 - midnight, and 12 noon).
+- Conversely, can a policy find any _violations_ that would block authorization
+ of the operation? Building on the above, consider a policy that takes a
+ schedule, and finds all time blocks that fall outside of the example time window
+ supplied in the above policy.
+
+It is easy to imagine that such a rule might be used in a system such
+as [Nomad](https://www.nomadproject.io) to restrict the times when a
+deploy can occur. The power of arbitrary logical statements within Sentinel
+allows Sentinel policies to restrict almost any behavior.
+
+Next, we'll
+[introduce and explain rules](/sentinel/intro/getting-started/rules)
+so we can use them in our policies.
diff --git a/content/sentinel/v0.21.x/content/sentinel/intro/getting-started/imports.mdx b/content/sentinel/v0.21.x/content/sentinel/intro/getting-started/imports.mdx
new file mode 100644
index 0000000000..dbbb146ecf
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/intro/getting-started/imports.mdx
@@ -0,0 +1,58 @@
+---
+page_title: Imports
+sidebar_current: intro-getting-started-imports
+description: Imports enable Sentinel to access external data and functions.
+layout: intro
+---
+
+# Imports
+
+Imports enable Sentinel to access external data and functions.
+
+Sentinel ships with a set of [standard imports](/sentinel/imports).
+Standard imports are available by default to every Sentinel policy. Note that
+the application embedding Sentinel may allow or deny the available
+set of imports.
+
+To use an import, use the `import` statement. Then, access data and
+functions on the import using the import name followed by a period and
+the field you want to access. The example below checks whether the request
+path starts with a prefix. Assume that `request_path` is available.
+
+```sentinel
+import "strings"
+
+main = rule { strings.has_prefix(request_path, "/admin") }
+```
+
+## External Data
+
+The true power in imports is their ability to reference external data.
+Imports can be added to a Sentinel-enabled application through
+[plugins](/sentinel/extending). This enables any Sentinel-enabled
+application to make policy decision based on any source of data.
+
+This ability allows the policy system to enforce almost any necessary
+organizational policy, since the ability of a policy isn't restricted
+purely to the embedding application's data model.
+
+For example, policies in [Nomad](https://www.nomadproject.io) can
+access data in [Consul](https://www.consul.io) to determine attributes
+of a policy. In the example below, we use a hypothetical Consul import:
+
+```sentinel
+import "consul"
+
+main = rule {
+ job.tasks[0].resources.memory <= int(consul.get("policy/nomad/max-memory"))
+}
+```
+
+In this example, a Nomad job's memory usage is limited to the value of
+a Consul key/value item. By simply changing a Consul KV entry, policy
+can be changed. Imports can do anything, you can [write your own import plugins](/sentinel/extending)
+to extend Sentinel.
+
+Next, we'll learn how to
+[test policies](/sentinel/intro/getting-started/testing)
+to ensure our policy logic is correct.
diff --git a/content/sentinel/v0.21.x/content/sentinel/intro/getting-started/index.mdx b/content/sentinel/v0.21.x/content/sentinel/intro/getting-started/index.mdx
new file mode 100644
index 0000000000..5c9b6b43c5
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/intro/getting-started/index.mdx
@@ -0,0 +1,16 @@
+---
+page_title: Install Sentinel CLI - Getting Started
+sidebar_current: intro-getting-started-overview
+description: The first step to using Sentinel is to get the CLI installed.
+layout: intro
+---
+
+# Getting Started With Sentinel
+
+This section covers getting started with Sentinel. Here, we will take you
+through the first steps that you will need to do to get started with installing
+the Sentinel CLI, writing your first policy, and getting familiar with rules,
+boolean logic, imports, and testing.
+
+You can select a topic on the menu to get started. The first step is to [install
+the Sentinel CLI](/sentinel/intro/getting-started/install).
diff --git a/content/sentinel/v0.21.x/content/sentinel/intro/getting-started/install.mdx b/content/sentinel/v0.21.x/content/sentinel/intro/getting-started/install.mdx
new file mode 100644
index 0000000000..e478fd532f
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/intro/getting-started/install.mdx
@@ -0,0 +1,56 @@
+---
+page_title: Install Sentinel CLI - Getting Started
+sidebar_current: intro-getting-started-install
+description: The first step to using Sentinel is to get the CLI installed.
+layout: intro
+---
+
+# Install Sentinel CLI
+
+Sentinel is a policy framework that is embedded in the enterprise versions of
+HashiCorp tools. The policies you write are deployed to these applications and
+enforced there.
+
+The Sentinel CLI is a command-line interface for local development and testing.
+For the getting started guide, we'll use the CLI to learn how to write policies
+for Sentinel-enabled applications. The Sentinel CLI is distributed as a [binary
+package](/sentinel/downloads) for all supported platforms and architectures.
+
+## Installation Instructions
+
+To install the Sentinel CLI, find the [appropriate package](/sentinel/downloads) for your
+system and download it. The CLI is packaged as a zip archive.
+
+After downloading Sentinel, unzip the package. The CLI runs as a single binary
+named `sentinel`. Any other files in the package can be safely removed and
+Sentinel will still function.
+
+The final step is to make sure that the `sentinel` binary is available on the `PATH`.
+See [this page](https://stackoverflow.com/questions/14637979/how-to-permanently-set-path-on-linux)
+for instructions on setting the PATH on Linux and Mac.
+[This page](https://stackoverflow.com/questions/1618280/where-can-i-set-path-to-make-exe-on-windows)
+contains instructions for setting the PATH on Windows.
+
+## Verifying the Installation
+
+After installing the Sentinel CLI, verify the installation worked by opening a
+new terminal session and checking that the `sentinel` binary is available. By
+executing `sentinel`, you should see help output similar to the following:
+
+```
+$ sentinel
+Usage: sentinel [--version] [--help] []
+
+Available commands are:
+ apply Execute a policy and output the result
+ fmt Format Sentinel policy to a canonical format
+ test Test policies
+ version Prints the Sentinel version
+```
+
+If you get an error that the binary could not be found, then your `PATH`
+environment variable was not setup properly. Please go back and ensure that your
+`PATH` variable contains the directory where Sentinel was installed.
+
+Otherwise, the CLI is installed and ready to go. Let's celebrate by [writing our
+first policy!](/sentinel/intro/getting-started/first-policy)
diff --git a/content/sentinel/v0.21.x/content/sentinel/intro/getting-started/logic.mdx b/content/sentinel/v0.21.x/content/sentinel/intro/getting-started/logic.mdx
new file mode 100644
index 0000000000..d3716a64ea
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/intro/getting-started/logic.mdx
@@ -0,0 +1,54 @@
+---
+page_title: Logic
+sidebar_current: intro-getting-started-logic
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Logic
+
+The core of a policy is a set of logical expressions to determine whether
+a behavior is allowed or not.
+Sentinel makes it easy to write readable logical expressions to express
+the intended policy.
+
+The logical expression syntax will be familiar to anyone who has programmed
+before. Sentinel also supports a couple more unique constructs to assist
+with common policy requirements. Detailed documentation on how to write
+logical expressions and the supported operators is available in
+[the boolean expression reference](/sentinel/language/boolexpr).
+
+Example logic:
+
+```sentinel
+a is b // Equality, you can also use ==
+a is not b // Inequality, you can also use !=
+
+a > b // Relational operators, comparison
+a < b
+a >= b
+a <= b
+
+a contains b // Inclusion check, substrings, etc.
+a not contains b // Negated version of above
+```
+
+The full list of available operators can be found in
+[the boolean expression reference](/sentinel/language/boolexpr).
+
+Logic can be combined with `and` or `or`. When combined with `and`, both
+sides must result in `true`. When combined with `or`, only one side needs
+to be true to result in `true`.
+
+With these building blocks, basic rules can be built up:
+
+```sentinel
+main = rule {
+ hour >= 8 and
+ hour < 17
+}
+```
+
+Next, we'll introduce
+[imports](/sentinel/intro/getting-started/imports)
+so that we can write rules that interact with external data.
diff --git a/content/sentinel/v0.21.x/content/sentinel/intro/getting-started/next-steps.mdx b/content/sentinel/v0.21.x/content/sentinel/intro/getting-started/next-steps.mdx
new file mode 100644
index 0000000000..23b470be59
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/intro/getting-started/next-steps.mdx
@@ -0,0 +1,21 @@
+---
+page_title: Next Steps
+sidebar_current: intro-getting-started-nextsteps
+description: That concludes the getting started guide for Sentinel.
+layout: intro
+---
+
+# Next Steps
+
+That concludes the getting started guide for Sentinel. Hopefully you're excited
+about the possibilities of Sentinel and ready to put this knowledge to use to
+improve the safety of your environments.
+
+We've covered the basics of the core features of Sentinel in this guide.
+We recommend the following as next steps:
+
+- [Documentation](/sentinel) - The documentation is an in-depth
+ reference guide of all the features of Sentinel.
+
+- [Language Reference](/sentinel/language) - The language reference
+ is a complete reference to the features of the Sentine policy language.
diff --git a/content/sentinel/v0.21.x/content/sentinel/intro/getting-started/rules.mdx b/content/sentinel/v0.21.x/content/sentinel/intro/getting-started/rules.mdx
new file mode 100644
index 0000000000..f12a61fe75
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/intro/getting-started/rules.mdx
@@ -0,0 +1,86 @@
+---
+page_title: Rules
+sidebar_current: intro-getting-started-rules
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Rules
+
+Rules in Sentinel are a first-class concept. Within a policy, rules serve a few
+purposes:
+
+- They make complex logic more understandable by allowing said logic to be
+ broken down.
+- They allow assertion of this logic through
+ [testing](/sentinel/intro/getting-started/testing) of the rule's contents.
+- They facilitate reporting of data as rules get published as part of the [policy
+ trace](/sentinel/writing/tracing).
+
+A rule functions in ways similar to both a variable and a function: they hold a
+value, but are lazily evaluated; a rule's value is not assigned until the first
+time it's referenced in a policy. Additionally, while the value of evaluated
+rules will be available within a policy's trace after it's evaluated, values of
+variables - and the return value of functions - are not. Finally, a rule value
+is memoized - further references to the rule will not change its result.
+
+Rules can hold more than just boolean data. For more advanced rule patterns, see
+[the language reference](/sentinel/language/rules).
+
+## Writing Rules
+
+Let's look at the Sentinel policy we wrote in the previous section:
+
+```sentinel playground
+hour = 4
+main = rule { hour >= 0 and hour < 12 }
+```
+
+The logic in this policy is straightforward and easy to understand. However,
+it is common for policy logic to become much more complicated. Rules are the
+key abstraction for making complex logic understandable. We can turn the
+policy above into a set of rules:
+
+```sentinel playground
+hour = 4
+
+past_midnight = rule { hour >= 0 }
+before_noon = rule { hour < 12 }
+
+main = rule {
+ past_midnight and
+ before_noon
+}
+```
+
+This policy is easier to read. Our original policy was already quite
+simple, but it is easy to imagine a more complex policy becoming much
+more readable by extracting logical expressions into a set of rules.
+
+Rules don't just exist to make a policy more readable. They're also a
+testing and debugging point. For testing, you can assert that certain rules
+are certain values given a specific input to verify that your policy works
+as you expect. Testing and debugging are covered in later sections.
+
+## Conditional Rules
+
+In many cases, a rule is only valid when something else is also true.
+
+Consider the human language statement "before you eat, you must wash your hands."
+The rule "you must wash your hands" is only valid "before you eat". In any
+other scenario, the rule is meaningless.
+This is also true in a technical context. Imagine the rule "if the driver
+is Docker, you must not expose any ports." For this example, assume
+rules `is_docker` and `has_no_exposed_ports` exist. You can write this rule
+like this:
+
+```sentinel
+main = rule when is_docker { has_no_exposed_ports }
+```
+
+When `is_docker` is true, the rule is evaluated as normal. However,
+when `is_docker` is false, the rule always returns `true`, because the
+logic of the rule is meaningless.
+
+Let's learn how to create more complex rules with
+[logical expressions](/sentinel/intro/getting-started/logic).
diff --git a/content/sentinel/v0.21.x/content/sentinel/intro/getting-started/testing.mdx b/content/sentinel/v0.21.x/content/sentinel/intro/getting-started/testing.mdx
new file mode 100644
index 0000000000..7149a7966f
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/intro/getting-started/testing.mdx
@@ -0,0 +1,72 @@
+---
+page_title: Testing
+sidebar_current: intro-getting-started-testing
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Testing
+
+It is important to ensure the policies you write are correct. Sentinel
+includes a built-in test framework that can be run locally and in CI
+environments to test that policies behave as expected.
+
+A common pitfall with many simple ACL systems is that they provide no easy
+way to verify their correctness. You basically have to set the ACL and try
+the behaviors against a real system to verify it is working as expected.
+This requires a lot of setup and is unique to each system.
+
+[Sentinel's built-in test framework](/sentinel/writing/testing) has zero
+dependencies. Contained within the [Sentinel CLI](/sentinel/commands), it can
+mock the data that real systems are exposing to the policy. It is designed to be
+CI-friendly and enables continuous testing of your policies. This is necessary
+for [policy as code](/sentinel/concepts/policy-as-code).
+
+Detailed documentation on testing policies is available in [the Sentinel Testing
+reference](/sentinel/writing/testing).
+
+## Writing Tests
+
+For this example, save the following policy as `policy.sentinel`:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Next, let's write a passing test case. This will test that the policy tests
+when we expect it to pass. Save the following in `test/policy/good.hcl`:
+
+```hcl
+global "day" {
+ value = "monday"
+}
+
+global "hour" {
+ value = 14
+}
+```
+
+And run `sentinel test`:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/good.json
+```
+
+The `sentinel test` command will automatically find all Sentinel policies
+and their associated test cases and run them all. The test framework will
+run all HCL files as individual test cases, allowing you to test a variety
+of scenarios for your policies.
+
+Try adding another test case to force the policy to fail. This test case can
+be saved at `test/policy/fail.hcl`. For test cases with intentional
+failures, you'll need to use the [test assertions described in the testing
+reference](/sentinel/writing/testing#a-failing-test).
+
+Now that you have a good grasp of what Sentinel is and how to use it, please feel
+free to look through the
+[next steps](/sentinel/intro/getting-started/next-steps)
+you can take to further your Sentinel knowledge.
diff --git a/content/sentinel/v0.21.x/content/sentinel/intro/index.mdx b/content/sentinel/v0.21.x/content/sentinel/intro/index.mdx
new file mode 100644
index 0000000000..785a442fb8
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/intro/index.mdx
@@ -0,0 +1,32 @@
+---
+layout: intro
+page_title: Documentation
+sidebar_current: docs-home
+description: >-
+ Sentinel is a language and framework for policy built to be embedded in
+ existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+[introduction](/sentinel/intro) to understand what Sentinel is, how it
+compares to other software, and more.
+
+
diff --git a/content/sentinel/v0.21.x/content/sentinel/intro/what.mdx b/content/sentinel/v0.21.x/content/sentinel/intro/what.mdx
new file mode 100644
index 0000000000..36853d366c
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/intro/what.mdx
@@ -0,0 +1,64 @@
+---
+page_title: Introduction
+sidebar_current: intro-what
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: intro
+---
+
+# Introduction to Sentinel
+
+Welcome to the intro guide to Sentinel! This guide is the best
+place to start with Sentinel.
+
+If you are already familiar with the basics of Sentinel, the
+[documentation](/sentinel) provides a better reference
+guide for all available features as well as internals.
+
+## What is Sentinel?
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+Sentinel provides a language for writing policy and a workflow for developing
+and testing policies independent of the software they'll be written to. We
+also provide a framework for developers to build plugins that allow a
+Sentinel-enabled system to access external information to make policy
+decisions.
+
+Most systems today have some degree of access control. You are able to define
+identities and what they have access to. These ACL systems solve an immediate
+and necessary problem of locking down a system in very broad strokes. Sentinel
+is a reusable system for more advanced software policy decisions. Sentinel
+enables:
+
+- **Fine-Grained Policy**: Most ACL systems only enable coarse-grained
+ behaviors: "read", "write", etc. Sentinel enables fine-grained behavior such
+ as disallowing a certain API call when specific parameters are present.
+
+- **Logic-Based Policy**: You can write policy using full
+ conditional logic. For example, you may only allow a certain application behavior
+ on Monday to Thursday unless there is a manager override.
+
+- **Accessing External Information**: Sentinel can source external information
+ to be used in policy decisions. For example, a policy that restricts the size
+ of a payload may read data from [Consul](https://www.consul.io) to determine
+ the payload size limit.
+
+- **Enforcement levels**: Sentinel allows policies to be defined along
+ with an "enforcement level" that dictates the pass/fail behavior of a policy.
+ Advisory policies warn if they fail, soft mandatory policies can have their
+ failures overridden, and hard mandatory policies must pass under all
+ circumstances. Having this as a built-in concept enables you to model
+ policy more accurately and completely for your organization.
+
+## Next Steps
+
+See the page on [Why Sentinel?](/sentinel/intro/why) to learn more
+about the origins of the product. Then, continue onwards with
+the [getting started guide](/sentinel/intro/getting-started/install) to write
+your first policies and see how Sentinel works in practice.
diff --git a/content/sentinel/v0.21.x/content/sentinel/intro/why.mdx b/content/sentinel/v0.21.x/content/sentinel/intro/why.mdx
new file mode 100644
index 0000000000..98b58aab92
--- /dev/null
+++ b/content/sentinel/v0.21.x/content/sentinel/intro/why.mdx
@@ -0,0 +1,49 @@
+---
+page_title: Why Sentinel?
+sidebar_current: intro-why
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: intro
+---
+
+# Why Sentinel?
+
+The growth of infrastructure and applications has been enabled in part
+by an increasing trend towards automation everywhere.
+Configuration as code (such as Chef or Puppet with [Packer](https://www.packer.io))
+has enabled automated machine configuration. Infrastructure
+as code (such as [Terraform](https://www.terraform.io)) has enabled
+automatic infrastructure creation. And schedulers (such as
+[Nomad](https://www.nomadproject.io) and Kubernetes) have enabled
+automatic application deployment.
+Sentinel enables guardrails to be put in place
+on automation while allowing the codification and automatic enforcement
+of business requirements in critical areas of your infrastructure.
+
+Meanwhile, businesses have business requirements and sometimes legal
+requirements which must be expressed in policies. Traditionally, these
+policies are enforced by humans. But in a highly automated world, the
+automation is only as fast as its slowest component. In many cases, this
+is the human verification step.
+
+As an example: before infrastructure as code and autoscaling, if an order
+came through for 5,000 new machines, a human would likely respond to the
+ticket verifying that the user really intended to order 5,000 new machines.
+Today, automation can almost always freely order 5,000 new compute instances
+without any hesitation, which can result in unintended expense or system
+instability.
+
+Sentinel introduces [policy as code](/sentinel/concepts/policy-as-code)
+and a powerful framework built-in to HashiCorp tooling to allow automation
+guardrails, business requirements, legal compliance, and more to be
+actively enforced by the running systems in realtime.
+
+With Sentinel, you can require an override to create certain numbers of
+infrastructure resources. You can disallow unsafe deployment configurations
+with Nomad. You can enforce certain key/value formats in Consul. You
+can restrict secret access by time in Vault. And more.
+
+Sentinel is available today in HashiCorp enterprise products. You can
+try Sentinel immediately with the [getting started guide](/sentinel/intro/getting-started/install) or learn more about the integrations in the
+[documentation](/sentinel).
diff --git a/content/sentinel/v0.21.x/data/docs-nav-data.json b/content/sentinel/v0.21.x/data/docs-nav-data.json
new file mode 100644
index 0000000000..1e281f044a
--- /dev/null
+++ b/content/sentinel/v0.21.x/data/docs-nav-data.json
@@ -0,0 +1,324 @@
+[
+ {
+ "title": "Release Notes",
+ "path": "changelog"
+ },
+ {
+ "title": "Basic Concepts",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "concepts"
+ },
+ {
+ "title": "Policy as Code",
+ "path": "concepts/policy-as-code"
+ },
+ {
+ "title": "Policy Language",
+ "path": "concepts/language"
+ },
+ {
+ "title": "Imports",
+ "path": "concepts/imports"
+ },
+ {
+ "title": "Enforcement Levels",
+ "path": "concepts/enforcement-levels"
+ }
+ ]
+ },
+ {
+ "title": "Configuration File Syntax",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "configuration"
+ },
+ {
+ "title": "Override Files",
+ "path": "configuration/overrides"
+ },
+ {
+ "title": "Remote Sources",
+ "path": "configuration/remote-sources"
+ }
+ ]
+ },
+ {
+ "title": "Commands (CLI)",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "commands"
+ },
+ {
+ "title": "apply",
+ "path": "commands/apply"
+ },
+ {
+ "title": "fmt",
+ "path": "commands/fmt"
+ },
+ {
+ "title": "test",
+ "path": "commands/test"
+ }
+ ]
+ },
+ {
+ "title": "Writing Policy",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "writing"
+ },
+ {
+ "title": "Basics",
+ "path": "writing/basic"
+ },
+ {
+ "title": "Rules",
+ "path": "writing/rules"
+ },
+ {
+ "title": "Traces",
+ "path": "writing/tracing"
+ },
+ {
+ "title": "Testing",
+ "path": "writing/testing"
+ },
+ {
+ "title": "Imports",
+ "path": "writing/imports"
+ },
+ {
+ "title": "Debugging",
+ "path": "writing/debugging"
+ }
+ ]
+ },
+ {
+ "title": "Extending Sentinel",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "extending"
+ },
+ {
+ "title": "Modules",
+ "path": "extending/modules"
+ },
+ {
+ "title": "Plugins",
+ "path": "extending/plugins"
+ },
+ {
+ "title": "Static Imports",
+ "path": "extending/static-imports"
+ },
+ {
+ "title": "Internals",
+ "path": "extending/internals"
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Language",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "language"
+ },
+ {
+ "title": "Variables",
+ "path": "language/variables"
+ },
+ {
+ "title": "Values",
+ "path": "language/values"
+ },
+ {
+ "title": "Lists",
+ "path": "language/lists"
+ },
+ {
+ "title": "Maps",
+ "path": "language/maps"
+ },
+ {
+ "title": "Rules",
+ "path": "language/rules"
+ },
+ {
+ "title": "Imports",
+ "path": "language/imports"
+ },
+ {
+ "title": "Parameters",
+ "path": "language/parameters"
+ },
+ {
+ "title": "Boolean Expressions",
+ "path": "language/boolexpr"
+ },
+ {
+ "title": "Arithmetic",
+ "path": "language/arithmetic"
+ },
+ {
+ "title": "Slices",
+ "path": "language/slices"
+ },
+ {
+ "title": "Conditionals",
+ "path": "language/conditionals"
+ },
+ {
+ "title": "Loops",
+ "path": "language/loops"
+ },
+ {
+ "title": "Collection Operations",
+ "path": "language/collection-operations"
+ },
+ {
+ "title": "Functions",
+ "path": "language/functions"
+ },
+ {
+ "title": "Scope",
+ "path": "language/scope"
+ },
+ {
+ "title": "Undefined",
+ "path": "language/undefined"
+ },
+ {
+ "title": "Logging and Errors",
+ "path": "language/logging-errors"
+ },
+ {
+ "title": "Specification",
+ "path": "language/spec"
+ }
+ ]
+ },
+ {
+ "title": "Builtin Functions",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "functions"
+ },
+ {
+ "title": "append",
+ "path": "functions/append"
+ },
+ {
+ "title": "delete",
+ "path": "functions/delete"
+ },
+ {
+ "title": "error",
+ "path": "functions/error"
+ },
+ {
+ "title": "keys",
+ "path": "functions/keys"
+ },
+ {
+ "title": "length",
+ "path": "functions/length"
+ },
+ {
+ "title": "print",
+ "path": "functions/print"
+ },
+ {
+ "title": "range",
+ "path": "functions/range"
+ },
+ {
+ "title": "values",
+ "path": "functions/values"
+ }
+ ]
+ },
+ {
+ "title": "Standard Imports",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "imports"
+ },
+ {
+ "title": "base64",
+ "path": "imports/base64"
+ },
+ {
+ "title": "decimal",
+ "path": "imports/decimal"
+ },
+ {
+ "title": "http",
+ "path": "imports/http"
+ },
+ {
+ "title": "json",
+ "path": "imports/json"
+ },
+ {
+ "title": "runtime",
+ "path": "imports/runtime"
+ },
+ {
+ "title": "sockaddr",
+ "path": "imports/sockaddr"
+ },
+ {
+ "title": "strings",
+ "path": "imports/strings"
+ },
+ {
+ "title": "time",
+ "path": "imports/time"
+ },
+ {
+ "title": "types",
+ "path": "imports/types"
+ },
+ {
+ "title": "units",
+ "path": "imports/units"
+ },
+ {
+ "title": "version",
+ "path": "imports/version"
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Consul",
+ "path": "consul"
+ },
+ {
+ "title": "Nomad",
+ "path": "nomad"
+ },
+ {
+ "title": "Terraform",
+ "path": "terraform"
+ },
+ {
+ "title": "Vault",
+ "path": "vault"
+ }
+]
diff --git a/content/sentinel/v0.21.x/data/intro-nav-data.json b/content/sentinel/v0.21.x/data/intro-nav-data.json
new file mode 100644
index 0000000000..c95181af70
--- /dev/null
+++ b/content/sentinel/v0.21.x/data/intro-nav-data.json
@@ -0,0 +1,47 @@
+[
+ {
+ "title": "What is Sentinel?",
+ "path": "what"
+ },
+ {
+ "title": "Why Sentinel?",
+ "path": "why"
+ },
+ {
+ "title": "Getting Started",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "getting-started"
+ },
+ {
+ "title": "Installing the CLI",
+ "path": "getting-started/install"
+ },
+ {
+ "title": "Your First Policy",
+ "path": "getting-started/first-policy"
+ },
+ {
+ "title": "Rules",
+ "path": "getting-started/rules"
+ },
+ {
+ "title": "Logic",
+ "path": "getting-started/logic"
+ },
+ {
+ "title": "Imports",
+ "path": "getting-started/imports"
+ },
+ {
+ "title": "Testing",
+ "path": "getting-started/testing"
+ },
+ {
+ "title": "Next Steps",
+ "path": "getting-started/next-steps"
+ }
+ ]
+ }
+]
diff --git a/content/sentinel/v0.21.x/img/sentinel-import-topology.svg b/content/sentinel/v0.21.x/img/sentinel-import-topology.svg
new file mode 100644
index 0000000000..36f7c79bc9
--- /dev/null
+++ b/content/sentinel/v0.21.x/img/sentinel-import-topology.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/content/sentinel/v0.21.x/redirects.jsonc b/content/sentinel/v0.21.x/redirects.jsonc
new file mode 100644
index 0000000000..d9e73f4a49
--- /dev/null
+++ b/content/sentinel/v0.21.x/redirects.jsonc
@@ -0,0 +1,15 @@
+[
+ {
+ "source": "/",
+ "destination": "/sentinel",
+ "permanent": true,
+ },
+ {
+ "source": "/sentinel/commands/config",
+ "destination": "/sentinel/configuration",
+ "permanent": true,
+ },
+ // disallow ".html" or "/index.html" in favor of cleaner, simpler paths
+ { "source": "/:path*/index", "destination": "/:path*", "permanent": true },
+ { "source": "/:path*.html", "destination": "/:path*", "permanent": true },
+]
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/changelog.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/changelog.mdx
new file mode 100644
index 0000000000..9e077cd786
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/changelog.mdx
@@ -0,0 +1,790 @@
+---
+page_title: Sentinel Runtime Release Notes
+sidebar_current: docs-changelog
+description: >-
+ These are the public release notes for the Sentinel runtime. See this document
+ for the latest updates.
+layout: docs
+---
+
+# Sentinel Runtime Release Notes
+
+These are the release notes for the Sentinel runtime.
+
+Each version of the runtime is released with a corresponding version of
+[Sentinel CLI](/sentinel/commands). To download the CLI, see the [downloads
+page](/sentinel/downloads).
+
+Sentinel integrations and embedded runtimes may not always have the latest
+version installed, depending on the product's individual release cycle. For more
+information, contact the support team for your specific integration.
+
+
+## 0.22.1 (June 22, 2023)
+
+BUG FIXES:
+
+- `sentinel/eval`: Under certain conditions, per-policy parameters would cause
+ Sentinel to panic. This has been resolved.
+
+## 0.22.0 (May 31, 2023)
+
+- `config`: Configuration now supports a `sentinel` block to manage the
+ Sentinel runtime.
+
+## 0.21.1 (May 8, 2023)
+
+BUG FIXES:
+
+- `config`: An issue with certain identifiers being treated as incorrectly invalid
+ has been resolved.
+
+## 0.21.0 (March 8, 2023)
+
+BREAKING CHANGES:
+
+- `lang/ast`: `is empty` and `is not empty` are now treated as `ast.UnaryExpr`
+ expressions, with `ast.IsEmptyExpr` being removed.
+- `lang/ast`: You can now assert if a value is defined or not using the `is defined`
+ and `is not defined` syntax.
+
+FEATURES:
+
+- `config`: Parameter values can now be provided for individual policies within
+ a policy block.
+
+## 0.20.0 (February 16, 2023)
+
+ENHANCEMENTS:
+
+- `cmd/testcmd`: Allows sentinel tests to run concurrently by default. Sequential style testing can
+ be enabled by running with the `-maxConcurrency=1` option.
+- `cmd/testcmd`: Allows sentinel test command to timeout after a certain duration. This can be provided
+ by the user or will default to 5 minutes.
+- `cmd/apply`: The policy enforcement level is now included in the JSON output.
+- `lang/ast`: Functions can now be declared as named statements, providing
+ a safer function declaration.
+
+BREAKING CHANGES:
+
+- `cmd/apply`: Policies provided directly to the apply command will now default their enforcement
+ level to `advisory`, aligning with the `policy` configuration block.
+- `sentinel`: JSON results will no longer return `allowed_failure` or `can_override` fields.
+- `sentinel/result`: A new package has been added which provides additional methods to return
+ supplemental data about the evaluation result.
+
+## 0.19.5 (February 9, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `SelectorExpr` will correctly rewrite `X`.
+
+## 0.19.4 (February 8, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `Compile` function will correctly set the `Original`
+ field when rewriting a `CallExpr` as an `ImportExpr`.
+
+## 0.19.3 (February 3, 2023)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.19.2 (January 17, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `Compile` function will no longer modify `ast.CallExpr`
+ arguments in place.
+
+## 0.19.1 (December 14, 2022)
+
+BUG FIXES:
+
+- `lang/ast`: The `Rewrite` function will no longer modify the provided Node
+ in place.
+
+ENHANCEMENTS:
+
+- `lang/semantic`: The Break semantic check uses the AST walker.
+
+## 0.19.0 (December 6, 2022)
+
+BREAKING CHANGES:
+
+- `config`: Attempts to override a standard import with a custom import will
+ now be met with an error.
+- `imports`: All plugin imports now return `sdk.Plugin` instead of `sdk.Import`,
+ as per the SDK update to v0.4.0.
+
+ENHANCEMENTS:
+
+- `runtime/importer`: An `Import` and `ImportInstance` interface have been
+ added to improve the import capabilities.
+- `runtime/importer`: The `Importer` interface now returns a `Import` interface
+ instead of a `sdk.Import`.
+- `lang/semantic`: The Import Assignment semantic check uses the AST walker.
+- `lang/ast`: Added an AST Walker so the AST rewriter does not need to be used for basic AST tasks.
+
+FEATURES:
+
+- `config`: Configuration syntax now allows for configuration of imports within
+ the standard library.
+- `config`: A new configuration syntax for defining modules and plugins is now
+ available.
+- Static data can now be added via `import` configuration.
+- `cmd/apply`: The `apply` command now supports multiple primary configuration
+ files by default, loading all configuration files within the working
+ directory.
+- `cmd/apply`: The `apply` command now accepts applying configuration overrides
+ to primary configuration.
+
+## 0.18.13 (October 31, 2022)
+
+BUG FIXES:
+
+- `cmd/apply`: `sentinel apply` no longer aborts on advisory policy failure.
+
+## 0.18.12 (September 15, 2022)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where nested `CallExpr` were not rewritten.
+
+## 0.18.11 (June 8, 2022)
+
+NOTES:
+
+- `config`: The `enforcement_level` key for policies is now optional, defaulting to `"advisory"` when not supplied.
+
+## 0.18.10 (May 20, 2022)
+
+NOTES:
+
+- `runtime/initwd`: Improvements to the installer, including timeouts and filesize limits, as well as disabling support
+ for symlinks within `git` or `hg` repositories.
+
+## 0.18.9 (March 23, 2022)
+
+NOTES:
+
+- This release fixes an issue with the Docker container build pipeline. There are no changes to the
+Sentinel runtime or CLI.
+
+## 0.18.8 (March 22, 2022)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue in the installer for Windows paths.
+
+## 0.18.7 (February 24, 2022)
+
+NOTES:
+
+- **Darwin arm64**. This release will be our first to include Darwin arm64 architecture.
+
+BUG FIXES:
+
+- `lang/ast`: Fixed issues where `IndexSpec` and `RuleLit` nodes where returning incorrect end positions.
+- `lang/ast`: Fixed an issue where `ParamSpec` nodes where returning incorrect end positions.
+
+## 0.18.6 (February 2, 2022)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.18.5 (January 14, 2022)
+
+IMPROVEMENTS:
+
+- `runtime/initwd`: Changed the sum algorithm used during remote installation from `md5` to `sha256` to reduce chance of collision.
+
+## 0.18.4 (July 20, 2021)
+
+FEATURES:
+
+- `imports/static`: Improved handling of struct tags when performing encoding, allowing `-` to mark as ignored.
+
+## 0.18.3 (June 1, 2021)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue for remote source installation where duplicate sub-directories resulted in incorrect installation.
+- `imports/static`: Fixed an issue where global configuration that contained an empty map could not be accessed without raising selector issues.
+
+## 0.18.2 (May 18, 2021)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where import expressions inside lists and maps were not being evaluated correctly.
+
+## 0.18.1 (May 11, 2021)
+
+BUG FIXES:
+
+- `imports/json`: Fixed an issue where empty maps where failing when passed at any level to `marshal`.
+
+## 0.18.0 (March 25, 2021)
+
+FEATURES:
+
+- `imports/http`: Added support for POST actions within the `http` import
+ through addition of the `post` function. Additionally, support for adding
+ a body to the `request` object has been added via `with_body`.
+
+## 0.17.4 (February 2, 2021)
+
+BUG FIXES:
+
+- `runtime/format`: Fixed a nesting issue with the rule printer where nesting
+ state leaks were leading to all collection values eventually being truncated,
+ regardless of their nesting level.
+
+## 0.17.3 (January 15, 2021)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue where local file installation failed on Windows
+ when using `sentinel test`.
+
+## 0.17.2 (January 13, 2021)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where duplicate log messages were being printed with
+ `sentinel test`.
+
+## 0.17.1 (January 7, 2021)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where hard-coded path separator caused `sentinel
+ test` issues in Windows.
+
+## 0.17.0 (January 5, 2021)
+
+LANGUAGE CHANGES:
+
+- **Map Expression** `map`. A new quantifier expression, `map` has been added.
+ `map` takes a collection and applies an operation to each element in the
+ collection, returning a list of results with the resulting values.
+- **Emptiness checks** `is empty` and `is not empty`. Two new expressions,
+ `is empty` and `is not empty` have been added. As they are aliases to the
+ `length` built-in, only `string`, `map`, `list` or `undefined` is
+ supported.
+- **Comparable maps** Maps (the data type) are now comparable. Maps are equal if
+ they are of equal length and both their corresponding keys and values are
+ comparable and equal.
+- **Rich Return Types** Rules can now process and return non-boolean data.
+ Scalar, list, and map types are supported. When non-boolean values are used,
+ the presence of a non-zero or non-zero-length `main` rule determines policy
+ failure. More details can be found on
+ https://docs.hashicorp.com/sentinel/language#main.
+
+FEATURES:
+
+- **CLI**: `sentinel apply` and `sentinel test` now have options for JSON
+ output. For more details, see the CLI documentation.
+- `imports/base64`: Added a new import, `base64`, to assist with encoding and
+ decoding base64 strings.
+
+## 0.16.1 (October 21, 2020)
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where `contains` to ensure any instance of `undefined` would correctly result in `undefined`.
+- `runtime/eval`: Fixed an issue in `any` and `all` expressions to ensure correct boolean logic when dealing with a collection containing `undefined`.
+- `imports`: Fixed an issue where import processes were not killed, which caused non-graceful closures for the clients.
+- `lang/scanner`: Fixed an issue where inline `#` style comments were not being consumed.
+
+## 0.16.0 (October 14, 2020)
+
+BREAKING CHANGES:
+
+- `cmd/doc`: Removal of the deprecated `sentinel doc` command.
+- `sentinel`: Replace `whitelist` and `blacklist` with `allowlist` and `denylist`, as per inclusive language changes.
+
+FEATURES:
+
+- `imports/version`: Added a new import, `version`, which supplies helpers for dealing with semantic versioning.
+- `cmd/apply`: Added an `HCL` based configuration file, which will eventually deprecate the legacy `JSON` configuration.
+- `cmd/apply`: Added support for download remote policies and modules.
+- `cmd/test`: Added support for downloading remote test modules.
+- `cmd/apply`: Added support for evaluating a policy based on it's key within the configuration.
+- `cmd/apply`: Added support for running all policies in a configuration by default.
+
+## 0.15.6 (July 7, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.15.5 (May 20, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel integrations. There are no user-facing changes.
+
+## 0.15.4 (May 13, 2020)
+
+BUG FIXES:
+
+- `imports`: Fixed an issue with the standard imports that was affecting the handling of null data within lists.
+
+## 0.15.3 (April 16, 2020)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where `IndexExpr` were not rewritten, as well as ensuring `SelectorExpr` uses any nested rewrites.
+- `lang/printer`: Fixed issue printing deeply nested structures and/or loops.
+
+IMPROVEMENTS:
+
+- `imports/decimal`: Added alias methods to improve consistency of method names within the `decimal` import. The alias methods are `is_nan` for `isnan`, as well as both `is_inf` and `is_infinite` for `isinfinite`.
+- `command/apply`: `sentinel apply` will now output the policy description when a trace is forced via `-trace`.
+- `sentinel`: Improved `String` output formatting of Policy docstring for `EvalPolicyResult`.
+
+## 0.15.2 (April 2, 2020)
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue with the `printer` that was causing a panic
+ when a `#` line comment was used with no text following it.
+- `imports/types`: Fixed an issue with the `types` import where calls to
+ `type_of` for undefined types were incorrectly returning as map types. These
+ will now be correctly be identified as undefined as expected.
+
+## 0.15.1 (March 6, 2020)
+
+BUG FIXES:
+
+- `sentinel`: Fixed how modules are managed internally in the runtime to correct
+ race conditions in concurrent scenarios and to allow for per-evaluation module
+ restrictions. This functionality is only of interest to embedded applications
+ with long-lived runtimes and is currently not implemented in any HashiCorp
+ integration.
+
+## 0.15.0 (March 5, 2020)
+
+NOTES:
+
+- **Windows Digital Signature**. Our windows releases for this version and up will be signed
+ and verified according to Microsoft's requirements.
+
+FEATURES:
+
+- `cmd/config`: Modules can now be loaded off of local disk using the Sentinel
+ CLI. For information on how to do so, see the Sentinel documentation.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: Changed modules so that they now have singleton scope when
+ loaded. Importing a module under two different aliases, or within a nested
+ module, will now share scopes - modification of state in one will alter the
+ other.
+- `lang/object`: Introduced an interface to allow the duplication of various
+ types, like collections.
+- `runtime/eval`: Changed how collection data is returned from modules. This
+ data is now duplicated to prevent accidental or deliberate circumvention of
+ the prohibition on assignment to import data, and brings behavior to parity
+ with binary imports.
+
+## 0.14.4 (February 6, 2020)
+
+IMPROVEMENTS:
+
+- `imports/time`: Changed the `add` timespace function in the `time` import to
+ allow it to take float types as durations. These values will be truncated to
+ the appropriate integer-value duration.
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where out-of-place right-hand braces were being
+ parsed as empty statements, instead of raising syntax errors.
+
+## 0.14.3 (January 22, 2020)
+
+IMPROVEMENTS:
+
+- `cmd/test`: Fail when an assertion is not found in a trace.
+- `cmd/test`: Added a message to notify when no rules were evaluated in a test.
+
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue where import aliases (the `as baz` in `import
+ "foo/bar" as baz`) were being removed when formatting with `sentinel fmt`.
+
+- `imports/http`: Fixed an issue with the http import where the client library's
+ debug logs were being printed to standard error.
+
+## 0.14.2 (January 15, 2020)
+
+NOTES:
+
+With this release, we are discontinuing support for MacOS 32-bit. 64-bit builds
+are now the only builds available for MacOS.
+
+IMPROVEMENTS:
+
+- `lang/printer`: A newline is now added to files formatted via `sentinel fmt`.
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue in printing where multi-line expressions would
+ indent infinitely. They will now only indent once at the most. This is mostly
+ seen when using `sentinel fmt`.
+- `runtime/encoding`: Fixed an issue where if a plugin returned a deeply
+ embedded undefined value, the value was instead decoded into a map.
+
+## 0.14.1 (January 6, 2020)
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where reserved words could not be used as
+ selectors when the selector expression was the last lexeme on a line.
+
+## 0.14.0 (December 18, 2019)
+
+LANGUAGE CHANGES:
+
+- **Filter Expression** `filter`. A new quantifier expression, `filter` has been added.
+ `filter` returns a subset of the provided collection based on a boolean condition that
+ is asserted for each element.
+
+NOTES:
+
+- **Apple Notarization**. Our darwin releases for this version and up will be signed
+ and notarized according to Apple's requirements.
+
+## 0.13.1 (November 25, 2019)
+
+BUG FIXES:
+
+- `runtime/eval`: Parameters are no longer allowed in mock files. Adding one to
+ a mock will result in a runtime error.
+- `runtime/eval`: Fixed an issue where import calls from within mocks were
+ failing under certain circumstances.
+- `imports/decimal`: `int` should now correctly provide the truncated integer
+ representation of a decimal number, not the rounded one.
+
+## 0.13.0 (November 15, 2019)
+
+LANGUAGE CHANGES:
+
+- **Policy Parameters**. The new [policy
+ parameters](https://docs.hashicorp.com/sentinel/language/parameters) feature
+ allows authors to use a `param` declaration at the top of a policy to supply
+ values that are expected to come from outside of a policy. See the linked
+ documentation for more details.
+
+FEATURES:
+
+- `imports/http`: The [`http`
+ import](https://docs.hashicorp.com/sentinel/imports/http) is a new addition
+ to the standard library enabling the use of HTTP-accessible data from outside
+ the runtime in policy rules.
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where loading other imports before a mocked
+ import would cause those imports to no longer be visible from within the
+ policy.
+
+## 0.12.0 (October 7, 2019)
+
+LANGUAGE CHANGES:
+
+- **New `case` statement**. This statement is a selection control mechanism to
+ conditionally execute a branch based on expression equality, allowing
+ simplification of complex conditional chains that may otherwise need to be
+ written with `else if`. See the [Case
+ Statements](https://docs.hashicorp.com/sentinel/language/spec#case-statements)
+ section of the Sentinel language specification for more details.
+
+IMPROVEMENTS:
+
+- `lang/semantic`: Added a semantic check to ensure usage of append is not using
+ a return value.
+
+BUG FIXES:
+
+- `runtime/eval`: Corrected an issue where calling a method on an import object
+ value that was the result of a method call on another import object value
+ would have erroneously tried to call an import of the name of the "parent"
+ import object value. Example: in `a = subject.new(); b = a.call(); c =
+ b.call()`, `b.call()` would attempt to call a method named `call` on the root
+ namespace for an import named `a`. This has now been corrected so that
+ `b.call()` will now correctly call the `call` method for the respective object
+ namespace residing in the import named `subject`.
+- `imports`: Some standard imports may have been returning null for some unknown
+ keys in objects when they should have been returning undefined. This was due
+ to an SDK issue which was corrected in SDK version 0.3.2, which has now been
+ corrected.
+- `cmd/test`: `sentinel test` will now correctly fail a policy if it encounters
+ an error.
+- `cmd/test`: `sentinel test` will now correctly display errors and other output
+ that were missing due to a formatting issue.
+- `runtime/eval`: The `append` builtin now correctly returns undefined for all
+ calls, as called for by the Specification. Note that in most cases, the
+ semantic check outlined above will trigger an error if the return value is
+ used.
+
+## 0.11.0 (September 5, 2019)
+
+LANGUAGE CHANGES:
+
+- **New builtin function:** `range()`. This function existed in the spec in
+ earlier versions but was removed as it lacked an implementation. This has now
+ been implemented and re-added to the spec. See the
+ [Range](https://docs.hashicorp.com/sentinel/language/spec#range) section of
+ the Sentinel Specification for more details.
+- Lists are now comparable. Lists are equal if their corresponding elements are
+ comparable and equal.
+- Method calls on values returned by imports are now supported. See the
+ [Imports](https://docs.hashicorp.com/sentinel/language/spec#imports) section
+ of the Sentinel Specification for more details.
+
+FEATURES:
+
+- `imports/decimal`: This is a new import designed to do exact precision
+ mathematical calculations.
+- `runtime/eval`: Compound call expressions that refer to imports (example:
+ foo.bar().baz() when `foo` is a loaded import) will now function as expected.
+ Previously, this was only supported up to the first call (example:
+ `foo.bar()`).
+- `imports/time`: Timespaces returned by calls such as `time.now` are now
+ callable. Example: `t = time.now; t.after(some_previous_time)` will now
+ function.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: The implementation of comparison of non-comparable types has
+ now changed. Rather than triggering a runtime error, non-comparable types will
+ now return false when attempting to compare.
+
+## 0.10.4 (August 15, 2019)
+
+BUG FIXES:
+
+- `runtime/encoding`: Fixed an issue with conversion of null values that could
+ lead to crashes in imports.
+
+## 0.10.3 (July 15, 2019)
+
+BUG FIXES:
+
+- `command/config`: Parsing a configuration file where malformed data is
+ encountered after apparently properly-formed data will now report an error. An
+ example would be a situation where misplaced braces would cause a JSON object
+ to only parse part of the configuration file.
+
+## 0.10.2 (June 25, 2019)
+
+BUG FIXES:
+
+- `lang/parser`: Corrected an issue where parsing certain compound binary
+ expressions where the negation predicate (`not`) was in use would cause the
+ negation to have no effect. Example: `foo else "bar" not in "baz"` would have
+ been parsed and evaluated as `foo else "bar" in "baz"`, effectively producing
+ the opposite result.
+
+## 0.10.1 (May 9, 2019)
+
+BUG FIXES:
+
+- `command/test`: `sentinel test` now displays policies without
+tests as an unknown result with [no test files], instead of the somewhat
+erroneous behavior of displaying it as a PASS. A full test run with a mixture
+of passing tests and no tests still results in an overall successful result.
+
+## 0.10.0 (April 18, 2019)
+
+LANGUAGE CHANGES:
+
+- Mixed-number arithmetic operations are now allowed. Addition (`+`),
+ subtraction (`-`), multiplication (`*`), division (`/`), and remainder
+ operations (`%`) are no longer restricted to number values of the same
+ type, and can mix integer and floating point. The result of these operations
+ is always a floating-point number.
+- Remainder (modulo, `%`) operations are now allowed on floating-point numbers.
+
+
+BUG FIXES:
+
+- `lang/parser`: Fixed a bug that affected the use of `not` with `contains`,
+ `matches`, or `in` in a compound expression.
+- `runtime/eval`: Fixed error messages on evaluation errors with `contains` and
+ `in` to indicate that strings are an allowed type along with lists and maps.
+
+
+## 0.9.2 (March 15, 2019)
+
+This is a dependency update related to the changes mentioned in 0.9.1. No other
+changes have been made.
+
+## 0.9.1 (March 14, 2019)
+
+This is a patch release that is required to integrate with the latest versions
+of the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). No other
+changes have been made.
+
+## 0.9.0 (January 28, 2019)
+
+FEATURES:
+
+- `imports/strings`: Added a `join` function to the `strings` import. This can
+ be used to join a list into a string with a specific separator.
+ Multi-dimensional lists and all Sentinel primitives are supported.
+
+## 0.8.1 (January 17, 2019)
+
+BUG FIXES:
+
+- `lang/eval`: Fixed a bug that prevents the effective use of `return`
+ statements in `for` loops.
+
+## 0.8.0 (January 14, 2019)
+
+FEATURES:
+
+- Mocks can now be represented by Sentinel code. This allows for the mocking of
+ functions and other complex data structures that cannot be represented in
+ JSON. For more information on using this feature via mocks, click
+ [here](https://docs.hashicorp.com/sentinel/commands/config#mocking-with-sentinel-code).
+
+
+IMPROVEMENTS:
+
+- Import validation has now been moved to the semantic checking phase. This
+ should result in better reporting of validation errors. In addition, import
+ validation will now enforce the use of an `as` identifier when an import path
+ is not a valid identifier on its own (example: `import "foo/bar"`).
+
+## 0.7.0 (December 12, 2018)
+
+BUG FIXES:
+
+- There have been changes to the runtime in how scope is handled over multiple
+ policy executions. Scope is now correctly unique per single policy execution,
+ and values set or builtins that are overridden in one policy will no longer
+ affect those values within another.
+
+## 0.6.0 (November 30, 2018)
+
+FEATURES:
+
+- `imports/runtime`: This new import allows for one to check various aspects of
+ the Sentinel runtime as it may be embedded in the simulator or a specific
+ implementation. For now, it allows the version to be checked.
+
+## 0.5.1 (November 28, 2018)
+
+IMPROVEMENTS:
+
+- `imports/time`: Added the `zone` and `zone_string` attributes to assist with
+ validation of a timespace's zone.
+- `command/fmt`: Added a new -check flag. This option does not commit changes,
+ but instead checks to see what files need formatting and outputs them on
+ stdout.
+
+BUG FIXES:
+
+- `command/test`: Ensure that passing test results are correctly output one per
+ line. Tests are also now run in a deterministic fashion based on
+ lexicographical (alphabetical) order.
+- `imports/time`: `month_name` and `weekday_name` will now show up correctly in
+ a returned timespace result.
+
+
+## 0.5.0 (November 5, 2018)
+
+IMPROVEMENTS:
+
+- `spec`: Selectors can now contain any reserved word (example: `rule`) or
+ keyword operator (example: `any`, `all`, `is`, `not`). This only works for the
+ _selector_ part of the expression (after the first period) - the first primary
+ expression (before the first period) still needs to be an identifier that does
+ not conflict with reserved words.
+
+BUG FIXES:
+
+- The simulator should now display import function call names correctly in
+ import errors.
+
+## 0.4.0 (October 1, 2018)
+
+FEATURES:
+
+- `builtin`: Added the `bool` built-in type conversion function. Booleans will
+ also now accepted as conversion into other values as well, with the full list
+ of behaviors available in the spec.
+
+## 0.3.2 (September 27, 2018)
+
+FEATURES:
+
+- `command/apply`: `sentinel apply` now prints out messages output by the
+ `print()` function when a trace is output on policy failure, or when a trace
+ is forced with `-trace`.
+- `imports/time`: Added the `month_name` and `weekday_name` keys to the
+ timespace, which return full-English names for the month and day of the week.
+
+
+BUG FIXES:
+
+- `command/fmt`: `sentinel fmt -` Will no longer print out the filter status
+ message on the output stream when `-write=false` Is not explicitly stated.
+ This brings the behavior of the command in line with the help text.
+- `runtime`: Index operations on the right-hand-side that have negative indexes
+ that go out of range (example: `length(list) * -1 - 1`) now correctly return
+ `undefined`. left-hand-side index assignments with a out-of-range negative
+ index still return runtime errors.
+
+## 0.3.1 (August 3, 2018)
+
+BUG FIXES:
+
+- `runtime`: Basic index assignment has been implemented as per the spec.
+- `runtime`: Index expressions for lists with negative indexes will no longer panic if the
+ list index is less than `length(list) * -1`.
+
+## 0.3.0 (July 20, 2018)
+
+FEATURES:
+
+- **New standard import: `types`.** This can be used to dynamicaly detect the
+ type of some value.
+
+## 0.2.0 (April 11, 2018)
+
+FEATURES:
+
+- **New standard import: `json`.** Marshal and unmarshal JSON documents and
+ access their contents as native Sentinel values.
+- **`break` and `continue`.** These are now both specified and implemented.
+ `break` allows loop exiting and `continue` allows immediate execution of the
+ next iteration.
+
+IMPROVEMENTS:
+
+- runtime: `print()` map values are now ordered alphabetically by keys.
+
+BUG FIXES:
+
+- command/test: If no `test` block exists, test behaves like it is asserting
+ `main: true`.
+- runtime: default maximum stack depth to 500
+- runtime: `print()` map values now appear like more typical maps.
+- runtime: division by zero is an error, not a crash
+- runtime: plugins that send map values with `null` values now decode properly
+ into native Sentinel values.
+
+## 0.1.0 (September 19, 2017)
+
+Initial release.
+
+
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/commands/apply.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/commands/apply.mdx
new file mode 100644
index 0000000000..c5e81d6d65
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/commands/apply.mdx
@@ -0,0 +1,63 @@
+---
+page_title: 'Command: apply'
+sidebar_current: docs-commands-apply
+description: >-
+ The `sentinel apply` command is used to execute a policy locally for
+ development purposes.
+layout: docs
+---
+
+# Command: `apply`
+
+The `sentinel apply` command is used to execute a policy locally for development
+purposes.
+
+## Usage
+
+Usage: `sentinel apply [options] POLICY`
+
+This command executes the policy file at the path specified by POLICY. In
+addition to a path to a policy, POLICY can reference a label of a
+[policy](/sentinel/configuration#policies) entry in the configuration.
+
+Use the exit code of this command to determine the exact status of the policy
+evaluation. `0` is pass, `1` is fail, `2` is undefined (fail, but because the
+result was undefined), and `3` is a runtime error. Errors unrelated to the
+policy status itself are returned with an exit status of `9`.
+
+To control the behavior of the `apply` command, create a [configuration
+file](/sentinel/configuration). With this, you can define available
+[import plugins](/sentinel/concepts/imports), mock data, and global values.
+This can help you simulate a policy embedded within an application.
+
+As of the 0.16 release, POLICY is optional. When it is not supplied,
+`sentinel apply` will run **all** policies in the supplied configuration.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-config=path` - Path to a configuration file specifying available imports,
+ mock data, globals, etc. The default is `sentinel.[hcl|json]`.
+
+- `-json` Enable JSON output. Using this mode, all other output will be
+ suppressed and the full detail of the apply will be output in a
+ machine-readable format. Enabling this implies `-trace`. See the section on
+ [tracing JSON output](/sentinel/writing/tracing#json-output) for more details.
+
+- `-json-rule=RULE` Enable JSON output, outputting only the value of the
+ specified rule. When running against a policy set, the policy must be supplied
+ to enable per-rule filtering. Implies `-json`.
+
+- `-global key=value` - Set global values. This is the same as setting `global`
+ in the configuration file, and will override any of these respective values
+ set in the configuration. The value is either a string, or a JSON number,
+ array, or object. To force strings, use quotes.
+
+- `-param key=value` - Set parameters, the same as setting `param` in the
+ configuration file. Values are handled in the same way they are with the
+ `-global` flag.
+
+- `-trace` - Always show the execution trace. This shows intermediate
+ boolean expression values. This always shows for failed policies.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/commands/fmt.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/commands/fmt.mdx
new file mode 100644
index 0000000000..8297316d8b
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/commands/fmt.mdx
@@ -0,0 +1,33 @@
+---
+page_title: 'Command: fmt'
+sidebar_current: docs-commands-fmt
+description: The `sentinel fmt` command formats a policy source to a canonical format.
+layout: docs
+---
+
+# Command: `fmt`
+
+The `sentinel fmt` command formats a policy source to a canonical format.
+
+## Usage
+
+Usage: `sentinel fmt [options] FILE ...`
+
+This command formats all the specified policy files to a canonical format.
+
+By default, policy files are overwritten in place. This behavior can be
+changed with the `-write` flag. If a specified FILE is `-` then stdin is
+read and the output is always written to stdout.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-write=true` - Write formatted policy to the named source file. If false,
+ output will go to stdout. If multiple files are specified, the output will
+ be concatenated directly.
+
+- `-check=false` - Don't format, only check if formatting is necessary. Files
+ that require formatting are printed, and a non-zero exit code is returned if
+ changes are required.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/commands/index.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/commands/index.mdx
new file mode 100644
index 0000000000..7177093cec
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/commands/index.mdx
@@ -0,0 +1,23 @@
+---
+page_title: Commands (CLI)
+sidebar_current: docs-commands
+description: >-
+ Sentinel provides a `sentinel` command-line interface (CLI) for developing and
+ testing policies.
+layout: docs
+---
+
+# Sentinel CLI Commands
+
+The Sentinel command-line interface (CLI) allows for the developing and testing
+of policies outside of a particular Sentinel implementation. Having a standard
+workflow to develop policies is critical for our mission of [policy as
+code](/sentinel/concepts/policy-as-code).
+
+The CLI takes a subcommand to execute. The complete list of subcommands is in
+the navigation to the left.
+
+The Sentinel CLI is a well-behaved command line application. In erroneous cases,
+a non-zero exit status will be returned. It also responds to -h and --help as
+you'd expect. To view a list of the available commands at any time, just run
+`sentinel` with no arguments.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/commands/test.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/commands/test.mdx
new file mode 100644
index 0000000000..bc785c3725
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/commands/test.mdx
@@ -0,0 +1,52 @@
+---
+page_title: 'Command: test'
+sidebar_current: docs-commands-test
+description: The `sentinel test` command runs policies against the Sentinel test framework.
+layout: docs
+---
+
+# Command: `test`
+
+The `sentinel test` command runs policies against the Sentinel test framework.
+
+To learn more about testing, see the [testing
+policies](/sentinel/writing/testing) page.
+
+## Usage
+
+Usage: `sentinel test [options] [PATH] ...`
+
+This command runs the defined test cases against a set of policies.
+
+The test cases are expected to live at the path `test//*.[hcl|json]` relative
+to the policy being tested. `` should be the name of the policy
+excluding the file extension. The files should be in the [configuration
+file structure](/sentinel/configuration). The `test` key is used for
+assertions.
+
+The PATH arguments specified may be a list of zero or more files or directories.
+When no arguments are given, it defaults to the current directory. When a
+directory is given, all policies ending with the extension `.sentinel` are
+tested.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-json` - Suppress normal output and output test results as a JSON document.
+ See the [testing JSON output](/sentinel/writing/testing#test-json-output)
+ section for more details.
+
+- `-run=regexp` - Run only tests matching the regular expression pattern.
+ A `/` splits policy name and test case name.
+
+- `-verbose` - Show tracing and log output for tests that succeed in addition
+ to tests that fail. By default, traces and logs are only shown for tests that
+ fail.
+
+- `-maxConcurrency` - Allows users to specify the number of tests to be run concurrently.
+ Defaults to the number of logical CPUs if not provided. To run tests sequentially, use `-maxConcurrency=1`
+
+- `-timeout` - Allows users to specify a timeout after which the test command will stop running.
+Defaults to 5 minutes if not provided. The timeout needs to be specified as a Duration type, eg `100ms, 5s etc`
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/concepts/enforcement-levels.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/concepts/enforcement-levels.mdx
new file mode 100644
index 0000000000..fc4b8062d0
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/concepts/enforcement-levels.mdx
@@ -0,0 +1,43 @@
+---
+page_title: Enforcement Levels
+sidebar_current: docs-concepts-levels
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Enforcement Levels
+
+Enforcement levels are a first class concept in Sentinel allowing pass/fail
+behavior to be associated separately from the policy logic. This enables
+any policy to be a warning, allow overrides, or be absolutely mandatory.
+Because this level is not part of the policy body itself, different uses of
+the same policy can have different enforcement levels.
+
+Sentinel has three enforcement levels:
+
+- **Advisory:** The policy is allowed to fail. However, a warning should be
+ shown to the user or logged. Advisory is the default enforcement level.
+
+- **Soft Mandatory:** The policy must pass unless an override is specified.
+ The semantics of "override" are specific to each Sentinel-enabled application.
+ The purpose of this level is to provide a level of privilege separation
+ for a behavior. Additionally, the override provides non-repudiation since
+ at least the primary actor was explicitly overriding a failed policy.
+
+- **Hard Mandatory:** The policy must pass no matter what. The only way to
+ override a hard mandatory policy is to explicitly remove the policy.
+ It should be used in situations where an override is not possible.
+
+## Configuring Enforcement Levels
+
+Enforcement levels are configured when a policy is deployed to a
+Sentinel-enabled application. The exact mechanism that the level is specified
+is determined by each application. Please reference the documentation for
+your Sentinel-enabled application for more information.
+
+Enforcement levels are not configured and are not known by the policy body
+itself. All policies should be written to describe exactly the behavior
+they're attempting to control. For example, a policy that restricts deploys
+to business hours should be written exactly like so. When that policy is
+configured on an application, the operator may specify that it is advisory,
+soft, or hard mandatory.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/concepts/imports.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/concepts/imports.mdx
new file mode 100644
index 0000000000..1306e1512e
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/concepts/imports.mdx
@@ -0,0 +1,32 @@
+---
+page_title: Imports
+sidebar_current: docs-concepts-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom imports](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only
+local context for making policy decisions.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that the
+import calendar allows access to engineer calendars. This import only exists
+for the purpose of this example and at the time of writing is not a real
+available import.
+
+```json sentinel
+{
+ "policy": "import \"calendar\"\n// Get the calendar for Bob for today\nbob_calendar = calendar.of(\"bob\").today\n\n// Allow this policy to pass if Bob is not on vacation.\nmain = rule { not bob_calendar.has_event(\"vacation\") }",
+ "mocks": {
+ "calendar": "has_event = func(event) {\n\treturn true\n}\nof = func(name) {\n\treturn { \"today\": { \"has_event\": has_event } }\n}"
+ }
+}
+```
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/concepts/index.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/concepts/index.mdx
new file mode 100644
index 0000000000..ce7ac51454
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/concepts/index.mdx
@@ -0,0 +1,15 @@
+---
+page_title: Basic Concepts
+sidebar_current: docs-concepts
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Basic Concepts
+
+This section covers some high level basic concepts that are important
+to understand for day to day Sentinel usage. Every page in
+this section is recommended reading for anyone consuming
+or extending Sentinel.
+
+Please use the navigation to the left to learn more about a topic.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/concepts/language.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/concepts/language.mdx
new file mode 100644
index 0000000000..ce91f3eb8f
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/concepts/language.mdx
@@ -0,0 +1,91 @@
+---
+page_title: Policy Language
+sidebar_current: docs-concepts-language
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Policy Language
+
+Sentinel defines and uses its own [policy language](/sentinel/language).
+
+The language was designed to be approachable by non-programmers, since
+there are many use cases where the individual defining policy may not
+be a developer. However, the language includes constructs that
+are familiar to developers to enable powerful policies.
+
+To learn more about the language, please see the
+[writing policy section](/sentinel/writing)
+or the [language reference](/sentinel/language).
+
+An example of the policy language is shown below:
+
+```sentinel playground
+import "time"
+
+# Validate time is between 8 AM and 4 PM
+valid_time = rule { time.now.hour >= 8 and time.now.hour < 16 }
+
+# Validate day is M - Th
+valid_day = rule {
+ time.now.weekday_name in ["Monday", "Tuesday", "Wednesday", "Thursday"]
+}
+
+main = rule { valid_time and valid_day }
+```
+
+## Why?
+
+To explain why Sentinel defines and uses its own language, the question
+can be more easily split into roughly two types of languages: _configuration_ languages
+and _programming_ languages.
+
+### Configuration Languages
+
+Configuration languages are formats such as JSON, YAML, XML, etc. Many applications
+use configuration languages as their ACL format. Configuration languages
+are good for static, declarative information. ACL systems are a good fit
+for this. ACL systems are focused, providing the limiting options necessary
+to restrict access to a system.
+
+Configuration languages are not good for dynamic or logical rules. They
+typically only contain limited conditions, loops, or functions. Further
+configuration is often declarative, which can introduce complexities to
+logical statements that depend on ordering.
+
+The use cases that Sentinel was built for require the ability to perform
+complex behavior that didn't model well into a configuration format or a
+declarative form.
+
+### Programming Languages
+
+The Sentinel language was designed with the following goals:
+
+- **Non-programmer friendly.** Sentinel was built to be used by non-programmers.
+ For the use cases we discovered, non-programmers needed the ability to
+ enforce certain rules within a system. For example, a person responsible for
+ compliance may need to insert rules into a system.
+
+- **Programmer friendly.** At the same time, the language needed to support
+ programmer-friendly constructs such as conditionals, loops, and functions
+ for complex policies that a programmer may be writing.
+
+- **Embeddable.** Sentinel is embedded in existing software. The language
+ itself needs to be easily embeddedable. Further, Sentinel was designed
+ to be embedded in [HashiCorp](https://www.hashicorp.com) software
+ written in Go, so it needed to be easily embeddable in Go.
+
+- **Safe.** The language is used in highly security-sensitive environments.
+ It must not be able to crash its host system or access system resources
+ without explicit approval.
+
+Prior to building our own language, Sentinel evaluated other languages.
+Some languages worked quite well. As we continued to develop Sentinel, we
+found that the flexibility and control over designing our own language
+outweighed the costs.
+
+Because the language itself doesn't need to be general purpose, building
+a language focused on policy proved to be the best route for Sentinel.
+It allows us to build powerful tracing capabilities for debugging, make
+interesting performance choices, and extend the language as needed in the
+future.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/concepts/policy-as-code.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/concepts/policy-as-code.mdx
new file mode 100644
index 0000000000..6dac593e18
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/concepts/policy-as-code.mdx
@@ -0,0 +1,72 @@
+---
+page_title: Policy as Code
+sidebar_current: docs-concepts-pac
+description: >-
+ Policy as code is the idea of writing code in a high-level language to manage
+ and automate policies. By representing policies as code in text files, proven
+ software development best practices can be adopted such as version control,
+ automated testing, and automated deployment.
+layout: docs
+---
+
+# Policy as Code
+
+Policy as code is the idea of writing code in a high-level language to
+manage and automate policies. By representing policies as code in text files,
+proven software development best practices can be adopted such as version
+control, automated testing, and automated deployment.
+
+Many existing policy or ACL systems do not practice policy as code. Many
+policies are set by clicking in a GUI, which isn't easily repeatable nor
+versionable. They usually don't provide any system for testing policies
+other than testing an action that would violate the policy. This makes it
+difficult for automated testing. And the policy language itself varies by
+product.
+
+Sentinel is built around the idea and provides all the benefits of policy as code.
+
+## Benefits
+
+Policy as code provides a number of benefits:
+
+- **Sandboxing.** Policies provide the guardrails for other automated systems.
+ As the number of automated systems grow, there is also a growing need to
+ protect those automated systems from performing dangerous actions. Manual
+ verification is too slow; policies need to be represented as code to
+ keep up with other automated systems.
+
+- **Codification.** By representing policy logic as code, the information
+ and logic about a policy is directly represented in code and can be augmented
+ with comments rather than relying on oral tradition to learn about the
+ reason for policies.
+
+- **Version Control.** Policies are encouraged to be stored as simple text
+ files managed by a version control system. This lets you gain all the
+ benefits of a modern VCS such as history, diffs, pull requests, and more.
+
+- **Testing.** Policies are just code. Their syntax and behavior can be
+ [easily validated with Sentinel](/sentinel/commands/test). This also encourages
+ automated testing such as through a CI. Paired with a VCS system, this
+ allows a pull request workflow to verify that a policy keeps the system
+ behavior as expected before merging.
+
+- **Automation.** With all policies as code in simple text files, various
+ automation tools can be used. For example, it is trivial to create tools
+ to automatically deploy the policies into a system.
+
+## Sentinel and Policy as Code
+
+Sentinel fully embraces policy as code in a number of ways:
+
+- **Language.** All Sentinel policies are written using the
+ [Sentinel language](/sentinel/concepts/language). This language is
+ made to be inputted directly to text files. As an additional benefit,
+ all Sentinel-enabled applications share the same policy language.
+
+- **Development.** Sentinel provides a [CLI](/sentinel/commands)
+ for development and testing. This local CLI can be used to verify policies
+ before deploying them to a system.
+
+- **Testing.** Sentinel provides a [test framework](/sentinel/commands/test)
+ designed specifically for automation. This allows developers and CI systems
+ to further verify policies.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/configuration/index.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/configuration/index.mdx
new file mode 100644
index 0000000000..248945b3b7
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/configuration/index.mdx
@@ -0,0 +1,406 @@
+---
+page_title: Sentinel CLI Configuration File Syntax
+sidebar_current: docs-configuration
+description: >-
+ The Sentinel CLI's configuration file can be used to control the
+ behavior of the simulator during apply and test operations.
+layout: docs
+---
+
+# Sentinel CLI Configuration File Syntax
+
+The Sentinel CLI's configuration file can be used to control the behavior
+of the simulator during [`apply`](/sentinel/commands/apply) and
+[`test`](/sentinel/commands/test) operations.
+
+## Usage
+
+The configuration file is used in different ways depending on what operation you
+are trying to execute.
+
+### Apply
+
+When using `sentinel apply`, configuration files that have the `.hcl`
+extension are loaded into a single configuration. This allows for configuration
+to be split across multiple files, which is useful for large policy sets. To
+supply a single configuration file, the `-config=FILE` flag can be used, where
+`FILE` is the path to the configuration file. This argument also allows for a
+`.json` configuration file to be supplied. The default is `sentinel.[hcl|json]`.
+
+See the [apply command](/sentinel/commands/apply) reference for more details.
+
+### Test
+
+When using `sentinel test`, each file matching `test//*.[hcl|json]` is a
+configuration file representing a single test case, where `` is the name
+of the policy being tested. Configure assertions for each [test
+case](#test-cases) using the test section.
+
+See the [test command](/sentinel/commands/test) reference for more details.
+
+#### Remote sources
+
+When declaring a `policy` or `module` block within the configuration, a
+`source` attribute must be supplied. This `source` should declare the location
+of the resource, which can be either a local file or a URL pointing to a remote
+file. Examples of local `source` attributes are detailed both in the
+[Policies](#policies) and [Modules](#modules) sections of this page. Below
+are a few examples of remote `source` attributes.
+
+Example:
+
+```hcl
+policy "foo" {
+ source = "git::https://github.com/hashicorp/sentinel-example.git//main.sentinel"
+ enforcement_level = "hard-mandatory"
+}
+```
+
+The above example will fetch the policy from the provided URL and place it into
+the cache ready for use. Be sure to read the
+[Remote Sources](/sentinel/configuration/remote-sources) page for an
+in-depth overview.
+
+## Configuration File Reference
+
+The format of the configuration file is either JSON or HCL. The available
+blocks are:
+
+- [`mock`](#mock-imports) - A mock import specification.
+- [`policy`](#policies) - Configuration for a [policy](/sentinel/writing).
+- [`import`](#imports) - Configuration for a [module](/sentinel/extending/modules), [standard import](/sentinel/imports), [plugin](/sentinel/extending/plugins) or
+ [static import](/sentinel/extending/static-imports).
+- [`global`](#globals) - Data that is inserted into the global scope.
+- [`param`](#parameters) - Values for [parameters](/sentinel/language/parameters) defined in the policy.
+- [`test`](#test-cases) - Test cases for the [test command](/sentinel/commands/test).
+
+### Mock Imports
+
+Mock imports allow running a policy with an
+[import](/sentinel/concepts/imports) that you may not have access to or is
+too difficult to run. For example, it can be easier to mock data for [Terraform
+Enterprise](https://www.terraform.io/docs/enterprise/sentinel/index.html)
+locally instead of having to run a workspace through a plan/policy check cycle.
+
+Mock imports are specified using the `mock` block, labelled by the import
+name that you want to mock. For example, if you wanted to mock the `time`
+import, you could create an entry with the `time` label pointing to the data
+you want to mock.
+
+The data can take one of two forms:
+
+#### Mocking static data
+
+Static data can be mocked directly via HCl using the `data` attribute on the
+`mock` block.
+
+Example:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ hour = 9
+ minute = 42
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"time\"\n\nmain = rule { time.now.hour is 9 }",
+ "mocks": {
+ "time": "now = { \"hour\": 9, \"minute\": 42 }"
+ }
+}
+```
+
+#### Mocking with Sentinel code
+
+There are some Sentinel types that raw data cannot mock, such as functions,
+and maps that don't have string keys. To mock this data, you can use
+a Sentinel file itself. In this case, you can set the `module` attribute to
+a file with the Sentinel code in it, relative to the current working directory in
+the event of `apply`, or relative to the configuration file location in the
+event of `test`.
+
+Note that `main` is not required in a mock data file.
+
+Example:
+
+```hcl
+mock "foo" {
+ module {
+ source = "mock-foo.sentinel"
+ }
+}
+```
+
+`mock-foo.sentinel` would contain:
+
+```sentinel
+bar = func() {
+ return "baz"
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"foo\"\n\nmain = rule { foo.bar() is \"baz\" }",
+ "mocks": {
+ "foo": "bar = func() { return \"baz\" }"
+ }
+}
+```
+
+### Policies
+
+The `policy` block allows for the the configuration of policies to assist
+with integrating into other commands.
+
+Each `policy` block has a label to identify the policy, with the block
+providing configuration of the policy. The available configuration options for
+policy are `source`, `enforcement_level` and an optional `params` attribute. The
+`source` key provides the location of the policy, while `enforcement_level` is
+currently used by integrations such as [Terraform Cloud](https://www.terraform.io/docs/cloud/sentinel/manage-policies.html#enforcement-levels).
+For more information on the `source` value, see [Policy and Module Sources](#policy-and-module-sources).
+
+The optional `params` attribute is used to provide values to [parameters](/sentinel/language/parameters)
+defined within the policy file.
+
+```hcl
+policy "foo" {
+ source = "foo.sentinel"
+ enforcement_level = "hard-mandatory"
+ params = {
+ "name" = "Sample"
+ }
+}
+```
+
+If `enforcement_level` is not supplied, it will fallback to the `advisory` level.
+
+### Imports
+
+Import configuration allows you to configure [modules](/sentinel/extending/modules),
+[standard imports](/sentinel/imports) and [import plugins](/sentinel/extending/plugins).
+
+The `import` is a multi-label block, with the first label determining the kind of import
+being configured. Currently the supported values for this label are:
+
+* [`"module"`](#import-modules) for configuring import modules
+* [`"plugin"`](#import-plugins) for configuring standard imports and import plugins
+* [`"static"`](#static-imports) for configuring static data files as imports
+
+#### Import Modules
+
+The `import "module"` block allows you to map a Sentinel import to a
+[module](/sentinel/extending/modules). The Sentinel CLI will parse the module
+source and make it available for evaluation with the policy.
+
+Each block has a label aligning to the name of the import, with
+the block set to the configuration object for the module. Currently, the only
+value within the configuration object is `source`, which points to the
+location of the module. For more information on the `source` value, see
+[Policy and Module Sources](#policy-and-module-sources).
+
+```hcl
+import "module" "foo" {
+ source = "modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "modules/bar.sentinel"
+}
+```
+
+Note when using [`sentinel test`](/sentinel/commands/test), module paths must be
+specified relative to the location of the configuration file.
+
+```hcl
+import "module" "foo" {
+ source = "../../modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "../../modules/bar/sentinel"
+}
+```
+
+See [Writing and Using a
+Module](/sentinel/extending/modules#writing-and-using-a-module) for more details
+on using a module with this configuration.
+
+### Import Plugins
+
+Import `"plugin"` configuration allows you to configure both [standard imports](/sentinel/imports)
+as well as [import plugins](/sentinel/extending/plugins) that can be used within
+a policy. If defining a custom import plugin, the Sentinel CLI will launch the
+plugin, connect to it, configure it, and execute it as needed by the policy.
+
+The available configuration attributes for an `import "plugin"` are:
+
+- `source` (string, required) - Path to the import plugin executable.
+- `args` (list of string, optional) - A list of arguments to pass to the
+ executable when starting it.
+- `env` (map of string to string, optional) - A set of environmental variables
+ to set when launching the plugin.
+- `config` (map, optional) - Configuration for the plugin itself. This is
+ specific to each individual plugin. Please reference the documentation for
+ the plugin for more information.
+
+Standard import example:
+
+```hcl
+import "plugin" "time" {
+ config = { "timezone": "Australia/Brisbane" }
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"time\"\n\nmain = time.now.zone_string is \"+10:00\"",
+ "mocks": {
+ "time": "now = { \"zone_string\": \"+10:00\" }"
+ }
+}
+```
+
+Custom plugin example:
+
+```hcl
+# flipper receives a boolean and returns the opposite
+import "plugin" "flipper" {
+ source = "/path/to/flipper"
+}
+```
+
+```json sentinel
+{
+ "policy": "import \"flipper\"\n\nmain = flipper.flip(false)",
+ "mocks": {
+ "flipper": "flip = func(input) { return not input }"
+ }
+}
+```
+
+#### Static Imports
+
+Static import configuration allows you to supply a data file and make
+it available to a policy via an import statement.
+
+The available configuration attributes for an `import "static"` are:
+
+- `source` (string, required) - Path to the static data file.
+- `format` (string, required) - The format of the static data. Currently, only
+ the "json" value is supported.
+
+Static import example:
+
+```hcl
+import "static" "people" {
+ source = "./data/people.json"
+ format = "json"
+}
+```
+
+```json sentinel
+{
+ "policy": "import \"people\"\n\nmain = length(people.names) > 0",
+ "mocks": {
+ "people": "names = [\"John Smith\", \"Jane Smith\"]"
+ }
+}
+```
+
+### Globals
+
+Global data is injected directly into the global scope of the policy.
+This can be used to simulate global data that may be injected by a
+Sentinel-enabled application.
+
+Global data is specified by the `global` key. The value is a map of
+variables to inject directly into the running policy.
+
+Example:
+
+```hcl
+global "time" {
+ value = {
+ now = {
+ day = 31
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass. Notice
+that we don't have to do any `import`. The values of `global` are
+injected directly into the global scope.
+
+```json sentinel
+{
+ "policy": "main = time.now.day == 31",
+ "globals": {
+ "time": {
+ "now": {
+ "day": 31
+ }
+ }
+ }
+}
+```
+
+### Parameters
+
+The `param` section allows you to supply values for the
+[parameters](/sentinel/language/parameters) found in a policy. Values
+entered here will satisfy required parameters in a policy, in addition to
+override any defaults. Note that any of the manual parameter value methods
+supported by `sentinel apply` will override the values set here.
+
+```hcl
+param "foo" {
+ value = "bar"
+}
+```
+
+### Test Cases
+
+Test cases specify the test cases for the [test command](/sentinel/commands/test).
+
+Tests are specified by the `test` key. The value of this is a map of
+string to boolean. The key is the name of a rule and the value is the
+expected value of that rule. If a rule is not specified, than any value is
+allowed.
+
+Example:
+
+```hcl
+test {
+ rules = {
+ main = true
+ days = []
+ }
+}
+```
+
+For the policy:
+
+```json sentinel
+{
+ "policy": "valid_days = rule {\n\tfilter days as d {\n\td is \"monday\"\n\t}\n}\n\nmain = rule { valid_days is empty }",
+ "globals": {
+ "days": []
+ }
+}
+```
+
+For more information, read about the [test command](/sentinel/commands/test).
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/configuration/overrides.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/configuration/overrides.mdx
new file mode 100644
index 0000000000..ca1a6842b1
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/configuration/overrides.mdx
@@ -0,0 +1,80 @@
+---
+page_title: Override Files
+sidebar_current: docs-configuration-overrides
+description: >-
+ The Sentinel CLI's configuration can be overriden using override files.
+ Override files merge additional settings into existing configuration.
+layout: docs
+---
+
+# Override Files
+
+As outlined in the configuration [overview](/sentinel/configuration#Apply),
+the normal behavior of Sentinel is to load all `.hcl` files into a single
+configuration object.
+
+In addition to appending multiple configuration files, Sentinel allows for the
+rare case of overriding configuration through override files. Override files
+are any files that match either `_override.{hcl,json}` or `override.{hcl,json}`.
+
+Sentinel will parse override files after it has loaded the primary
+configuration, and then process each override file in turn (in lexicographical
+order). All top level blocks other than [test](/sentinel/configuration#test-cases)
+require the block to already be defined in the primary configuration. It will
+then attempt to merge the override block into the existing configuration.
+
+## Example
+
+If you had a Sentinel configuration `sentinel.hcl` that contained the following:
+
+```hcl
+policy "main" {
+ source = "./main.sentinel"
+ enforcement_level = "advisory"
+}
+```
+
+And you created a file `override.hcl` that contained the following:
+
+```hcl
+policy "main" {
+ enforcement_level = "soft-mandatory"
+}
+```
+
+Sentinel will merge the contents of the override into the former, providing the
+following configuration:
+
+```hcl
+policy "main" {
+ source = "./main.sentinel"
+ enforcement_level = "soft-mandatory"
+}
+```
+
+## Merging Behavior
+
+The general rule, which applies in most cases, is:
+
+- A top-level block in an override file merges with a block in a normal
+ configuration file that has the same block header. The block header is the
+ block type and any quoted labels that follow it.
+- Within a top-level block, an attribute argument within an override block
+ replaces any argument of the same name in the original block.
+- Within a top-level block, any nested blocks within an override block replace
+ all blocks of the same type in the original block. Any block types that do not
+ appear in the override block remain from the original block.
+- The contents of nested configuration blocks are not merged.
+- The resulting merged block must still comply with any validation rules that
+ apply to the given block type.
+
+If more than one override file defines the same top-level block, the overriding
+effect is compounded, with later blocks taking precedence over earlier blocks.
+Overrides are processed in order first by filename (in lexicographical order)
+and then by position in each file.
+
+## Required Attributes
+
+It is important to note that all attributes inside an override file are
+considered to be optional, meaning that an override file can itself fail
+validation rules for block types.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/configuration/remote-sources.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/configuration/remote-sources.mdx
new file mode 100644
index 0000000000..b64e4d3abc
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/configuration/remote-sources.mdx
@@ -0,0 +1,165 @@
+---
+page_title: Remote Sources
+sidebar_current: docs-configuration-remote-sources
+description: >-
+ There are a number of options for formatting the URL provided to the
+ source attribute on policy and module blocks.
+layout: docs
+---
+
+# Remote Sources
+
+There are a number of options for formatting the URL provided to the
+`source` attribute on `policy` and `import "module"` blocks. This flexibility
+should solve most use cases and allow for reusable policies and modules.
+
+### Supported Protocols
+
+-> **NOTE:** All protocols are supported on the CLI, however each production
+Sentinel integration may not. Be sure to read the appropriate integration
+documentation to check the supported protocols.
+
+- Local filesystem
+- Git
+- Mercurial
+- HTTP
+- Amazon S3
+- Google GCP
+
+### Forced Protocols
+
+Due to the ambiguous nature of URLs, it is possible to force a particular
+protocol to be used during the fetch. To achieve this, simply prefix the url
+with the appropriate protocol as listed below:
+
+- `file` - Local filesystem
+- `git` - Git
+- `hg` - Mercurial
+- `http` or `https` - HTTP
+- `s3` - Amazon S3
+- `gcp` - Google GCP
+
+Example:
+
+```
+git::http://github.com/hashicorp/sentinel.git
+```
+
+The above would download the provided HTTP URL using the Git protocol.
+
+### Protocol Options
+
+In addition to some global options, there are options available to specific
+protocols to perform features such as authentication.
+
+#### Subdirectories / File Pathing
+
+When supplying URLs that point to a directory (eg. `git`), a subdirectory and
+file can be provided by suffixing the URL with `//` and the path. For example,
+if we have a git repository that has a policy, `main.sentinel` in the root, we
+could directly fetch the file by using the following:
+
+```
+git::https://github.com/hashicorp/sentinel-docs-example.git//main.sentinel
+```
+
+Or, if the file was nested in the `policies` folder:
+
+```
+git::https://github.com/hashicorp/sentinel-docs-example.git//policies/main.sentinel
+```
+
+#### Checksumming
+
+For downloads of any protocol, you can automatically verify a checksum. To
+checksum a file, append a `checksum` query parameter to the URL. The parameter
+value can be in the format of type:value or just value, where type is "md5",
+"sha1", "sha256", "sha512" or "file" . The "value" should be the actual
+checksum value or download URL for "file". When type part is omitted, type will
+be guessed based on the length of the checksum string.
+
+```
+./foo.sentinel?checksum=md5:b7d96c89d09d9e204f5fedc4d5d55b21
+
+./foo.sentinel?checksum=b7d96c89d09d9e204f5fedc4d5d55b21
+
+./foo.sentinel?checksum=file:./foo.txt.sha256sum
+```
+
+#### Unarchiving
+
+An `archive` query parameter can be supplied to explicitly state what format
+to attempt to extract, if required. The supported formats are:
+
+- `tar.gz` and `tgz`
+- `tar.bz2` and `tbz2`
+- `tar.xz` and `txz`
+- `zip`
+- `gz`
+- `bz2`
+- `xz`
+
+If a URL has an extension that matches one of the supported formats, it will
+use that format to unarchive.
+
+```
+./file.zip
+```
+
+The above would use the `zip` format to unarchive.
+
+```
+./path/to/file?archive=zip
+```
+
+Would force the `zip` format. If for some reason you required archiving to be
+disabled, this can also be achieved by using the `false` value.
+
+```
+./path/to/file?archive=false
+```
+
+#### Git (`git`)
+
+- `ref` - The Git ref to checkout. This is a ref, so it can point to a commit
+ SHA, a branch name, etc. If it is a named ref such as a branch name, it will
+ be updated to the latest on each get.
+- `sshkey` - An SSH private key to use during clones. The provided key must be
+ a base64-encoded string. For example, to generate a suitable sshkey from a
+ private key file on disk, you would run base64 -w0 \.
+ **Note:** Git 2.3+ is required to use this feature.
+- `depth` - The Git clone depth. The provided number specifies the last `n`
+ revisions to clone from the repository.
+
+The git getter accepts both URL-style SSH addresses like
+`git::ssh://git@example.com/foo/bar`, and "scp-style" addresses like
+`git::git@example.com/foo/bar`. In the latter case, omitting the `git::` forced
+prefix is allowed if the username prefix is exactly git@.
+
+The "scp-style" addresses cannot be used in conjunction with the `ssh://`
+scheme prefix, because in that case the colon is used to mark an optional port
+number to connect on, rather than to delimit the path from the host.
+
+#### Mercurial (`hg`)
+
+- `rev` - The Mercurial revision to checkout.
+
+#### HTTP (`http` or `https`)
+
+Basic Authentication
+
+To use HTTP basic authentication, simply prepend `username:password@` to the
+hostname in the URL such as `https://Aladdin:OpenSesame@www.example.com/index.html`.
+All special characters, including the username and password, must be URL
+encoded.
+
+#### S3 (`s3`)
+
+The following query parameters are available and will take priority when
+authenticating against an S3 bucket.
+
+- `aws_access_key_id` - AWS access key.
+- `aws_access_key_secret` - AWS access key secret.
+- `aws_access_token` - AWS access token if this is being used.
+- `aws_profile` - Use this profile from local ~/.aws/ config. Takes priority
+ over the other three.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/consul.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/consul.mdx
new file mode 100644
index 0000000000..7a3aaaa697
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/consul.mdx
@@ -0,0 +1,47 @@
+---
+page_title: Consul and Sentinel
+sidebar_current: docs-app-consul
+description: >-
+ Consul Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies are applied during writes to
+ the KV Store.
+layout: docs
+---
+
+# Consul
+
+[Consul Enterprise](https://www.hashicorp.com/products/consul/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies are applied during writes to the KV Store.
+
+Sentinel policies have access to the key/value being written. They can be used to
+allow or deny the modification. The information that Sentinel policies have access
+to will expand over time.
+
+The Consul integration with Sentinel is documented in depth in the
+[Consul Enterprise documentation](https://www.consul.io/docs/guides/sentinel.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Input validation depending on the name of the key.**
+
+```sentinel
+main = rule { valid_key() }
+
+required = [
+ ["port", "\\d+"], # ports must be integers
+ ["name", "\\w+"], # name must be a word
+]
+
+valid_key = func() {
+ for required as v {
+ if key is v[0] {
+ return value matches v[1]
+ }
+ }
+
+ return false
+}
+```
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/extending/index.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/extending/index.mdx
new file mode 100644
index 0000000000..a39cb0384d
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/extending/index.mdx
@@ -0,0 +1,52 @@
+---
+page_title: Extending Sentinel
+sidebar_current: docs-extending
+description: Learn how to create your own Sentinel imports to extend Sentinel's functionality.
+layout: docs
+---
+
+# Extending Sentinel
+
+-> **NOTE:** Sentinel's extensibility is currently undergoing large
+improvements - watch this space for future updates!
+
+Sentinel can be extended by writing additional
+[imports](/sentinel/language/imports). These imports can bring new functionality
+to Sentinel by allowing access to new data or by the addition of new functions.
+This section is designed to show you how to accomplish this extensibility and
+describe how it works.
+
+Currently, the only usable way to add additional imports is via
+[modules](#modules). While [plugins](#plugins) currently work under the Sentinel
+CLI, no Sentinel integration currently supports their use.
+
+-> You may also be interested in advanced details on [import
+internals](/sentinel/extending/internals).
+
+## Modules
+
+Modules allow you to re-use Sentinel code as an import. Modules are loaded
+external of policies and mapped to imports. This is usually configured via a
+file such as the [CLI configuration file](/sentinel/configuration).
+
+See the [modules](/sentinel/extending/modules) section for more details.
+
+## Plugins
+
+-> **NOTE:** Plugins are currently only supported on the CLI and not in any
+production Sentinel integration, with the exception of existing configuration
+in [Nomad](https://www.nomadproject.io/docs/configuration/sentinel).
+
+Imports can also be implemented as plugins when Sentinel code itself is too
+restrictive to represent the import, or if you need access to features not
+normally available to the Sentinel runtime.
+
+See the [plugins](/sentinel/extending/plugins) section for more details.
+
+## Static Imports
+
+Static imports provide support for loading data files into the Sentinel runtime,
+making them available as an import.
+
+See the [static imports](/sentinel/extending/static-imports) section for more
+details.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/extending/internals.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/extending/internals.mdx
new file mode 100644
index 0000000000..11be9d3d67
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/extending/internals.mdx
@@ -0,0 +1,252 @@
+---
+page_title: >-
+ Extending Sentinel: Internals
+sidebar_current: docs-extending-internals
+description: This Section covers some details about Sentinel's import internals.
+layout: docs
+---
+
+# Extending Sentinel: Internals
+
+-> **Advanced Topic!** This page covers low-level technical details of Sentinel.
+You don't need to understand these details to effectively use Sentinel. The
+details are documented here for those who wish to learn about them.
+
+This section covers some of the internal details of Sentinel's import system.
+The goal of this section is to help remove notion of "magic" from Sentinel, to
+allow you to trust and understand what Sentinel is doing with imports, and also
+to help practitioners and developers alike understand the differences between
+the ways that imports can be loaded into Sentinel.
+
+## Language and Runtime
+
+Understanding the import system in Sentinel generally requires understanding of
+two key concepts within Sentinel: the _language_, and the _runtime
+implementation_ of the language.
+
+The Sentinel language and the rules governing it are detailed in the [Sentinel
+Language Specification](/sentinel/language/spec). A runtime implementation must
+conform to the rules laid out in the specification at a minimum. However, to
+meet the design goals of a particular implementation, the runtime may implement
+features on top of the language. The subtle implementation details within the
+runtime may also vary while still being compliant with the specification.
+
+The core runtime included within the [Sentinel CLI](/sentinel/commands) is
+sometimes referred to as the _reference implementation_ of the Sentinel
+language. This runtime is also the main runtime embedded within each
+Sentinel-enabled HashiCorp product such as Terraform Cloud and Enterprise, Vault
+Enterprise, Nomad Enterprise, and Consul Enterprise. The runtime includes an API
+to allow these integrations implement Sentinel in as robust or as restrictive of
+a way as possible, so depending on the implementation, your experience may vary.
+
+## Sentinel's Import Model
+
+The concept of Imports within Sentinel is a _language_ feature. You can see the
+exact rules governing imports within the
+[Imports](/sentinel/language/spec#imports) section of the specification.
+
+Within the runtime, there are currently two major classifications of imports:
+
+- **Modules:** The mapping of Sentinel code to an import, essentially mapping a
+ scope of values to a particular package.
+- **Binary Imports:** A grouping of embedded and external plugins written using
+ the Sentinel SDK. These are comprised of internal or embedded imports (usually
+ the [standard imports](/sentinel/imports) and imports included by an
+ integration) and import plugins.
+
+Below is a diagram explaining in brief the relationship between the langauge
+and runtime features.
+
+
+
+This kind of topology ultimately minimizes the visibility of implementation
+internals to the actual policy language. This allows us to keep the Sentinel
+language itself simple and keep concerns such as module or plugin management,
+validation, and configuration out of the language. These kinds of things would
+impact the readability of a policy, possibly present security challenges, and
+would ultimately be of no use to more minimal embedded applications.
+
+### Implementation Differences Between Import Types
+
+As one would imagine, both modules and binary imports are loaded in much
+different ways, and the methods that they expose data to Sentinel differ as
+well. The effect is generally the same however across both, and we take care to
+ensure that both modules and binary imports behave as close as possible to each
+other, however there are some subtle differences:
+
+- Modules currently support the exporting of rules, but do not support function
+ calls with object receiver data (as seen in some standard imports such as
+ [`decimal`](/sentinel/imports/decimal), [`http`](/sentinel/imports/http), and
+ [`time`](/sentinel/imports/time)). This will be coming in a later release.
+- Binary imports cannot export rules. However, they do support object receiver
+ data (see [`framework.New`](/sentinel/extending/plugins#framework-new) in
+ [Extending Sentinel: Plugins](/sentinel/extending/plugins)).
+
+## Modules
+
+_Modules_ are essentially Sentinel code that is intended to be re-used in multiple
+policies as an import.
+
+The mechanism of module loading is fairly straightforward: during initialization
+of the Sentinel runtime, modules are parsed along with policies. The parsed
+_abstract syntax tree_ (AST) for each module is loaded into the runtime under
+the specified _import path_. These are then passed to the lower-level
+interpreter during the evaluation of each policy.
+
+As with policy code, during evaluation, a module's AST is walked to execute the
+code within that specific module. The module data is then stored within an
+specific scope mapped to the import name. The module data can then be accessed
+through the import via selector expressions and function calls (e.g.,
+`foo.some_map` or `foo.some_func()` for a module loaded via `import "foo"`).
+
+The AST for a module is only walked if a policy imports it, and it is _only
+walked once_. That means if a policy and a module import the same module, or a
+policy imports the same module twice (using [import
+aliases](/sentinel/language/imports#aliases)), those instances will share state.
+If you call a function that alters a singleton variable within the module, that
+singleton will be modified for all instances of the import.
+
+-> **Fun fact!** When you [mock an import with Sentinel
+code](/sentinel/configuration#mocking-with-sentinel-code), you are actually
+using a module. The module is loaded with a flag that allows it to override an
+existing import, which would normally give a runtime error.
+
+### Map and List Cloning for Module Calls
+
+It's also worthwhile noting that map and list values are cloned for modules.
+
+Consider the case where you have a module with a map singleton.
+
+```sentinel
+// modules/foo.sentinel
+a_map = {"a": "b"}
+```
+
+Normally, assignment to the singleton, even when using an index expression, is
+blocked, so `foo.a_map["c"] = "d"` would not pass semantic checking.
+
+However, even when you try to do assignment via an intermediary, you will still
+be only modifying the copy, and the original will not be affected:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+a_map_copy = foo.a_map
+a_map_copy["c"] = "d" // Only modifies copy, does not modify module singleton
+print(foo.a_map) // Still only prints {"a": "b"}
+```
+
+This is to ensure that modules are consistent with binary imports in how return
+data is handled, and the expectation that most non-object data within an import
+is read-only, with the exception of modification of singleton data via
+functions. As return of complex object and collection data in a binary import is
+always via copy, it's not possible to modify any value elements within the
+import in a similar fashion.
+
+There are also some other implications of this cloning that are of note:
+
+- Rules are not cloned, either within a list or map, or by themselves. This is
+ to ensure that [memoization](/sentinel/language/rules#lazy-and-memoized)
+ functions correctly. As only a module can export rules at this time, there is no
+ parity for this in binary imports.
+- Functions are _not_ cloned. This mainly has implications for scope - for
+ example, if you assign a function to another value, or assign an object to a
+ value that has a method that utilizes a global module variable, those calls will
+ reference and/or modify those values.
+
+Functions, while represented differently in binary imports, generally follow the
+same logic here - as they would be invoked in the same package within the binary
+import, any singleton values they accessed there would be affected in a similar
+fashion. As rules are pseudo-functions, this generally makes sense here too.
+
+## Binary Imports
+
+_Binary imports_ is a grouping referring to all imports that are designed with
+the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). These can be
+either internally embedded, or executed via a plugin.
+
+All imports found in the [standard library](/sentinel/imports) are binary
+imports, embedded internally.
+
+The main difference between internally embedded imports and external plugins is
+whether or not the runtime needs to execute a plugin binary as part of
+[lifecycle management](#lifecycle), and whether or not it needs to
+[communicate](#communication) over RPC. Internal binary imports do not require
+these steps, so their execution model is somewhat simpler; however, technically,
+they still are the same as external plugins in terms of design model and value
+conversion. Most standard imports are even tested using the [SDK test
+suite](/sentinel/extending/plugins#testing), which builds the import as an
+external plugin.
+
+The remainder of this section discusses topics mostly relevant to external
+plugins. A large amount of technical detail regarding binary import development
+(also applicable to embedded binary imports) can be seen on the
+[Plugins](/sentinel/extending/plugins) page.
+
+-> **NOTE:** At this time, plugins can only be written for the Sentinel CLI, and
+cannot be used with any of Sentinel's integrations.
+
+### Plugin Basics
+
+Sentinel plugins are built on top of the HashiCorp [go-plugin
+system](https://github.com/hashicorp/go-plugin). This is the same plugin system
+powering all pluggable HashiCorp tools such as
+[Terraform](https://www.terraform.io), [Vault](https://www.vaultproject.io), and
+more. go-plugin is a system that has been used in production for millions of
+users for over 5 years.
+
+Plugins are executable binaries. Sentinel is responsible for launching and
+managing the lifecycle of plugins. Once a plugin is running, Sentinel
+communicates with the plugin via [gRPC](https://grpc.io/). The [protocol is open
+source](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/import.proto)
+to allow anyone to write a Sentinel plugin.
+
+### Lifecycle
+
+Sentinel launches plugins and is responsible for plugin lifecycle.
+
+The runtime optimizes for latency by launching the plugin as soon as it is
+configured, rather than when a policy requires it. This ensures that the plugin
+is ready to be used immediately. A plugin is only closed when Sentinel is
+reconfigured to no longer allow that plugin or if the Sentinel-enabled
+application is closing.
+
+When a plugin is removed from the configuration, Sentinel may wait to shut down
+the plugin until all currently executing policies that are using that plugin
+complete. New policy executions will not be allowed to use the old plugins.
+
+Plugins are automatically restarted if they shut down unexpectedly. Policies
+that were executing while this happens may fail. The Sentinel system is
+improving to more gracefully understand locations where it is safe to retry an
+execution.
+
+### Communication
+
+An [initial
+handshake](https://github.com/hashicorp/go-plugin/blob/master/docs/internals.md)
+is done via stdout to determine the main communication location. Following the
+handshake, communication will either occur on a local Unix domain socket or via
+a local-only TCP socket. This communication is done mostly over the low-level
+[`Get`](https://github.com/hashicorp/sentinel-sdk/blob/2b4db07dd12b55142f0c016f301af80aa4422520/proto/import.proto#L12)
+RPC call.
+
+#### Value Conversion
+
+As can generally be seen in [Developing an Import
+Plugin](/sentinel/extending/plugins#developing-an-import-plugin), the Sentinel
+type system is not exposed to binary imports. While explicit values for null and
+undefined exist, values are mostly converted over the wire from their Go values
+to their Sentinel equivalents.
+
+This has a few implications:
+
+- As discussed in [Map and List Cloning for Module
+ Calls](#map-and-list-cloning-for-module-calls), Map and list data returned from
+ binary import value lookups or function calls is always cloned. These can be
+ freely modified without affecting anything within the binary import.
+- It's currently impossible to represent certain Sentinel types such as
+ [rules](/sentinel/language/rules) within a binary import. This is not a major
+ issue at this point in time as practitioners generally do not look to binary
+ imports for rules; we may examine this as a later time if this situation
+ changes.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/extending/modules.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/extending/modules.mdx
new file mode 100644
index 0000000000..9846d263e5
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/extending/modules.mdx
@@ -0,0 +1,135 @@
+---
+page_title: >-
+ Extending Sentinel: Modules
+sidebar_current: docs-extending-modules
+description: >-
+ Modules allow you to re-use Sentinel code as an import.
+layout: docs
+---
+
+# Extending Sentinel: Modules
+
+Modules allow you to re-use Sentinel code as an import. This allows for the
+export of any general construct that Sentinel supports, including values,
+functions, and even rules. As such, it's a great way to package code that is
+useful across multiple policies, reducing boilerplate and making final policy
+code simpler.
+
+You may want to use modules for:
+
+- **Writing a simple re-usable helper library.** If you mostly know Sentinel or
+ have helpers that you have written in Sentinel, moving these helpers to a module
+ will offer a low-friction way of facilitating their re-use.
+- **Writing re-usable rules.** If you have a set of
+ [rules](/sentinel/language/rules) you know you want to use across multiple
+ policies, bundling them into a module is a great way of abstracting the logic
+ away.
+- **Abstracting existing Sentinel imports.** Using a module is a great way to
+ extend existing Sentinel imports by abstracting the common functionality that
+ you use within an import to your own workflow.
+
+This page describes how you can use modules within the context of the Sentinel
+CLI. For instructions for a particular integration, see the documentation for
+that product.
+
+-> **Not all Sentinel-enabled applications support modules.** Please refer
+to the documentation of the specific Sentinel-enabled application. Some
+applications disable modules for security reasons.
+
+## Configuring a Module
+
+A module is configured within the Sentinel CLI by adding an entry for it in
+within the `imports` section of the [CLI configuration
+file](/sentinel/configuration). The key for each entry specifies the path that
+the module is imported as.
+
+```hcl
+import "module" "foo" {
+ source = "modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "modules/bar.sentinel"
+}
+```
+
+In this example, we have two modules:
+
+- Import path `foo`, being loaded from the code within `modules/foo.sentinel`.
+- Import path `bar`, being loaded from the code within `modules/bar.sentinel`.
+
+See the [Import Modules](/sentinel/configuration#import-modules) section within
+the CLI configuration file syntax page for more details.
+
+### Remote Modules
+
+In addition to loading from a local source, modules can be loaded from a remote
+location. This can increase reusability and allow for sharing modules across
+multiple configurations.
+
+### Testing Using Modules
+
+As with [mocks](/sentinel/configuration#mocking-with-sentinel-code), modules
+are loaded relative to the configuration file location when using `sentinel test`. As such, you need to account for this in your test configuration files:
+
+```hcl
+import "module" "foo" {
+ source = "../../modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "../../modules/bar.sentinel"
+}
+```
+
+This could be a test file for a policy (example: `policy.sentinel`), residing
+under the correct test file directory for this policy (example:
+`test/policy/pass.json`).
+
+## Writing and Using a Module
+
+Writing a module is nearly the same as writing a Sentinel policy, except for the
+following two exceptions:
+
+- Modules do not require [`main`](/sentinel/writing/basic#the-simplest-policy)
+ to be present. It can be, but it will not carry any extra significance as it
+ does within a policy.
+- [`param`](/sentinel/language/parameters) declarations cannot be present in a module. If they are encountered,
+ a runtime error is given.
+
+Once you have a complete module ready for use and configured, using the module
+is as simple as importing it.
+
+Building on the above [configuration example](#configuring-a-module), we can
+export a simple test function in the module `foo` by adding this code to
+`modules/foo.sentinel`:
+
+```sentinel
+// modules/foo.sentinel
+hello = func() {
+ print("hello world!")
+ return undefined
+}
+```
+
+We can then import it within our policy and use it:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+// prints "hello world" in the trace
+foo.hello()
+
+main = true
+```
+
+Note that modules are considered [imports](/sentinel/language/spec#imports) by
+the Sentinel runtime and as such conform to the specification. This means that
+you cannot directly assign values to anything behind a module scope. You can,
+however, use functions to change state.
+
+-> **NOTE:** At this time, modules do not support object receiver data in the
+way that some imports do, like [`time`](/sentinel/imports/time),
+[`decimal`](/sentinel/imports/decimal), and [`http`](/sentinel/imports/http).
+Support for this will be coming in later versions of Sentinel.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/extending/plugins.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/extending/plugins.mdx
new file mode 100644
index 0000000000..e80de5d4b1
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/extending/plugins.mdx
@@ -0,0 +1,521 @@
+---
+page_title: >-
+ Extending Sentinel: Plugins
+sidebar_current: docs-extending-plugins
+description: >-
+ Plugins allow you to write imports as standalone executables, allowing you to
+ extend Sentinel in ways not normally possible with policy code alone.
+layout: docs
+---
+
+# Extending Sentinel: Plugins
+
+-> **NOTE:** Plugins are currently only supported on the CLI and not in any
+production Sentinel integration, with the exception of existing configuration
+in [Nomad](https://www.nomadproject.io/docs/configuration/sentinel).
+
+Plugins allow you to write imports as standalone executables, allowing you to
+extend Sentinel in ways not normally possible with policy code alone.
+
+You might want to write or use a plugin when you need to:
+
+- **Use a provider or API integration for Sentinel.** In this case, you will
+ probably be working with a provider SDK, or other libraries that will be making
+ complex network requests to external services. Sentinel only provides limited
+ capabilities for these kinds of operations in the standard library, so writing
+ this kind of import as a module is not optimal.
+- **Introduce novel functionality not currently supported by Sentinel.**
+ Sentinel's policy language is limited by design to encourage simple policies,
+ reduce its runtime footprint, and to keep it efficient and safe. This will
+ mean that some functionality may be difficult or impossible to express as
+ Sentinel code, and would require a plugin to write.
+- **Extend a complex module.** Additionally, modules are restricted to a single
+ file of Sentinel code, so these will naturally become more and more unwieldy as
+ you continue to add to them. Eventually, there might be a time where a plugin is
+ better suited for the functionality, especially if it's a general-purpose
+ library.
+
+This section details how import plugins are currently configured in the Sentinel
+CLI, and some detail on how they are written. As we continue to build on the
+ability to use import plugins, we will extend this section with more details.
+
+-> **Not all Sentinel-enabled applications will support plugins.** Some
+applications will disable plugins for security reasons. For those that do enable
+plugins, the method to configure plugins will vary.
+
+## Installing Plugins
+
+Sentinel plugins are standalone executables. Sentinel-enabled applications such
+as the CLI launch these plugins and communicate with them via
+[RPC](https://en.wikipedia.org/wiki/Remote_procedure_call). To install a plugin,
+the first step is to download the plugin executable.
+
+After downloading the plugin, you must add it in the CLI configuration file,
+setting its name, path, and any arguments, environment variables, or
+configuration. An example is below:
+
+```hcl
+import "plugin" "custom_time" {
+ source = "/path/to/sentinel-import-time"
+ config = { "fixed_time": 1504155600 }
+}
+```
+
+-> **NOTE:** We are using "custom_time" as standard import paths cannot be
+overriden. See [Configuration File Syntax](/sentinel/configuration#imports) for
+more details.
+
+After configuring the plugin, it will be available on the next `sentinel apply`
+or `sentinel test`.
+
+For more details on configuration, see the
+[plugins](/sentinel/configuration#plugins) section of the [CLI configuration
+file syntax](/sentinel/configuration).
+
+## Debugging Plugins
+
+The Sentinel CLI logs when it launches, configures, and closes a plugin. The
+plugin may also log additional information when it is running.
+
+To debug issues with a plugin, you can usually refer to these logs. Depending on
+the plugin configuration, the logs you see may vary - refer to the plugin
+documentation on how to enable logs for a particular plugin.
+
+## Developing an Import Plugin
+
+Anyone can develop a Sentinel import plugin using the [Sentinel
+SDK](https://github.com/hashicorp/sentinel-sdk). The SDK contains a high-level
+framework for writing plugins in [Go](https://golang.org) including a test
+framework. Additionally, the SDK contains the low-level [gRPC protocol buffers
+definition](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/plugin.proto)
+for writing plugins in other languages.
+
+The rest of this page will document how to write a new Sentinel plugin in Go
+using the Sentinel SDK and the high-level framework provided. You are expected
+to already know the basics of Go and have a properly configured Go installation.
+
+Throughout this page, we'll be developing a simple plugin to access time values.
+
+-> **NOTE:** The example here is not reflective of the actual implementation of
+the [`time`](/sentinel/imports/time) standard import, so make sure to not
+get the two confused.
+
+### Plugin Framework
+
+The Sentinel SDK provides a high-level
+[framework](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework)
+for writing plugins. We recommend using this framework.
+
+#### Basic Concepts
+
+This framework works by implementing the correct Go interfaces.
+
+As a general overview:
+[`framework.Plugin`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Plugin)
+implements the
+[`sdk.Plugin`](https://godoc.org/github.com/hashicorp/sentinel-sdk#Plugin)
+interface and therefore is a valid import plugin. You must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface to configure the plugin. The root may then delegate nested access to
+various
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+implementations. Other interfaces are implemented to provide functionality past
+what is provided by the basic `framework.Namespace` implementation - these are
+documented below.
+
+This all may sound like a lot of interfaces, but each interface typically only
+requires a single function implementation and you're only required to implement
+a single namespace (`framework.Namespace`).
+
+As we move on, we'll use real examples to make this clear.
+
+#### Implementing framework.Root
+
+To begin, you must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface. This is the interface representing the root of your plugin. The root
+itself must be implement
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace),
+which allows values to be accessed. Note that the root can optionally implement
+other interfaces, but they're more advanced and omitted here for simplicity.
+
+For our custom time example, our root may look like this:
+
+```sentinel
+type root struct {
+ time time.Time
+}
+
+// framework.Root impl.
+func (m *root) Configure(raw map[string]interface{}) error {
+ if _, ok := raw["timestamp"]; !ok {
+ raw["timestamp"] = time.Now().Unix()
+ }
+
+ v := raw["timestamp"]
+ timestamp, ok := v.(int)
+ if !ok {
+ return fmt.Errorf("invalid timestamp type %T", v)
+ }
+
+ m.time = time.Unix(timestamp, 0).UTC()
+ return nil
+}
+
+// framework.Namespace impl.
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "minute":
+ return m.time.Minute(), nil
+ }
+
+ return nil, nil
+}
+```
+
+This example implements `framework.Root` and `framework.Namespace` and would
+enable the following within a Sentinel policy once the completed plugin is
+installed.
+
+```sentinel
+import "custom_time"
+
+print(custom_time.minute)
+main = true
+```
+
+#### framework.Namespace
+
+The
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+interface implements a namespace of values. You saw above that
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+must itself be a namespace. However, a namespace `Get` implementation may
+further return namespaces to access nested values.
+
+This enables behavior such as `custom_time.month.string` vs. `custom_time.month.index`.
+Notice each of these examples accesses a nested value within `month`. This can
+be modeled as namespaces within the framework.
+
+In the example below, we return a new namespace for `month` to do just this:
+
+```sentinel
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "month":
+ return &namespaceMonth{Month: m.time.Month()}, nil
+ }
+
+ // ...
+}
+
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil nil
+}
+```
+
+#### Primitive Types and Structs
+
+A namespace may also return any primitive Go type as well as structs. The
+framework automatically exposes primitive values as you would expect. For
+structs, exported fields are lowercased and exposed to the policies. The
+`sentinel` struct tag can be used to control how Sentinel can access the field.
+
+In the example below, we expose a struct directly from the root namespace:
+
+```sentinel
+type Location struct {
+ Name string
+ TimeZone string `sentinel:"time_zone"`
+}
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "location":
+ return &Location{Name: "somewhere", TimeZone: "some zone"}, nil
+ }
+
+ // ...
+}
+```
+
+This makes the following values work within a Sentinel policy:
+`time.location.name`, `time.location.time_zone`.
+
+If a field has an empty `sentinel` struct tag (example: `sentinel:""`),
+then that field will not be accessible from a policy.
+
+#### Optional Interfaces
+
+The following are optional interfaces that can be implemented on top of
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace).
+All of these interfaces can be implemented at any level, except for
+[`framework.New`](#framework-new), which only works at the root level.
+
+##### `framework.Call`
+
+The
+[`framework.Call`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Call)
+interface can be implemented to support function calls.
+
+Implementing this interface is very similar to attribute access, except instead
+of returning the attribute value, you return a function. The framework uses Go
+reflection to determine the argument and result types and calls it. If the
+argument types do not match or the signature is otherwise invalid, an error is
+returned.
+
+For example, let's implement a function to add months to the current month:
+
+```sentinel
+func (m *root) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *root) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.time.AddDate(0, n, 0).Month()}
+}
+```
+
+You can now call the function. If today was in the month of September,
+the policy below would pass:
+
+```sentinel
+import "custom_time"
+
+main = custom_time.add_month(4).string == "January"
+```
+
+##### `framework.Map`
+
+The optional
+[`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+interface allows an alternative method to to present a namespace back to a
+Sentinel policy as a map.
+
+`framework.Map` is best paired with
+[`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys),
+which allows you to quickly fetch the necessary data via `Get` calls on the
+namespace:
+
+```
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil, nil
+}
+
+func (m *namespaceMonth) Map() (map[string]interface{}, error) {
+ return framework.MapFromKeys(m, []string{"string", "index"})
+}
+```
+
+##### `framework.New`
+
+The optional
+[`framework.New`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#New)
+interface can be implemented on a root namespace to add _methods_ to your
+namespaces.
+
+Data returned from a namespace is memoized and returned as a map, and is
+normally not callable - to call a function to operate on the data, you would
+need to create a new namespace from the top-level of the plugin, and then make a
+function call on the result within the same expression.
+
+Let's consider the [root example](#implementing-framework-root). Let's say,
+instead of hard-coding the time value at configuration, we wanted to load it
+on-demand using a `time.now` key, and allow `add_month` to be callable on that
+value. Our root and de-coupled time namespace would now look like:
+
+```
+type root struct{}
+
+func (m *root) Configure(raw map[string]interface{}) error { return nil }
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "now":
+ return &namespaceTime{Time: time.Now()}
+ }
+
+ return nil
+}
+
+func (m *root) New(data map[string]interface{}) (framework.Namespace, error) {
+ if v, ok := data["unix"]; ok {
+ if t, ok := v.(int64); !ok {
+ return &namespaceTime{Time: time.Unix(t, 0)}
+ }
+
+ return nil, fmt.Errorf("expected timestamp to be int64, got %T", v)
+ }
+
+ return nil, nil
+}
+
+type namespaceTime struct {
+ Time time.Time
+}
+
+func (m *namespaceTime) Get(key string) interface{} {
+ switch key {
+ case "unix":
+ return m.Time.Unix()
+
+ // case ...
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) Get(key string) (interface{}, error) {
+ return framework.MapFromKeys(m, []string{"unix", "..."})
+}
+
+func (m *namespaceTime) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.Time.AddDate(0, n, 0).Month()}
+}
+```
+
+Via this example, we can now create and assign a `namespaceTime` using `t = custom_time.now`, and then call `t.add_month(months_to_add)` to return a
+`namespaceMonth` for the corresponding month.
+
+Methods on a namespace can also _modfiy_ the receiver data. So you can, for
+example, add a method named `increment_month` that takes no arguments, but
+increments the time stored in `namespaceTime.Time` by a month. Subsequent
+statements using the receiver would see the new value.
+
+Note that there are some restrictions and guidelines that you should take into
+account when using `framework.New`:
+
+- Only data that makes it back to a policy can be used as receiver data to
+ instantiate new namespaces. As such it's recommended to make use of
+ [`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+ and
+ [`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys)
+ to return all of the attribute data you need to construct new objects.
+- `framework.New` is only supported on the root namespace and has no effect on
+ namespaces below the root. Check all possible cases of receiver data within
+ the top-level `New` function and return the appropriate namespace based on the
+ input data.
+- Do not return general errors from your `New` method. When an unknown lookup is
+ made off of memoized data, it will hit your `New` method for possible
+ instantiation and key calls. This will ensure `undefined` is correctly
+ returned for these cases.
+- `framework.New` is designed to add utility to plugins where calling methods on
+ a value is more intuitive than just making a function call, or just returning
+ all of a data set as a memoized map. Use it sparingly and avoid using it on
+ recursively complex data sets.
+
+### Testing
+
+The SDK exposes a testing framework to verify your plugin works as
+expected. This test framework integrates directly into `go test` so it
+is part of a familiar workflow.
+
+The test framework works by dynamically building your plugin and
+running it against the `sentinel` CLI to verify it behaves as expected.
+This ensures that your plugin builds, your plugin communicates via RPC
+correctly, and that the behavior within a policy is also correct.
+
+The example below shows a complete ready table-driven test for our time plugin.
+You can run this with a normal `go test`.
+
+```sentinel
+package main
+
+import (
+ "os"
+ "testing"
+
+ "github.com/hashicorp/sentinel-sdk"
+ plugintesting "github.com/hashicorp/sentinel-sdk/testing"
+)
+
+func TestMain(m *testing.M) {
+ exitCode := m.Run()
+ plugintesting.Clean()
+ os.Exit(exitCode)
+}
+
+func TestPlugin(t *testing.T) {
+ cases := []struct {
+ Name string
+ Data map[string]interface{}
+ Source string
+ }{
+ {
+ "month",
+ map[string]interface{}{"timestamp": 1495483674},
+ `main = subject.month.string == "September"`,
+ },
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.Name, func(t *testing.T) {
+ plugintesting.TestPlugin(t, plugintesting.TestPluginCase{
+ Config: tc.Data,
+ Source: tc.Source,
+ })
+ })
+ }
+}
+```
+
+### Building Your Plugin
+
+To build your plugin, you must first implement the `main` function.
+The main function should just use the `rpc.Serve` method to serve the
+plugin over the Sentinel RPC layer:
+
+```sentinel
+package main
+
+import (
+ "github.com/hashicorp/sentinel-sdk"
+ "github.com/hashicorp/sentinel-sdk/rpc"
+)
+
+func main() {
+ rpc.Serve(&rpc.ServeOpts{
+ PluginFunc: func() sdk.Plugin {
+ return &framework.Plugin{Root: &root{}}
+ },
+ })
+}
+```
+
+You can then build this using `go build`. The output can be named
+anything. Once your plugin is built, [install it](#installing-plugins)
+and try it out!
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/extending/static-imports.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/extending/static-imports.mdx
new file mode 100644
index 0000000000..cb51beb5d8
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/extending/static-imports.mdx
@@ -0,0 +1,48 @@
+---
+page_title: >-
+ Extending Sentinel: Static Imports
+sidebar_current: docs-extending-static-imports
+description: >-
+ Static imports allow you to write imports that use static data files.
+layout: docs
+---
+
+# Extending Sentinel: Static Imports
+
+Static imports allow you to write imports that use static data files. This can
+provide methods of consuming data in ways that are not available via
+[modules](./modules) or [plugins](./plugins).
+
+### Configuring a Static Import
+
+The [configuration page](/sentinel/configuration#static-imports) details how to
+configure static imports.
+
+### Usage
+
+Using a static import is not that different to using a plugin or module. The
+import must first be introduced to the policy via an import statement.
+
+```sentinel
+import "people"
+```
+
+From here, the static data can be used throughout the policy as you would any
+normal Sentinel value.
+
+```sentinel
+admins = filter people as person {
+ person.role is "Admin"
+}
+```
+
+One differentiator for static imports is that you can directly reference the
+import without the use of selectors. For example, to print the value;
+
+```sentinel
+print(people)
+```
+
+This is due to the static data being directly loaded and converted into a
+Sentinel value, ready for use. Static imports, like modules and plugins, cannot
+be assigned a new value.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/functions/append.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/functions/append.mdx
new file mode 100644
index 0000000000..81395154f6
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/functions/append.mdx
@@ -0,0 +1,46 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Append'
+sidebar_current: docs-funcs-append
+description: The built-in function `append` appends a value to the end of a list.
+layout: docs
+---
+
+# Builtin Function: append
+
+The built-in function `append` appends a value to the end of a list. The list
+is modified in-place. The return value will always be [undefined](/sentinel/language/undefined).
+
+`append` called on any value other than a list or undefined will result
+in an immediate error.
+
+Examples:
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+### Why does this function return undefined?
+
+This function returns `undefined` to avoid unintended side effects of the function in the context
+of a [rule](/sentinel/language/rules).
+
+For example, if `append` returned the list value as a result, the following policy would depend
+on the order in which rules are referenced:
+
+```sentinel
+list = []
+a = rule { append(list, 1) contains 1 }
+b = rule { append(list, 2) contains 2 }
+
+// Scenario 1: list becomes [1, 2]
+main = rule { a and b }
+
+// Scenario 2: list becomes [2, 1]
+main = rule { b and a }
+```
+
+This sort of side effect behavior introduces complex and difficult to track logical errors in programs.
+As a result, functions like this return `undefined` and modify values in-place.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/functions/delete.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/functions/delete.mdx
new file mode 100644
index 0000000000..3b60803997
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/functions/delete.mdx
@@ -0,0 +1,25 @@
+---
+page_title: 'Sentinel Language - Builtin Function: delete'
+sidebar_current: docs-funcs-delete
+description: The built-in function `delete` deletes an element from a map.
+layout: docs
+---
+
+# Builtin Function: delete
+
+The built-in function `delete` deletes an element from a map.
+
+If the element to delete does not exist, the map is left unchanged.
+Calling `delete` on a value that is not a map or
+[undefined](/sentinel/language/undefined)
+is an immediate error. The result of `delete` is always `undefined`.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/functions/error.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/functions/error.mdx
new file mode 100644
index 0000000000..96d253b662
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/functions/error.mdx
@@ -0,0 +1,15 @@
+---
+page_title: 'Sentinel Language - Builtin Function: error'
+sidebar_current: docs-funcs-error
+description: >-
+ The built-in function `error` is used to exit immediately with an error
+ message.
+layout: docs
+---
+
+# Builtin Function: error
+
+The built-in function `error` is used to exit immediately with an error message.
+
+Please see the [page on errors](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/functions/index.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/functions/index.mdx
new file mode 100644
index 0000000000..4e12143ad0
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/functions/index.mdx
@@ -0,0 +1,13 @@
+---
+page_title: Sentinel Language - Builtin Functions
+sidebar_current: docs-funcs
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Language: Builtin Functions
+
+Builtin functions are
+[functions](/sentinel/language/functions)
+that are available in all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in function.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/functions/keys.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/functions/keys.mdx
new file mode 100644
index 0000000000..a96cafe897
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/functions/keys.mdx
@@ -0,0 +1,22 @@
+---
+page_title: 'Sentinel Language - Builtin Function: keys'
+sidebar_current: docs-funcs-keys
+description: The built-in function `keys` returns the keys of a map.
+layout: docs
+---
+
+# Builtin Function: keys
+
+The built-in function `keys` returns the keys of a map.
+
+`keys` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned keys are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+```
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/functions/length.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/functions/length.mdx
new file mode 100644
index 0000000000..8b874b14c4
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/functions/length.mdx
@@ -0,0 +1,23 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Length'
+sidebar_current: docs-funcs-length
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Builtin Function: length
+
+The built-in function `length` returns the length of a collection or string.
+
+The length of a string is the number of bytes in the string. It is not
+necessarilly the number of characters. The length of a collection is the
+number of elements in that collection. The length of
+[undefined](/sentinel/language/undefined) is `undefined`.
+
+Examples:
+
+```sentinel
+length("foo") // 3
+length([1, 2, 3, 4]) // 4
+length({ "key": "value" }) // 1
+```
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/functions/print.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/functions/print.mdx
new file mode 100644
index 0000000000..bcf441d09d
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/functions/print.mdx
@@ -0,0 +1,13 @@
+---
+page_title: 'Sentinel Language - Builtin Function: print'
+sidebar_current: docs-funcs-print
+description: The built-in function `print` is used for logging and debugging.
+layout: docs
+---
+
+# Builtin Function: print
+
+The built-in function `print` is used for logging and debugging.
+
+Please see the [page on logging](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/functions/range.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/functions/range.mdx
new file mode 100644
index 0000000000..0f86bfc402
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/functions/range.mdx
@@ -0,0 +1,49 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Range'
+sidebar_current: docs-funcs-range
+description: The built-in function `range` returns a list of numbers in a range.
+layout: docs
+---
+
+# Builtin Function: range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it
+defaults to `1`.
+
+Negative steps are permitted and count down from `start` towards `end`, instead
+of up.
+
+The range ends when the next value given by the sum of the last value and `step`
+would exceed `end`, regardless of if it has been reached.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4] - starts at 1 instead of 0
+range(1, 5, 2) // [1,3] - 3+2 does not satisfy r[-1] < end
+range(0, -3, -1) // [0,-1,-2] - example of negative range
+```
+
+Impossible ranges, or ranges where `start` will never reach `end` given `step`,
+yield an empty list.
+
+```
+range(1, 1) // [] - already starting at 1
+range(0, 1, -1) // [] - negative step will never reach 1
+```
+
+Supplying an undefined value to any argument of `range` yields an undefined
+value back.
+
+A range with a zero step is a runtime error.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/functions/values.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/functions/values.mdx
new file mode 100644
index 0000000000..8f98d3ab56
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/functions/values.mdx
@@ -0,0 +1,22 @@
+---
+page_title: 'Sentinel Language - Builtin Function: values'
+sidebar_current: docs-funcs-values
+description: The built-in function `values` returns the values of a map.
+layout: docs
+---
+
+# Builtin Function: values
+
+The built-in function `values` returns the values of a map.
+
+`values` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned values are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+values(data) // [3, 2]
+```
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/imports/base64.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/imports/base64.mdx
new file mode 100644
index 0000000000..4e58429268
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/imports/base64.mdx
@@ -0,0 +1,46 @@
+---
+page_title: 'Import: base64'
+sidebar_current: docs-imports-base64
+description: The base64 import enables a Sentinel policy to encode and decode Base64 values.
+layout: docs
+---
+
+# Import: base64
+
+The base64 import enables a Sentinel policy to encode and decode Base64 values.
+
+### base64.encode(str)
+
+Encodes the string `str` into a Base64 encoded string, using the standard
+encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-4).
+
+```sentinel
+enc = base64.encode("foo") // "Zm9v"
+```
+
+### base64.decode(str)
+
+Decodes the Base64 encoded string `str`, returning the string result,
+using the standard encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-4).
+
+```sentinel
+dec = base64.decode("Zm9v") // "foo"
+```
+
+### base64.urlencode(str)
+
+Encodes the string `str` into a Base64 encoded string, using the alternate
+encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-5).
+
+```sentinel
+enc = base64.urlencode("foo") // "Zm9v"
+```
+
+### base64.urldecode(str)
+
+Decodes the Base64 encoded string `str`, returning the string result, using the
+alternate encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-5).
+
+```sentinel
+dec = base64.urldecode("Zm9v") // "foo"
+```
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/imports/decimal.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/imports/decimal.mdx
new file mode 100644
index 0000000000..a6633a38e0
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/imports/decimal.mdx
@@ -0,0 +1,318 @@
+---
+page_title: 'Import: decimal'
+sidebar_current: docs-imports-decimal
+description: |-
+ The decimal import provides functions for operating on numbers represented
+ as decimals.
+layout: docs
+---
+
+# Import: decimal
+
+The decimal import provides functions for operating on numbers represented
+as decimals.
+
+Binary floating-point numbers provided by the `float` type are unable to
+fully represent numbers like `1.1` and `2.2`. The decimal import can
+represent these numbers exactly, and so should be used when exactness is
+required.
+
+Decimals are created through the `new` function, which takes any type that
+can represent a number. Arguments to the decimal operators can also take
+a value of any of these types. The full list is:
+
+- float
+- int
+- string
+- existing decimal value
+
+### decimal.infinity(sign)
+
+Creates an infinite-value decimal. Infinite-value decimals are always larger
+or smaller, depending on sign, than any non-infinite decimals.
+
+```sentinel
+decimal.infinity(1) // +Infinity
+decimal.infinity(-1) // -Infinity
+```
+
+Also available as `decimal.inf`.
+
+### decimal.nan
+
+A decimal representing a "not-a-number" value. NaNs are not equal to any
+other number, even themselves.
+
+```sentinel
+decimal.new(-1).sqrt() // NaN
+decimal.new(0).mul(decimal.inf(1)) // NaN
+decimal.nan.is(decimal.nan) // false
+```
+
+### decimal.is_infinite(d)
+
+Evaluates to true if the decimal is infinite.
+
+```sentinel
+decimal.is_infinite(100) // false
+decimal.is_infinite("Infinity") // true
+```
+
+Also available as `decimal.is_inf`, `decimal.isinf` and `decimal.isinfinite`.
+
+### decimal.is_nan(d)
+
+Evaluates to true if the decimal is not-a-number.
+
+```sentinel
+decimal.is_nan("NaN") // true
+```
+
+Also available as `decimal.isnan`.
+
+### decimal.new(v)
+
+Constructs a decimal from another value.
+
+```sentinel
+decimal.new("1.1") // Constructs a decimal from a string.
+decimal.new(2.2) // Constructs a decimal from a float.
+decimal.new(3) // Constructs a decimal from an integer.
+decimal.new("1.1234E+400") // Constructs a decimal from a string using E-notation.
+```
+
+## Type: number
+
+Numbers are represented internally by a sign, coefficient, and exponent.
+The value is calculated with the formula `sign * coefficient * 10^exponent`.
+Exponent is limited to 32 bits, and the coefficient is limited by the total
+precision.
+
+The maximum precision a number can represent is 100 digits. This includes
+both before and after the decimal place.
+
+### number.string
+
+A string representation of the decimal.
+
+```sentinel
+decimal.new(1.5).string // 1.5
+```
+
+### number.sign
+
+A number between `-1` and `+1` representing the sign of the decimal.
+
+```sentinel
+decimal.new(1.5).sign // 1
+```
+
+### number.coefficient
+
+The coefficient component of the decimal.
+
+```sentinel
+decimal.new(1.5).coefficient // 15
+```
+
+### number.exponent
+
+The exponent component of the decimal.
+
+```sentinel
+decimal.new(1.5).exponent // -1
+```
+
+### number.float
+
+A floating-point representation of the decimal.
+
+```sentinel
+decimal.new(1.5).float // 1.500000
+```
+
+### number.int
+
+An integer representation of the decimal. This always rounds down to the nearest integer.
+
+```sentinel
+decimal.new(1.5).int // 1
+```
+
+### number.is(v)
+
+Test for equality with another value.
+
+```sentinel
+decimal.new(1.5).is(1) // false
+```
+
+### number.is_not(v)
+
+Test for inequality with another value.
+
+```sentinel
+decimal.new(1.5).is_not(1) // true
+```
+
+### number.less_than(v)
+
+Test that the decimal is less than another value.
+Can also be called as `number.lt(v)`
+
+```sentinel
+decimal.new(1.5).less_than(1) // false
+```
+
+### number.less_than_or_equals(v)
+
+Test that the decimal is less than or equals to another value.
+Can also be called as `number.lte(v)`
+
+```sentinel
+decimal.new(1.5).less_than_or_equals(1) // false
+```
+
+### number.greater_than(v)
+
+Test that the decimal is greater than another value.
+Can also be called as `number.gt(v)`
+
+```sentinel
+decimal.new(1.5).greater_than(1) // true
+```
+
+### number.greater_than_or_equals(v)
+
+Test that the decimal is greater than or equals to another value.
+Can also be called as `number.gte(v)`
+
+```sentinel
+decimal.new(1.5).greater_than_or_equals(1) // true
+```
+
+### number.add(v)
+
+Add another number to the decimal.
+
+```sentinel
+decimal.new(1.5).add(1) // 2.5
+```
+
+### number.subtract(v)
+
+Subtract another number from the decimal.
+Can also be called as `number.sub(v)`
+
+```sentinel
+decimal.new(1.5).subtract(1) // 0.5
+```
+
+### number.multiply(v)
+
+Multiply the decimal by a number.
+Can also be called as `number.mul(v)`
+
+```sentinel
+decimal.new(1.5).multiply(2) // 3.0
+```
+
+### number.divide(v)
+
+Divide the decimal by a number.
+Can also be called as `number.div(v)`
+
+```sentinel
+decimal.new(3.0).divide(1.5) // 2
+```
+
+### number.modulo(v)
+
+Find the remainder after dividing the decimal by a number.
+Can also be called as `mod`, `remainder`, or `rem`.
+
+```sentinel
+decimal.new(1.5).modulo(1) // 0.5
+```
+
+### number.power(v)
+
+Raise the decimal to a power.
+Can also be called as `number.pow(v)`
+
+```sentinel
+decimal.new(1.5).power(2) // 2.25
+```
+
+### number.exp()
+
+The natural exponent of the decimal. This calculates `e^number`.
+
+```sentinel
+decimal.new(1.5).exp() // 4.4816...
+```
+
+### number.loge()
+
+The natural logarithm of the decimal.
+Can also be called as `number.ln()`
+
+```sentinel
+decimal.new(1.5).loge() // 0.4054...
+```
+
+### number.log10()
+
+The base-10 logarithm of the decimal.
+Can also be called as `number.log()`
+
+```sentinel
+decimal.new(1.5).log10() // 0.1760...
+```
+
+### number.square_root()
+
+The square root of the decimal.
+Can also be called as `number.sqrt()`
+
+```sentinel
+decimal.new(1.5).square_root() // 1.2247...
+```
+
+### number.ceiling()
+
+Round the decimal to the smallest integer greater or equal to itself.
+Can also be called as `number.ceil()`
+
+```sentinel
+decimal.new(1.5).ceiling() // 2
+```
+
+### number.floor()
+
+Round the decimal to the largest integer less than or equal to itself.
+
+```sentinel
+decimal.new(1.5).floor() // 1
+```
+
+### number.absolute()
+
+The absolute value of the decimal.
+Can also be called as `number.abs()`
+
+```sentinel
+decimal.new(1.5).absolute() // 1.5
+decimal.new(-1.5).absolute() // 1.5
+```
+
+### number.negate()
+
+The negated value of the decimal. A positive decimal will become negative,
+and a negative decimal will become positive.
+Can also be called as `number.neg()`
+
+```sentinel
+decimal.new(1.5).negate() // -1.5
+decimal.new(-1.5).negate() // 1.5
+```
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/imports/http.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/imports/http.mdx
new file mode 100644
index 0000000000..f94ae98c27
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/imports/http.mdx
@@ -0,0 +1,370 @@
+---
+page_title: 'Import: http'
+sidebar_current: docs-imports-http
+description: The http import enables the use of HTTP-accessible data from outside the runtime in Sentinel policy rules.
+layout: docs
+---
+
+# Import: http
+
+The http import enables the use of HTTP-accessible data from outside
+the runtime in Sentinel policy rules.
+
+The simplest example of the import in use would be:
+
+```sentinel
+import "http"
+
+resp = http.get("https://example.hashicorp.com")
+main = rule { resp.body contains "something" }
+```
+
+By default, any request response that is not a successful "200 OK" HTTP
+response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information and
+how to customize this behavior.
+
+For more advanced requests which require setting request headers, use a
+[`request` type](#type-request):
+
+```sentinel
+import "http"
+import "json"
+
+param token
+
+req = http.request("https://example.hashicorp.com").with_header("Authorization", "token "+token)
+resp = json.unmarshal(http.get(req).body)
+main = rule { resp["some_key"] is true }
+```
+
+The `with_header` function above returns the `request` object with the
+`Authorization` HTTP header set to value of the variable `some_value`. Many
+of the methods within the http import API are designed around this concept
+of method chaining, allowing for complex building of HTTP requests
+encapsulated in functions. The following is an idiomatic example further
+demonstrating this pattern:
+
+```sentinel
+import "http"
+import "json"
+
+param github_user
+param github_token
+
+client = func(req) {
+ return http.with_timeout(5).with_retries(3)
+}
+
+github_request = func(path) {
+ full_url = "https://api.github.com" + path
+ return http.request(full_url).
+ with_basic_auth(github_user, github_token).
+ with_header("Accept", "application/vnd.github.v3+json").
+ with_header("Custom", "foo"),
+}
+
+default_response = client.get(github_request("/example"))
+
+# Override the Custom header for this single request
+custom_header_response = json.unmarshal(
+ client.get(
+ github_request("/example").with_header("Custom", "bar"),
+ ),
+)
+
+# Override the timeout value for a known slower request
+slow_response = json.unmarshal(
+ client.with_timeout(15).get(github_request("/slow-endpoint")),
+)
+
+some_key_is_true = rule { json.unmarshal(default_response.body)["some_key"] is true }
+body_contains_text = rule { custom_header_response.body contains "something" }
+response_is_ok = rule { slow_response.status_code is 200 }
+
+main = rule { some_key_is_true and body_contains_text and response_is_ok }
+```
+
+### http.client
+
+Retrieve the base HTTP client with default settings. The returned client may be
+configured by calling configuration functions on it; all of these configuration
+functions on are also aliased as top-level functions in this namespace. See
+[`client`](#type-client) below.
+
+### http.request(url)
+
+Create a new [`request`](#type-request) to the specified `url` (string). A
+`request` type enables customization of headers and other concerns before then
+providing the constructed `request` to [`get()`](#client-get-url_or_request),
+[`post()`](#client-post-url_or_request) or [`send()`](#client-send-request).
+
+```sentinel
+req = http.request("example.hashicorp.com/foo").with_header("Foo", "bar")
+resp = http.get(req)
+```
+
+### http.methodPost
+
+Returns a string containing the accepted verb for POST requests, `"POST"`.
+
+### http.methodGet
+
+Returns a string containing the accepted verb for GET requests, `"GET"`.
+
+## Type: client
+
+All of the following configuration functions on the default client are also
+aliased as top-level functions in the import. This allows a short-hand (and
+idiomatic) way of configuring a new client without having to directly access
+`http.client` each time:
+
+```sentinel
+# The following two lines are semantically the same:
+resp = http.client.with_timeout(5).get(url)
+resp = http.with_timeout(5).get(url)
+```
+
+### url_or_request
+
+The [`get()`](#client-get-url_or_request) and [`post()`](#client-post-url_or_request)
+functions accept either a URL string or a request object, noted as
+`url_or_request` throughout the documentation.
+
+When `url_or_request` is a string, a request is made with the URL
+specified by the string. To customize HTTP headers and other settings, pass
+a request. By default, a request has a timeout value of 10 seconds and 1
+retry. These settings can be adjusted with [`with_timeout()`](#clientwith_timeoutinteger) and
+[`with_retries()`](#clientwith_retriesinteger), respectively.
+
+### Redirects
+
+If the response is one of the following redirect codes, the client follows the
+redirect, up to a maximum of 10 redirects:
+
+- 301 (Moved Permanently)
+- 302 (Found)
+- 303 (See Other)
+- 307 (Temporary Redirect)
+- 308 (Permanent Redirect)
+
+If the redirect limit is exceeded, the result is a policy error.
+
+By default, after any redirects are followed (up to the maximum), any request
+response that is not a successful "200 OK" response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information
+and how to customize this behavior.
+
+### client.get(url_or_request)
+
+Issue a GET request with a URL string or a `request` type. Returns a `response`
+type.
+
+```sentinel
+import "http"
+
+resp = http.get("https://example.hashicorp.com")
+main = rule { resp.body contains "something" }
+```
+
+### client.post(url_or_request)
+
+Issue a POST request with a URL string or a `request` type. Returns a `response`
+type.
+
+```sentinel
+import "http"
+
+req = http.request("https://example.hashicorp.com")
+ .with_body(json.marshal({"foo": "bar"}))
+resp = http.post(req)
+main = rule { resp.body contains "something" }
+```
+
+### client.send(request)
+
+Make a request using the supplied `request` type. Returns a `response`.
+
+```sentinel
+import "http"
+
+req = http.request("https://example.hashicorp.com")
+resp = http.send(req)
+main = rule { resp.body contains "something" }
+```
+
+### client.accept_status_codes(list)
+
+Configures a client to not automatically cause a policy failure if
+the resulting response's status code is in `list`. Any status code received
+that is not present in this list will trigger a runtime error.
+
+By default, any request response that is not a successful "200 OK" response
+causes a policy error. This is for convenience, as the most common scenario
+by far is to receive a response and immediately signal a policy error if the
+status code is not 200. Use this scoping to specify a list of non-redirect
+HTTP codes that you wish to accept without error and evaluate the response
+yourself.
+
+Redirects are not considered final status codes in this context. That is,
+redirects are always followed up to the maximum allowed value (see [`Redirects`](#redirects))
+and the final response code in the redirect chain is validated against
+`list`.
+
+```sentinel
+resp = http.accept_status_codes([200, 404]).get(url)
+main = rule { resp.status_code is 404 }
+```
+
+### client.accepted_status_codes
+
+Returns a list of the client's accepted status codes.
+
+```sentinel
+client = http.accept_status_codes([200, 404])
+main = rule { client.accepted_status_codes contains 404 }
+```
+
+### client.with_retries(integer)
+
+Sets the maximum number of retries, specified by `integer`, that should be
+attempted on an unsuccessful request. An unsuccessful request is considered
+to be any 5xx response except `501 (Not Implemented)` or a request timeout.
+By default, one retry is attempted.
+
+The maximum number of retries is 10, for a total of 11 request attempts.
+When a request exceeds the maximum number of retries without a response, the
+result is a policy error. Specifying a number larger than this will result
+in a runtime error.
+
+Between each retry, an exponentially increasing delay is observed, allowing
+time for the server to recover. This delay starts at 1 second for the first
+retry and increases each attempt thereafter. The maximum delay for a single
+attempt is 30 seconds.
+
+Retries can be disabled by specifying 0.
+
+### client.retries
+
+Returns the client's configured number of retries as an integer.
+
+### client.with_timeout(integer)
+
+Sets the request timeout to the number of seconds indicated by `integer`.
+
+Each individual request performed by the HTTP client will be limited by the
+given request timeout. This timeout includes connection time, any redirects,
+and reading the response body.
+
+A maximum of 30 seconds is allowed. Specifying a number larger than this
+will result in a runtime error.
+
+By default, requests will time out after 10 seconds.
+
+### client.timeout
+
+Returns the client's configured timeout in whole seconds as an integer.
+
+### client.without_certificate_verification()
+
+Skips verification of TLS certificates during secure HTTP operations,
+allowing for requests to `https://` endpoints which are secured with a TLS
+certificate signed by an untrusted certificate authority. Takes no arguments.
+
+By default, the HTTP client will trigger a runtime error if a TLS certificate
+presented by a server is from an untrusted certificate authority.
+
+Do not change this setting unless you are aware of the risks involved.
+Without verification, TLS accepts any certificate presented by the server
+and any host name in that certificate. In this mode, TLS is susceptible to
+man-in-the-middle attacks. This option should only be used for testing or in
+trusted networks with self-signed certificates.
+
+```sentinel
+http.without_certificate_verification().get(url)
+```
+
+### client.certificate_verification
+
+Returns true if the client requires TLS certificates to be verifiable.
+
+## Type: request
+
+### request.url
+
+Returns the request URL as a string.
+
+### request.headers
+
+Returns the configured request headers as a string-keyed map of strings.
+
+### request.body
+
+Returns the configured body as a string.
+
+### request.with_header(key, value)
+
+Returns a `request` with the header `key` (string) set to `value` (string).
+
+Values are overridden on subsequent calls to the same `key`. To specify
+multiple values, use the existing value when setting the new one.
+
+```sentinel
+original = http.request("http://example.hashicorp.com").with_header("Foo", "bar")
+overridden = original.with_header("Foo", "baz")
+multi_value = original.with_header("Foo", original.headers["Foo"] + ",baz")
+
+main = rule {
+ original.headers["Foo"] is "bar" and
+ overridden.headers["Foo"] is "baz" and
+ multi_value.headers["Foo"] is "bar,baz"
+}
+```
+
+### request.with_basic_auth(username, password)
+
+Returns a `request` with the `Authorization` header set to use HTTP Basic
+Authentication with the provided username and password.
+
+Note that with HTTP Basic Authentication the provided username and password
+are encoded, but not encrypted.
+
+### request.with_body(body)
+
+Returns a `request` with the `body` set. This value will then be sent as a body
+as part of the request. Commonly used along with the [`post()`](#client-post-url_or_request) function.
+
+```sentinel
+req = http.request("http://example.hashicorp.com")
+ .with_header("Content-Type", "application/json")
+ .with_body(json.marshal({ "foo": "bar" }))
+
+resp = http.post(req)
+```
+
+## Type: response
+
+### response.status_code
+
+The response status code as an integer, e.g. `200`.
+
+### response.headers
+
+The response headers as a map.
+
+### response.body
+
+The response body as a string. Callers should unmarshal the content to a useful
+representation as necessary based on the content type. For example, if the
+response is in JSON:
+
+```sentinel
+import "http"
+import "json"
+
+# This example endpoint returns {"some_key": true}
+req = http.request("https://example.hashicorp.com/some-json")
+
+resp = json.unmarshal(http.get(req).body)
+main = rule { keys(resp) contains "some_key" and resp["some_key"] is true }
+```
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/imports/index.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/imports/index.mdx
new file mode 100644
index 0000000000..e45bf81b81
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/imports/index.mdx
@@ -0,0 +1,18 @@
+---
+page_title: Sentinel Language - Standard Imports
+sidebar_current: docs-imports
+description: >-
+ Standard Imports are the built-in imports that are available to all Sentinel
+ policies.
+layout: docs
+---
+
+# Standard Imports
+
+Standard Imports are the built-in [imports](/sentinel/language/imports)
+that are available to all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in import.
+
+Sentinel-embedded applications can choose to allow or deny certain
+standard imports. Please reference the documentation for the Sentinel-enabled
+application you're using to determine if all standard imports are available.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/imports/json.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/imports/json.mdx
new file mode 100644
index 0000000000..3355458f14
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/imports/json.mdx
@@ -0,0 +1,31 @@
+---
+page_title: 'Import: json'
+sidebar_current: docs-imports-json
+description: The json import enables a Sentinel policy to parse and access a JSON document.
+layout: docs
+---
+
+# Import: json
+
+The json import enables a Sentinel policy to parse and access a JSON
+document.
+
+### json.unmarshal(obj)
+
+Unmarshals the JSON object `obj` into a native Sentinel structure.
+
+All native JSON types can be represented perfectly as Sentinel native types.
+
+```sentinel
+// Typically the input for this would come from an external source.
+config = json.unmarshal("{ \"foo\": 42 }")
+config.foo // 42
+config.bar // undefined (as usual for accessing a non-existent map key)
+```
+
+### json.marshal(obj)
+
+Marshals the Sentinel object `obj` into a JSON object encoded as a string.
+
+Functions and `undefined` cannot be natively encoded as JSON. If values of
+either type are found in the structure, this will return undefined.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/imports/runtime.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/imports/runtime.mdx
new file mode 100644
index 0000000000..6493bbbb22
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/imports/runtime.mdx
@@ -0,0 +1,16 @@
+---
+page_title: 'Import: runtime'
+sidebar_current: docs-imports-runtime
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: runtime
+
+The runtime import contains various information about the Sentinel runtime. It
+can be used to get information about the runtime as it's embedded in the CLI or
+a specific integration, such as validating a specific runtime version.
+
+### runtime.version
+
+Returns the version of Sentinel within this implementation.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/imports/sockaddr.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/imports/sockaddr.mdx
new file mode 100644
index 0000000000..a6842ba5f0
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/imports/sockaddr.mdx
@@ -0,0 +1,41 @@
+---
+page_title: 'Import: sockaddr'
+sidebar_current: docs-imports-sockaddr
+description: The sockaddr import makes working with IP addresses easy.
+layout: docs
+---
+
+# Import: sockaddr
+
+The sockaddr import makes working with IP addresses easy.
+
+### sockaddr.is_contained(outer, inner)
+
+The is_contained function returns true if `inner` is an address that
+is contained within `outer`.
+
+```sentinel
+sockaddr.is_contained("192.168.0.0/24", "192.168.0.32") // true
+sockaddr.is_contained("192.168.0.0/24", "192.174.1.1") // false
+```
+
+### sockaddr.is_equal(addr1, addr2)
+
+The is_equal function returns true if addr1 is equal to addr2.
+
+```sentinel
+sockaddr.is_equal("192.168.12.24", "192.168.12.24") // true
+```
+
+### sockaddr.is_ipv4(addr)
+
+The is_ipv4 function returns true if addr is an IPv4 address.
+
+```sentinel
+sockaddr.is_ipv4("192.168.12.24") // true
+sockaddr.is_ipv4("2001:0db8:85a3:0000:0000:8a2e:0370:7334") // false
+```
+
+### sockaddr.is_ipv6(addr)
+
+The is_ipv6 function returns true if addr is an IPv6 address.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/imports/strings.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/imports/strings.mdx
new file mode 100644
index 0000000000..823c5d8f07
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/imports/strings.mdx
@@ -0,0 +1,91 @@
+---
+page_title: 'Import: strings'
+sidebar_current: docs-imports-strings
+description: The strings import exposes common string operations.
+layout: docs
+---
+
+# Import: strings
+
+The strings import exposes common string operations.
+
+### strings.has_prefix(s, prefix)
+
+Returns true if `s` starts with `prefix`.
+
+```sentinel
+strings.has_prefix("billing-id", "billing-") // true
+strings.has_prefix("bill-id", "billing-") // false
+```
+
+### strings.has_suffix(s, suffix)
+
+Returns true if `s` ends with `suffix`.
+
+```sentinel
+strings.has_suffix("billing-id", "id") // true
+strings.has_suffix("billing-name", "id") // false
+```
+
+### strings.join(a, sep)
+
+Joins a list with the string supplied by `sep`.
+
+Multi-dimensional lists are supported, and non-string primitive
+types will be converted to their string equivalents. Maps and
+other complex non-list types are not supported.
+
+```sentinel
+strings.join(["foo", "bar", "baz"], ".") // "foo.bar.baz"
+strings.join([["foo", "bar"], "baz"], ".") // "foo.bar.baz"
+strings.join(["a", 1, 1.01, true], ",") // "a,1,1.01,true"
+```
+
+### strings.trim_prefix(s, prefix)
+
+Trim the prefix from the string `s`. If the string doesn't have the
+prefix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_prefix("billing-id", "billing-") // "id"
+strings.trim_prefix("bill-id", "billing-") // "bill-id"
+```
+
+### strings.trim_suffix(s, suffix)
+
+Trim the suffix from the string `s`. If the string doesn't have the
+suffix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_suffix("billing-id", "-id") // "billing"
+strings.trim_suffix("bill-id", "-foo") // "bill-id"
+```
+
+### strings.to_lower(s)
+
+Lowercase the string s.
+
+```sentinel
+strings.to_lower("FoO") // "foo"
+```
+
+### strings.to_upper(s)
+
+Uppercase the string s.
+
+```sentinel
+strings.to_upper("FoO") // "FOO"
+```
+
+### strings.split(s, sep)
+
+Split the string 's' via a substring 'sep'. If 'sep' is not contained in
+'s', the return value will be a single-element list containing only 's'.
+As a special-case, if the input is already a list, it will be returned
+as-is. This allows calling the split function when not knowing if the input
+is already a list or not.
+
+```sentinel
+strings.split("foo,bar,baz", ",") // ["foo", "bar", "baz"]
+strings.split(["foo", "bar", "baz"], ",") // ["foo", "bar", "baz"]
+```
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/imports/time.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/imports/time.mdx
new file mode 100644
index 0000000000..3a675e2bbd
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/imports/time.mdx
@@ -0,0 +1,257 @@
+---
+page_title: 'Import: time'
+sidebar_current: docs-imports-time
+description: The time import provides access to the execution time and time functions.
+layout: docs
+---
+
+# Import: time
+
+The time import provides access to the execution time and time functions.
+
+During the execution of a policy the time is fixed to the moment of
+execution. This gives the illusion that a policy instantly executes at a
+single moment of time.
+
+The import uses Coordinated Universal Time (UTC).
+
+As an example of this, if a policy accessed `time.now.second` multiple times,
+for each access the same value would be returned even if they are several seconds apart.
+This is the execution `timespace`, which is mapped to `time.now`.
+
+It is also possible to run functions on a custom time by using the
+`time.load()` function to create a new timespace from the specified value.
+See the documentation for available actions on timespaces.
+
+Times specified as the reference time or via the `time.load()` function can
+be specified in a `timeish` fashion. This is either one of:
+
+- The number of seconds since the Unix epoch (Thursday, 1 January 1970,
+ 00:00:00 UTC),
+- A timestamp matching the format outlined in RFC3339, either in the
+ format `2006-01-02T15:04:05+07:00`, which includes a timezone offset, or
+ `2006-01-02T15:04:05Z`, which indicates UTC.
+
+### time.now
+
+Create a `timespace` locked either to execution time or, if set, the
+reference time. In a given execution, this will always produce the same
+value.
+
+```sentinel
+time.now // {"day": 17, "hour": 23, "minute": 19, "month": 11 ... }
+```
+
+### time.load(timeish)
+
+Create a timespace using the given value. Please see the import
+documentation for valid values for "timeish".
+
+```sentinel
+time.load("2019-11-16T01:20:30Z") // {"day": 16, "hour": 1, "minute": 20, "month": 11 ... }
+```
+
+### time.nanosecond
+
+A nanosecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.nanosecond * 1) // 2019-11-16T01:20:30.000000001Z
+```
+
+### time.microsecond
+
+A microsecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.microsecond * 1) // 2019-11-16T01:20:30.000001Z
+```
+
+### time.millisecond
+
+A millisecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.millisecond * 1) // 2019-11-16T01:20:30.001Z
+```
+
+### time.second
+
+A second duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.second * 1) // 2019-11-16T01:20:31Z
+```
+
+### time.minute
+
+A minute for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.minute * 1) // 2019-11-16T01:21:30Z
+```
+
+### time.hour
+
+An hour duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+## Type: timespace
+
+### timespace.hour
+
+The hour from 0 to 23.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").hour // 1
+```
+
+### timespace.minute
+
+The minute from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").minute // 20
+```
+
+### timespace.second
+
+The second from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").second // 30
+```
+
+### timespace.day
+
+The day of the month, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").day // 16
+```
+
+### timespace.month
+
+The month of the year as an integer, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month // 11
+```
+
+### timespace.month_name
+
+The name of the month of the year, in English. Example: `January`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month_name // November
+```
+
+### timespace.weekday
+
+The weekday as an integer, where 0 is Sunday and 6 is Saturday.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday // 6
+```
+
+### timespace.weekday_name
+
+The name of the day of the week, in English. Example: `Sunday`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday_name // Saturday
+```
+
+### timespace.year
+
+The year as an integer.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").year // 2019
+```
+
+### timespace.unix
+
+The number of seconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix // 1573867230
+```
+
+### timespace.unix_nano
+
+The number of nanoseconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix_nano // 1573867230000000000
+```
+
+### timespace.zone
+
+The timezone offset, in seconds. This can be a negative number to indicate
+time west of UTC.
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone // -28800
+```
+
+### timespace.zone_string
+
+The timezone offset, in the format `+HH:MM` (example: `-08:00` for PST).
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone_string // -08:00
+```
+
+### timespace.before(timeish_or_timespace)
+
+Check if the time in the timespace is before the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").before("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.after(timeish_or_timespace)
+
+Check if the time in the timespace is after the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").after("2019-11-15T01:20:30Z") // true
+```
+
+### timespace.equal(timeish_or_timespace)
+
+Check if two times are equivalent
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").equal("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.add(duration)
+
+Add a duration in nanoseconds to the time in the timespace and return a new timespace. The
+duration can be negative. The duration should be specified as an integer
+multiplied with the correct unit.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+### timespace.sub(timeish_or_timespace)
+
+Subtract a time from the time in the timespace and return a duration in nanoseconds.
+
+```sentinel
+// Gives a duration of 3 hours.
+duration = time.load(
+ "2019-11-16T01:20:30Z",
+).sub("2019-11-16T04:20:30Z")
+
+// Returns 2019-11-16T01:20:30Z
+time.load(
+ "2019-11-16T04:20:30Z",
+).add(duration)
+```
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/imports/types.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/imports/types.mdx
new file mode 100644
index 0000000000..fcfdbd2ae5
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/imports/types.mdx
@@ -0,0 +1,35 @@
+---
+page_title: 'Import: types'
+sidebar_current: docs-imports-types
+description: The types import enables a Sentinel policy to parse an object's type.
+layout: docs
+---
+
+# Import: types
+
+The types import enables a Sentinel policy to parse an object's type.
+
+### types.type_of(obj)
+
+Returns the object's type as a string
+
+```sentinel playground
+import "types"
+
+// Typically the inputs would come from an external source.
+is_boolean = rule { types.type_of(true) is "bool" }
+is_string = rule { types.type_of("Hello!") is "string" }
+is_integer = rule { types.type_of(42) is "int" }
+is_float = rule { types.type_of(42.123) is "float" }
+is_null = rule { types.type_of(null) is "null" }
+is_undefined = rule { types.type_of(undefined) is "undefined" }
+
+main = rule {
+ is_boolean and
+ is_string and
+ is_integer and
+ is_float and
+ is_null and
+ is_undefined
+}
+```
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/imports/units.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/imports/units.mdx
new file mode 100644
index 0000000000..e2ad3442f9
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/imports/units.mdx
@@ -0,0 +1,39 @@
+---
+page_title: 'Import: units'
+sidebar_current: docs-imports-units
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: units
+
+The units import provides access to quick calculations for various byte
+units.
+
+Note that this import uses base-2 terminology:
+
+- One kilobyte is 1024 bytes
+- One megabyte is 1024^2 = 1048576 bytes
+- One gigabyte is 1024^3 = 1073741824 bytes
+- One terabyte is 1024^4 = 1099511627776 bytes
+- One petabyte is 1024^5 = 1125899906842624 bytes
+
+### units.kilobyte
+
+The base-2 representation of a kilobyte (1024 bytes).
+
+### units.megabyte
+
+The base-2 representation of a megabyte (1048576 bytes).
+
+### units.gigabyte
+
+The base-2 representation of a gigabyte (1073741824 bytes).
+
+### units.terabyte
+
+The base-2 representation of a terabyte (1099511627776 bytes).
+
+### units.petabyte
+
+The base-2 representation of a petabyte (1125899906842624 bytes).
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/imports/version.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/imports/version.mdx
new file mode 100644
index 0000000000..59366c1087
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/imports/version.mdx
@@ -0,0 +1,151 @@
+---
+page_title: 'Import: version'
+sidebar_current: docs-imports-version
+description: |-
+ The version import provides functions for parsing versions and version constraints,
+ and verifying versions against a set of constraints.
+layout: docs
+---
+
+# Import: version
+
+The version import provides functions for parsing versions and version constraints,
+and verifying versions against a set of constraints.
+
+Versions are created through the `new` function, which accepts a string type in dotted-tri format (i.e. 1.0.0-alpha.1+001) that can represent a version.
+
+### version.new(v)
+
+Constructs a version from string value.
+
+```sentinel
+version.new("1.0.0-alpha.1+001")
+```
+
+### version.major
+
+An integer representation of the Major number for a given version .
+
+```sentinel
+version.new("1.0.0-alpha.1+001").major // 1
+```
+
+### version.minor
+
+An integer representation of the Minor number for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").minor // 0
+```
+
+### version.patch
+
+An integer representation of the Patch number for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").patch // 0
+```
+
+### version.prerelease
+
+A string representation of the Prerelease for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").prerelease // "alpha.1"
+```
+
+### version.metadata
+
+A string representation of the Metadata for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").metadata // "001"
+```
+
+### version.version
+
+A string representation of the version including pre-release and metadata information.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").version // "1.0.0-alpha.1+001"
+```
+
+### version.greater_than(v)
+
+Tests that the version is greater than a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").greater_than("0.12.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").gt("1.0.1-alpha.1+001") // False
+```
+
+### version.greater_than_or_equals(v)
+
+Tests that the version is greater than or equal to a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").greater_than_or_equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").gte("1.0.1-alpha.1+001") // False
+```
+
+### version.less_than(v)
+
+Tests that the version is less than a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").less_than("1.0.1-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").lt("0.12.0-alpha.1+001") // False
+```
+
+### version.less_than_or_equals(v)
+
+Tests that the version is less than or equal to a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").less_than_or_equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").lte("0.12.0-alpha.1+001") // False
+```
+
+### version.less_than_or_equals(v)
+
+Tests that the version equals a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").eq("0.12.0-alpha.1+001") // False
+```
+
+### version.compare(v)
+
+Compares one version with a given version. Compare returns -1, 0, or 1 if the version is less, equal, or greater than the other version, respectively.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").compare("0.12.0-alpha.1+001") // -1
+version.new("1.0.0-alpha.1+001").compare("1.0.0-alpha.1+001") // 0
+version.new("0.12.0-alpha.1+001").cmp("1.0.0-alpha.1+001") // 1
+```
+
+### version.satisfies(v, x)
+
+Tests that a version adheres to one or more version constraints. Satisfies supports the following restriction operators:
+
+- =
+- !=
+- \>
+- <
+- \>=
+- <=
+- ~>
+
+Satisfies supports the following usage scenarios:
+
+- A constraint with a pre-release can only match a pre-release version that contains the same major, minor and patch identifiers.
+- A constraint without a pre-release can only match a version without a pre-release.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").satisfies("> 1.0.0-alpha.1+001") // false
+version.new("1.0.0-alpha.1+001").satisfies(">= 1.0.0-alpha.1+001") // true
+version.new("1.0.0-alpha.1+001").satisfies(">= 1.0.0-alpha.1+001, < 1.0.0-beta+001") // true
+version.new("1.0.0").sat("= 1.0.0") // true
+version.new("0.12.7").sat("~> 0.12.0") // true
+```
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/index.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/index.mdx
new file mode 100644
index 0000000000..4497b40b83
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/index.mdx
@@ -0,0 +1,31 @@
+---
+layout: 'docs'
+page_title: 'Documentation'
+sidebar_current: 'docs-home'
+description: |-
+ Sentinel is a language and framework for policy built to be embedded in existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+[introduction](/sentinel/intro) to understand what Sentinel is, how it
+compares to other software, and more.
+
+
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/language/arithmetic.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/language/arithmetic.mdx
new file mode 100644
index 0000000000..06b1256a1b
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/language/arithmetic.mdx
@@ -0,0 +1,67 @@
+---
+page_title: Sentinel Language - Arithmetic
+sidebar_current: docs-language-arith
+description: >-
+ Sentinel supports arithmetic operators for integers and floats. Sentinel
+ supports sum, difference, product, quotient, and remainder.
+layout: docs
+---
+
+# Language: Arithmetic
+
+Sentinel supports arithmetic operators for integers and floats. Sentinel
+supports sum, difference, product, quotient, and remainder.
+
+```plaintext
++ sum
+* difference
+* product
+/ quotient
+% remainder
+```
+
+These operators work in a typical infix style:
+
+```sentinel
+4 + 8 // 12
+8 * 2 // 16
+8 / 4 // 2
+8 / 5 // 1
+8 % 5 // 3
+```
+
+## Order of Operations
+
+Arithmetic follows a standard mathematical order of operations.
+Grouping with parentheses can be used to affect ordering.
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+(4 + 5) * 2 // 18
+```
+
+A full table of operator precendence can be found on the
+[boolean expressions page](/sentinel/language/boolexpr). This
+shows how arithmetic operators relate to other operators.
+
+## Integer Division
+
+Integer division with a remainder rounds down to the nearest integer.
+Example: `8 / 3 is 2`.
+
+If the divisor is zero, an error occurs.
+
+## Mixed Numeric Operations
+
+Mixed numeric operations between integer and floating-point values are
+permitted. The result is a floating-point operation with the integer converted
+to a floating-point value for purposes of calculation.
+
+```sentinel
+import "types"
+
+a = 1.1 + 1 // 2.1
+types.type_of(a) // "float"
+```
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/language/boolexpr.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/language/boolexpr.mdx
new file mode 100644
index 0000000000..f1c518c3bf
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/language/boolexpr.mdx
@@ -0,0 +1,225 @@
+---
+page_title: Sentinel Language - Boolean Expressions
+sidebar_current: docs-language-boolexpr
+description: >-
+ Boolean expressions are expressions that evaluate to a boolean value from the
+ result of a combination of one or more comparisons and logical operators.
+layout: docs
+---
+
+# Language: Boolean Expressions
+
+Boolean expressions are expressions that evaluate to a boolean value
+from the result of a combination of one or more comparisons and logical
+operators. Boolean expressions form the basis of policy since policy can be broken
+down to a set of logical decisions that turn into true or false.
+
+A single boolean expression is the body of a [rule](/sentinel/language/rules).
+Additionally, boolean expressions are used with [conditionals](/sentinel/language/conditionals),
+and more.
+
+## Order of Operations
+
+The precedence of operators is shown below. Operators with higher
+precedence values (larger numbers) are evaluated first. Operators with
+the same precedence value are evaluated left-to-right.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Examples of this precedence are shown below:
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+```
+
+## Logical Operators
+
+Logical operators are used to change or combine logical expressions.
+There are three binary operators `and`, `or`, and `xor`. There is
+a single unary operator `not` (which can also be specified as `!`).
+
+The binary operators require two boolean operands and the unary
+operator requires a single boolean operand. In both case, the operands
+are values or expressions that are boolean types.
+
+Below is a basic explanation of the logical operators directly from
+the specification:
+
+```sentinel
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+Using parentheses to group boolean expressions, you can combine
+boolean expressions to become more complex or affect ordering:
+
+```sentinel
+(p and q) or r
+p and (q or r)
+```
+
+## Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+For any comparison, the two operands must be equivalent types. The one
+exception are integers and floats. When an integer is compared with
+a float, the integer is promoted to a floating point value.
+
+The available comparison operators are:
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+The behavior of `is` with `==` and `is not` with `!=` is identical.
+
+Example comparisons:
+
+```sentinel
+name is "Mitchell"
+idnumber > 42
+idnumber <= 12
+```
+
+Using the logical operators, these can be combined:
+
+```sentinel
+name is "Mitchell" and idnumber > 42
+```
+
+The language specification provides more detail on exactly
+[how comparison behaves](/sentinel/language/spec#comparison-operators).
+
+## Set Operators
+
+The set operators `contains` and `in` test for inclusion in a collection
+(a list or map).
+
+Set operators may 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.
+
+`contains` tests if the left-hand collection contains the right-hand value.
+`in` tests if the right-hand collection contains the left-hand value. For
+maps, "contains" looks at the keys, not the values.
+
+Examples:
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+```
+
+## Matches Operator
+
+The `matches` operator tests if a string matches a regular expression.
+The `matches` operator can be negated by prefixing the operator with
+`not`, such as `not matches`.
+
+The left-hand value is the string to test. The right-hand value is
+a string value representing a regular expression. The syntax of the regular
+expression is the same general syntax used by Python, Ruby, and other languages.
+The precise syntax accepted is the syntax accepted by RE2.
+
+Regular expressions are not anchored by default; any anchoring must be
+explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+## Any, All Expressions
+
+`any` and `all` expressions allow testing that any or all elements of a
+collection match some boolean expression. The result of an `any` and `all`
+expression is a boolean.
+
+`any` returns the boolean value `true` if any value in the collection expression
+results in the body expression evaluating to `true`. If the body expression
+evalutes to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression
+result in the body expression evaluating to `true`. If any value in the
+collection expression result in the body expression evaluating to `false`,
+the `all` expression returns `false`.
+
+For empty collections, `any` returns `false` and `all` returns `true`.
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" }
+any group.tasks as t { t.driver is "vmware" }
+```
+
+Since `any` and `all` expressions are themselves boolean expressions, they
+can be combined with other operators:
+
+```sentinel
+any ["a", "b"] as char { char is "a" } or
+other_value is "another"
+```
+
+## Emptiness Comparison
+
+The expressions `is empty` and `is not empty` provide a convenience
+method for determining the emptiness of a value. It supports the same types
+as the [built-in length](/sentinel/functions/length), collections and strings.
+
+```sentinel
+[] is empty // true
+[] is not empty // false
+["foo"] is empty // false
+["foo"] is not empty // true
+undefined is empty // undefined
+undefined is not empty // undefined
+```
+
+## Defined Comparison
+
+The expressions `is defined` and `is not defined` provide a convenience
+method for determining if a value has been defined. In other words, any value
+other than `undefined` can be considered as `defined`.
+
+```sentinel
+[] is defined // true
+4 is defined // true
+true is defined // true
+{} is defined // true
+undefined is defined // false
+[] is not defined // false
+4 is not defined // false
+true is not defined // false
+undefined is not defined // true
+```
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/language/collection-operations.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/language/collection-operations.mdx
new file mode 100644
index 0000000000..41da8f3584
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/language/collection-operations.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Sentinel Language - Collection Operations
+sidebar_current: docs-language-collection-operations
+description: |-
+ Operations for interacting with collections (list, map).
+layout: docs
+---
+
+# Language: Collection Operations
+
+Collection operations are expressions that are performed on a list or map to
+return a variation of the initial data.
+
+At the moment, `filter` is the only collection operation available.
+
+## Filter Expression
+
+`filter` is a [quantifier
+expression](/sentinel/language/spec#quantifier-expressions-any-all-filter-map) that
+returns a subset of the provided collection. Only elements whose filter body
+returns true will be returned. If any of the elements filter body returns
+undefined, the final result will be undefined.
+
+Filter uses the same syntax as the `any` and `all` [boolean
+expressions](/sentinel/language/boolexpr#any-all-expressions):
+
+```sentinel
+filter list as value { condition } // Single-iterator, list
+filter list as idx, value { condition } // Double-iterator, list
+
+filter map as key { condition } // Single-iterator, map
+filter map as key, value { condition } // Double-iterator, map
+```
+
+Examples:
+
+```sentinel
+l = [1, 1, 2, 3, 5, 8]
+evens = filter l as v { v % 2 is 0 } // [2, 8]
+
+m = { "a": "foo", "b": "bar" }
+matched_foo = filter m as _, v { v is "foo" } // { "a": "foo" }
+```
+
+## Map Expression
+
+`map` is a [quantifier
+expression](/sentinel/language/spec#quantifier-expressions-any-all-filter-map) that
+returns a list, regardless of the input collection type. Each element within the
+input collection is evaluted according to the map expression body and appended
+to the result.
+
+```sentinel
+l = [1, 2]
+r = map l as v { v % 2 } // [false, true]
+
+m = { "a": "foo", "b": "bar" }
+r = map m as k, v { v } // ["foo", "bar"]
+```
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/language/conditionals.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/language/conditionals.mdx
new file mode 100644
index 0000000000..1736795dbb
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/language/conditionals.mdx
@@ -0,0 +1,161 @@
+---
+page_title: Sentinel Language - Conditionals
+sidebar_current: docs-language-conditional
+description: |-
+ Conditional statements allow your policy to behave differently depending on a condition.
+layout: docs
+---
+
+# Language: Conditionals
+
+Conditional statements allow your policy to behave differently depending
+on a condition.
+
+Conditional statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## If Statements
+
+`if` statements only execute their bodies if a condition is met.
+The syntax of an `if` statement is:
+
+```sentinel
+if condition {
+ // ... this is executed if condition is true
+}
+```
+
+The `condition` must result in a boolean, such as by calling a function
+or evaluating a [boolean expression](/sentinel/language/boolexpr). If
+the `condition` is `true`, the body (within the `{}`) is executed. Otherwise,
+the body is skipped.
+
+Examples:
+
+```sentinel
+// This would execute the body
+value = 12
+if value is 18 {
+ print("condition met")
+}
+
+// Direct boolean values can be used
+value = true
+if value {
+ print("condition met")
+}
+
+// This would not execute the body since the boolean expression will
+// result in undefined.
+value = {}
+if value["key"] > 12 {
+ print("condition met")
+}
+```
+
+### Else, Else If
+
+An `else` clause can be given to an `if` statement to execute a body
+in the case the condition is _not met_. By putting another `if` statement
+directly after the `else`, multiple conditions can be tested for.
+The syntax is:
+
+```sentinel
+if condition {
+ // ...
+} else {
+ // ...
+}
+
+if condition {
+ // ...
+} else if other_condition {
+ // ...
+} else {
+ // ...
+}
+```
+
+### Scoping
+
+The body of an `if` statement does not create a new
+[scope](/sentinel/language/scope). Any variables assigned within the body
+of an if statement will modify the scope that the `if` statement itself
+is in.
+
+Example:
+
+```sentinel
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+```sentinel
+a = 18
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+## Case Statements
+
+`case` statements are a selection control mechanism that execute a clause based
+on matching expressions. It is worth noting that the expression for `case` is
+optional. When no expression is provided, it defaults the expression to `true`.
+Additionally, the order of clauses is important, as they are evaluated from top
+to bottom, executing the first match.
+The syntax of a case statement is:
+
+```sentinel
+case expression {
+ when clause_expression:
+ // executed when clause_expression and expression are equal
+ else:
+ // executed if no clause matches expression
+}
+```
+
+### When Clause
+
+Any clause that has an expression for comparison must use the `when` keyword.
+It accepts a list of expressions, seperated by a `,`.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+}
+```
+
+```sentinel
+case {
+ when x > 40:
+ return true
+}
+```
+
+### Else Clause
+
+The `else` keyword allows for capturing any expressions that have no matching
+`when` clause.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+ else:
+ return false
+}
+```
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/language/functions.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/language/functions.mdx
new file mode 100644
index 0000000000..8214078b6b
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/language/functions.mdx
@@ -0,0 +1,229 @@
+---
+page_title: Sentinel Language - Functions
+sidebar_current: docs-language-functions
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Functions
+
+Functions allow you to create reusable code to perform computations.
+
+Below is a trivial example function that doubles a number and shows
+the main rule using the function:
+
+```sentinel playground
+double = func(x) {
+ return x * 2
+}
+
+main = rule { double(12) is 24 }
+```
+
+Functions may contain any expressions,
+[conditionals](/sentinel/language/conditionals),
+and [loops](/sentinel/language/loops). They must return a value
+at the end of their execution. If no reasonable return value exists,
+the function should return [undefined](/sentinel/language/undefined).
+
+## Function Types
+
+### Named Functions
+
+-> **NOTE:** Named functions must be created within the [package scope](/sentinel/language/scope#package-scope).
+
+Named functions are declared using the `func` keyword as its own statement.
+They provide a safe method of creating functions and have additional
+restrictions that do not apply to anonymous functions.
+
+Firstly, named functions cannot be re-assigned, and also cannot use a name
+that is already used elsewhere. For instance, the below example will error due
+to the attempt to reassign the named function identifier to a new value:
+
+```sentinel
+func sum(a, b) {
+ return a + b
+}
+
+sum = 4
+```
+
+Additionally, the following will error due to the named function attempting
+to make use of an already assigned identifier:
+
+```sentinel
+sum = 4
+
+func sum(a, b) {
+ return a + b
+}
+```
+
+Named functions are helpful for policy authors to declare critical functions
+whose value or implementation should not be changed.
+
+### Anonymous Functions
+
+An anonymous function is created by assigning a variable to a `func`. The
+variable can be reassigned at any time including to different value types.
+Anonymous functions are helpful for use cases like closures, where a function
+can return another function.
+
+```sentinel
+func makeAdder(a) {
+ return func(b) {
+ return a + b
+ }
+}
+```
+
+## Creating a Function
+
+A function can have zero or more parameters. These parameters are specified
+within parentheses `()` separated by commas. If a function has no parameters,
+the parentheses must still be present.
+
+A function must terminate with a `return` statement to return a value back to
+the caller. Only a single value can be returned. The type of a return can
+vary between return statements.
+
+Anonymous function example:
+
+```sentinel
+add1 = func(x) { return x + 1 }
+```
+
+Named function example:
+
+```sentinel
+func add1(x) {
+ return x + 1
+}
+```
+
+Both examples create a function that adds 1 to the parameter `x`.
+
+## Calling a Function
+
+Functions are called by accessing their name followed by parentheses with
+a list of comma-separated values for the parameters. If the function takes no
+parameters, an empty set of parentheses must still be used to denote a
+function call.
+
+To call the function in the example above:
+
+```sentinel
+x = 1
+y = add1(x)
+```
+
+## Scoping
+
+The body of a `func` creates a new [scope](/sentinel/language/scope).
+
+The parent scope is the scope in which the function is created. This allows
+for the creation of functions within functions that can access their outer
+scopes.
+
+Example:
+
+```sentinel
+f = func() {
+ a = 42
+ print(a) // 42
+ return undefined
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+f = func() {
+ a = 42
+ return undefined
+}
+
+print(a) // 18
+```
+
+And below is an example of creating a function in a function which uses
+outer values:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double()
+}
+
+print(f()) // 84
+```
+
+A more complex example below shows how scoping works when passing
+functions around as arguments or results:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double
+}
+
+double = f()
+print(double()) // 84
+```
+
+## Pass By Value
+
+The parameters to a function are passed by value, but not deep copied.
+This means that elements of collections can still be modified but the
+root value cannot be changed.
+
+Example:
+
+```sentinel
+f = func(x) {
+ x = "value"
+ return x
+}
+
+x = "outside"
+f(x)
+print(x) // "outside"
+```
+
+```sentinel
+f = func(x) {
+ append(x, "value")
+ return x
+}
+
+x = []
+f(x)
+print(x) // ["value"]
+```
+
+## Recursion
+
+A function is allowed to call other functions, including itself.
+This can be used to implement recursion. For example, the fibonacci sequence
+is implemented below:
+
+```sentinel
+fib = func(x) {
+ if x <= 0 {
+ return undefined
+ }
+
+ if x == 1 {
+ return 1
+ } else {
+ return x + fib(x - 1)
+ }
+}
+```
+
+Note that this example also shows using
+[undefined](/sentinel/language/undefined)
+as a return value in cases where a function has undefined behavior.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/language/imports.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/language/imports.mdx
new file mode 100644
index 0000000000..0ed1541b85
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/language/imports.mdx
@@ -0,0 +1,116 @@
+---
+page_title: Sentinel Language - Imports
+sidebar_current: docs-language-imports
+description: >-
+ Imports enable a Sentinel policy to access reusable libraries and external
+ data and functions.
+layout: docs
+---
+
+# Language: Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external
+data and functions. The exact list of available imports is dependent on the
+host system. Host systems may also make the available imports configurable
+via plugins.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that
+the import `calendar` allows access to engineer calendars. This import
+only exists for the purpose of this example and at the time of writing is
+not a real available import.
+
+```sentinel
+import "calendar"
+
+// Get the calendar for Bob for today
+bob_calendar = calendar.for("bob").today
+
+// Allow this policy to pass if Bob is not on vacation.
+main = rule { not bob_calendar.has_event("vacation") }
+```
+
+This example shows an import being used to deny a behavior if Bob is on
+vacation. Hopefully real policies do not depend on a single person being
+in the office, but the example shows the power of imports.
+
+In addition to consuming imports, you can also
+[extend Sentinel by writing custom imports](/sentinel/extending)
+This allows you to add any arbitrary behavior to your Sentinel-protected
+systems.
+
+## Importing
+
+Whether accessing a built-in library or an external plugin, the syntax
+for an import is the same:
+
+```sentinel
+import "name"
+```
+
+This adds the import with the name `name`. It can be referenced using
+the identifier `name`. For example, if the import above exposed first
+and last name data, you could access it via `name.first` or `name.last`.
+
+You can shadow imports by assigning a variable. In the example below,
+the import `name` becomes unavailable within the function because it is
+shadowed by a variable of the same name.
+
+Shadowing an import is not the same as overwriting a variable. Imports
+are not part of the [scope](/sentinel/language/scope), meaning that
+the shadow only exists for the scope it is created within. For everything
+else, the import works even after it is shadowed. The example below shows that
+as well.
+
+```sentinel
+import "name"
+
+f = func() {
+ name = "jane"
+ return name
+}
+
+print(f()) // "jane"
+print(name.first) // "bob"
+```
+
+## Aliases
+
+An import can be aliased to another name using the syntax below:
+
+```sentinel
+import "name" as other
+```
+
+This would make the import "name" available as `other`.
+
+An import may only be imported once, regardless of aliases. The following
+would be **invalid** and would result in an error:
+
+```sentinel
+import "name" as one
+import "name" as two
+```
+
+## Accessing Import Data
+
+Imports are accessed with dot-separated identifiers, such as `name.first` or
+`name.stage.first`. These are called _selector expressions_. An import may
+return nested data that further can be accessed via selector expressions.
+
+In the first example on this page with the "calendar" import, we see this:
+
+```sentinel
+import "calendar"
+
+a = calendar.for("bob") // selector expression calendar.for
+b = a.today // selector expression on the result
+c = b.vacation // accessing further nested data
+```
+
+The exact structure of an import is dependent on the import. Please reference
+the documentation for an import to learn more.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/language/index.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/language/index.mdx
new file mode 100644
index 0000000000..7f588c94cd
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/language/index.mdx
@@ -0,0 +1,114 @@
+---
+page_title: Sentinel Language
+sidebar_current: docs-language-basics
+description: |-
+ Sentinel policies are written using the Sentinel language. This language
+ is easy to learn and easy to write. You can learn the Sentinel language
+ and be productive within an hour. Learning Sentinel doesn't require any
+ formal programming experience.
+layout: docs
+---
+
+# Sentinel Language
+
+Sentinel policies are written using the Sentinel language. This language
+is easy to learn and easy to write. You can learn the Sentinel language
+and be productive within an hour. Learning Sentinel doesn't require any
+formal programming experience.
+
+This language guide will contain many details that are not necessary to
+be productive with Sentinel or are targeted to those who are looking for
+exact information about the language (such as what types of line endings are
+allowed). You can safely ignore these sentences.
+
+You may also view the
+[official language specification](/sentinel/language/spec)
+This is a specific and detailed document on the syntax and behavior of
+the language primarily intended for implementation creators and to disambiguate
+the language.
+
+## Simplest Example
+
+The example below is about the simplest practical example of Sentinel.
+It is reasonable to imagine this as a realistic policy. This shows that
+in most cases, Sentinel will be extremely simple:
+
+```sentinel
+main = rule { request.method is "GET" and request.headers contains "X-Key" }
+```
+
+## Files
+
+Sentinel policies are single files that end in the `.sentinel` file extension.
+There is currently no built-in mechanism to Sentinel for merging multiple
+files. This is purposefully done to make Sentinel policies easy to submit
+to systems that support Sentinel policies.
+
+Sentinel policy files must be UTF-8 encoded and can end in both
+Unix (LF) or Windows (CRLF) line breaks. When running the
+[auto-formatter](#),
+line endings will always use Unix line breaks.
+
+## Ordering
+
+Sentinel policies are executed top-down. For example:
+
+```sentinel
+a = 1 // a = 1 here
+b = a + 1 // b = 2 here
+a = 3 // a = 3, b = 2
+```
+
+In this example, the value of `a` and `b` is shown at each line. Since Sentinel
+executes values top-down, the final value of `a` is 3 and `b` is 2. `b` _does
+not_ become 4.
+
+## Main
+
+Sentinel expects there to be a `main` [rule](/sentinel/language/rules).
+The value of this rule is the result of the entire policy.
+
+The result of the policy depends on the evaluated contents of the `main` rule.
+For booleans, a policy passes on a `true` value, and fails on a `false` value.
+Other types generally follow a zero or zero-length pattern for determining
+success.
+
+| Type | Passing Condition | Failing Condition |
+| ------- | ----------------- | -------------------------- |
+| Boolean | `true` | `false` |
+| String | `""` | Any non-zero length string |
+| Integer | `0` | Any non-zero value |
+| Float | `0.0` | Any non-zero value |
+| List | `[]` | Any non-zero length list |
+| Map | `{}` | Any non-zero length map |
+
+A value of main that falls outside of the above types will result in a policy
+error. As a special case, if `main` evaluates to an undefined value, the error
+message will indicate as such, with a reference to where the undefined value was
+encountered.
+
+## More Complex Example
+
+The simple example above is a full working example. In our experience with
+Sentinel, many policies can be representing using this simple form. However,
+to show more features of the language, a more complex example is shown below.
+This example is also a realistic example of what Sentinel may be used for.
+
+```sentinel
+import "units"
+
+memory = func(job) {
+ result = 0
+ for job.groups as g {
+ for g.tasks as t {
+ result += t.resources.memory else 0
+ }
+ }
+
+ return result
+}
+
+main = rule {
+ memory(job) < 1 * units.gigabyte
+}
+```
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/language/lists.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/language/lists.mdx
new file mode 100644
index 0000000000..bc5269d8eb
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/language/lists.mdx
@@ -0,0 +1,135 @@
+---
+page_title: Sentinel Language - Lists
+sidebar_current: docs-language-lists
+description: Lists are a collection of zero or more values.
+layout: docs
+---
+
+# Language: Lists
+
+Lists are a collection of zero or more values.
+
+Lists can be created using by wrapping values in `[]` and separating them
+by commas. An optional trailing comma is allowed. List elements can be
+differing types. Examples:
+
+```sentinel
+[] // An empty list
+["foo"] // Single element list
+["foo", 1, 2, true] // Multi element list with different types
+["foo", [1, 2]] // List containing another list
+```
+
+A list can be [sliced](/sentinel/language/slices) to create sublists.
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for value inclusion in a list.
+
+## Accessing Elements
+
+List elements can be accessed with the syntax `name[index]` where
+`index` is zero-indexed.
+
+A negative index accesses the list in reverse. It is the same as
+reversing a list and then using a positive index. Similar to a positive
+index, it is bounded by the length of the list as a negative value.
+
+Accessing beyond the length of the list results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+a = ["foo", 1, true, [1, 2]]
+
+a[0] // "foo"
+a[2] // true
+a[4] // undefined
+a[-2] // true
+a[-4] // "foo"
+a[-5] // undefined
+a[3][1] // 2
+```
+
+## List Append
+
+Values can be appended to a list using the built-in
+[append](/sentinel/functions/append) function.
+
+This modifies the list in-place and returns
+[undefined](/sentinel/language/undefined). For more information on
+why `append` behaves this way, please read the full documentation for the
+[append](/sentinel/functions/append) function.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append([1,2], "foo") // [1, 2, "foo"]
+append([1,2], [3]) // [1, 2, [3]]
+append(1, 3) // error()
+```
+
+## List Concatenation
+
+Two lists can be concatenated using the `+` operator or the shorthand
+`+=` assignment operator. For the `+` operator, a new list is returned.
+For `+=`, the left-hand list is modified in place.
+
+Examples:
+
+```sentinel
+[1] + [2] // [1, 2]
+[1] + [[1]] // [1, [1]]
+[1] + 1 // error
+
+a = [1]
+a += [2] // a = [1, 2]
+a += 3 // error
+```
+
+## List Length
+
+The length of a list can be retrieved using the
+[length](/sentinel/functions/length) function.
+
+Examples:
+
+```sentinel
+length([]) // 0
+length(["foo"]) // 1
+```
+
+## Removing Items From a List
+
+You can use a combination of [list concatenation](#list-concatenation) and
+[slicing](/sentinel/language/slices) to remove elements from a list.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+a = a[:2] + a[3:] // [1, 2, 4, 5]
+```
+
+The shorthand shown here is effectively the same as `a[0:2] + a[3:length(a)]`,
+which creates a new list out of the concatenation two sub-lists composed of the
+first two elements, and the rest of the list starting at index 3. This
+effectively removes the 3rd element from the list (index 2).
+
+## List Comparison
+
+Lists may be [compared](/sentinel/language/boolexpr#comparison-operators)
+for equality. Lists are equal if they are of equal length and their
+corresponding elements are comparable and equal. Lists with the same elements
+but in a different order are not equal.
+
+```sentinel
+[1, 2] is [1, 2] // true
+[1, 2] is [2, 1] // false
+["a"] is ["a", "b"] // false
+["a", ["b", "c"]] is ["a", ["b", "c"]] // true
+```
+
+List comparison speed is O(N), meaning that the speed of the comparison is
+_linearly_ proportional to the number of elements in the list. The more
+elements, the more iterations that are necessary to verify equality.
+
+-> The N-value quoted above should account for the sum of the elements of _all_
+lists in the subjects of comparison, as list comparison will recurse into these
+lists to check for equality.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/language/logging-errors.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/language/logging-errors.mdx
new file mode 100644
index 0000000000..705a72cbd4
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/language/logging-errors.mdx
@@ -0,0 +1,52 @@
+---
+page_title: Sentinel Language - Logging and Errors
+sidebar_current: docs-language-log
+description: The built-in functions `print` and `error` can be used for logging and errors.
+layout: docs
+---
+
+# Language: Logging and Errors
+
+The built-in functions `print` and `error` can be used for logging
+and errors. Logging is helpful to understand how a policy is executing
+in development. And errors are useful to halting execution immediately in
+certain scenarios.
+
+## Print
+
+The `print` function is used to output debug information. The exact location
+where this print statements go is dependent on the host application and
+the Sentinel runtime itself.
+
+The `print` function takes zero or more Sentinel values as arguments.
+Each value is formatted for human-friendly output and space-separated.
+Example:
+
+```sentinel
+value = 42
+print("the value is", value) // the value is 42
+
+map = { "foo": false }
+print(map) // { "foo": false }
+
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // false
+```
+
+## Errors
+
+Certain actions in Sentinel, such as accessing an undefined variable,
+attempting to add two incompatible types, etc. result in _runtime errors_.
+These errors halt the execution of a policy immediately. The pass/fail result
+of a policy is considered fail.
+
+Runtime errors can be manually triggered using the `error` function.
+The `error` function behaves exactly like the `print` function except that
+execution is halted immediately.
+
+This should only be used in scenarios where the policy _must fail_ due to
+some unexpected or invalid condition. The line between `error` and
+[undefined](/sentinel/language/undefined) can sometimes be unclear. Undefined
+is recommended when a policy can conceivably recover or safely ignore the
+undefined behavior. An error should be used when the policy should fail and
+notify someone regardless.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/language/loops.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/language/loops.mdx
new file mode 100644
index 0000000000..622732bd35
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/language/loops.mdx
@@ -0,0 +1,92 @@
+---
+page_title: Sentinel Language - Loops
+sidebar_current: docs-language-loops
+description: >-
+ Loop statements allow you to execute a body of code for each element in a
+ collection or for some fixed number of times.
+layout: docs
+---
+
+# Language: Loops
+
+Loop statements allow you to execute a body of code for each element in
+a collection or for some fixed number of times.
+
+Loop statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## For Statements
+
+`for` statements allow repeated execution of a block for each
+element in a collection.
+
+Example:
+
+```sentinel
+// A basic sum
+count = 0
+for [1, 2, 3] as num {
+ count += num
+}
+```
+
+The syntax is `for COLLECTION as value`. This will iterate over the
+collection, assigning each element to `value`. In the example above,
+each element is assigned to `num`. The body is executed for each element.
+In the example above, the body adds `num` to the `count` variable. This
+creates a basic sum of all values in the collection.
+
+For a map, the assigned element is the key in the map. In the example
+below, `name` would be assigned map keys.
+
+```sentinel
+list = []
+for { "a": 1, "b": 2 } as name {
+ append(list, name)
+}
+
+print(list) // ["a" "b"]
+```
+
+An alternate syntax is `for COLLECTION as key, value`. This will assign
+both the key and value to a variable. For a list, the key is the element
+index. For a map, it is the key and value is assigned the element value.
+Example:
+
+```sentinel
+count = 0
+for { "a": 1, "b": 2 } as name, num {
+ count += num
+}
+
+print(count) // 3
+```
+
+## Scoping
+
+The body of a `for` statement creates a new
+[scope](/sentinel/language/scope). If a variable is assigned within
+the body of a for statement that isn't assigned in a parent scope,
+that variable will only exist for the duration of the body execution.
+
+Example:
+
+```sentinel
+for list as value {
+ a = 42
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+for list as value {
+ a = 42
+}
+
+print(a) // 18
+```
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/language/maps.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/language/maps.mdx
new file mode 100644
index 0000000000..298bf9a567
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/language/maps.mdx
@@ -0,0 +1,121 @@
+---
+page_title: Sentinel Language - Maps
+sidebar_current: docs-language-maps
+description: >-
+ Maps are a collection of zero or more key/value pairs. These are useful for
+ storing values that are looked up by a unique key.
+layout: docs
+---
+
+# Language: Maps
+
+Maps are a collection of zero or more key/value pairs. These are useful
+for storing values that are looked up by a unique key.
+
+Maps can be created using `{}` and specifying key/value pairs. Keys and
+values can be differing types. An optional trailing comma is allowed.
+Examples:
+
+```sentinel
+// Empty map
+{}
+
+// Map with a single value on one line
+{ "key": "value" }
+
+// Map with multiple values with differing types on multiple lines
+{
+ "key": "value",
+ 42: true,
+}
+```
+
+Maps are **unordered**. When
+[looping](/sentinel/language/loops)
+over a map, the key/value pairs can be returned in any order.
+
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for key inclusion in a map.
+
+## Accessing Elements
+
+Map elements can be accessed with the syntax `name[key]`. This looks up
+a value by a key.
+
+Accessing a key that doesn't exist results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+map = { "key": "value", 42: true, }
+
+map["key"] // "value"
+map[42] // true
+map[0] // undefined
+```
+
+## Modifying or Adding Elements
+
+Elements can be added or modified in a map by assigning to `name[key]`.
+If the key doesn't exist, the value is added. If the key already exists,
+the value is overridden.
+
+```sentinel
+map = { "key": "value" }
+
+map[42] = true // Add a new key/value
+map["key"] = 12 // Modify the value of "key"
+```
+
+## Deleting Elements
+
+An element can be deleted from a map using the
+[delete](/sentinel/functions/delete) function.
+
+Examples:
+
+```sentinel
+map = { "key": "value" }
+delete(map, "key") // map is now empty
+delete(map, "other") // no effect for non-existent key
+```
+
+## Keys and Values
+
+The keys and values of a map can be retrieved as
+[lists](/sentinel/language/lists) using the
+[keys](/sentinel/functions/keys) and
+[values](/sentinel/functions/values) functions.
+
+Because maps are unordered, the keys and values are returned in an
+unspecified order. It should not be assumed that keys and values will be
+returned in a consistent order.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+## Map Comparison
+
+Maps may be [compared](/sentinel/language/boolexpr#comparison-operators) for
+equality. Maps are equal if they are of equal length and both their
+corresponding keys and values are comparable and equal.
+
+```sentinel
+{"foo": "bar"} is {"foo": "bar"} // true
+{"foo": "bar"} is {"baz": "bar"} // false
+{"foo": "bar"} is {"foo": "baz"} // false
+{"foo": "bar"} is {"foo": "bar", "baz": "qux"} // false
+{1: "a"} is {1.0: "a"} // true (int/float comparable)
+
+// also true (maps are not ordered):
+{"m": {"a": "b"}, "l": ["a"]} is {"l": ["a"], "m": {"a": " b"}}
+```
+
+-> The current worst-case comparison speed for maps is O(N²), or _quadratic
+time_. This is the square of the cumulative elements or the parent and all child
+maps contained within the map. Consider this when making comparisons on larger,
+more complex maps. This may be optimized in the future.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/language/parameters.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/language/parameters.mdx
new file mode 100644
index 0000000000..348acf2593
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/language/parameters.mdx
@@ -0,0 +1,133 @@
+---
+page_title: Sentinel Language - Parameters
+sidebar_current: docs-language-parameters
+description: >-
+ Parameteres allow a Sentinel policy to describe variables that are expected
+ to be supplied by the calling application, in addition to any default value.
+layout: docs
+---
+
+# Language: Parameters
+
+Sentinel allows a policy author to supply parameters to help facilitate policy
+reuse and ensure sensitive values do not need to be hard-coded in a policy.
+
+Parameters are supplied by using the `param` keyword, followed by an identifier.
+A default value can also be supplied by using the `default` keyword.
+
+```sentinel
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+```
+
+Once declared, parameters can be used like any other variable, including being
+re-assigned.
+
+```sentinel
+param foo default 1 // 1 (default)
+
+foo = foo + 1 // 2
+```
+
+## Variable Descriptions
+
+You can supply a description to a parameter by adding a comment at the top of
+it. This value can be communicated to a specific implementation of Sentinel to
+provide information about what the parameter is for during configuration.
+
+```sentinel
+// An example parameter. Must be supplied or the policy will fail.
+param foo
+```
+
+## Supplying Parameter Values Using the Sentinel CLI
+
+In a production implementation, supplying parameters to a policy is an
+implementation-specific detail - see the documentation for your particular
+implementation for details.
+
+Using the [Sentinel CLI](/sentinel/commands), you can supply parameters one of
+four ways.
+
+### Supplying Parameter Values Using the Configuration File
+
+You can supply parameters using the
+[`param`](/sentinel/configuration#parameters) section of the
+[configuration file](/sentinel/configuration).
+
+```hcl
+param "foo" {
+ value = "bar"
+}
+```
+
+This method works for both `sentinel apply` and `sentinel test`.
+
+### Supplying Parameter Values Using the Environment
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can supply a value using environment variables - prefix the parameter with
+`SENTINEL_PARAM_`, using the name of the parameter to supply.
+
+```plaintext
+SENTINEL_PARAM_foo=bar sentinel apply policy.sentinel
+```
+
+### Supplying Parameter Values Using the CLI
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can also use the `-param` CLI argument to supply parameter in a `key=value`
+pair.
+
+```plaintext
+sentinel apply -param foo=bar policy.sentinel
+```
+
+### Interactive CLI Prompting
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+If a required value has not been supplied when a policy is run with `sentinel apply`, it will be prompted for, along with its description:
+
+
+
+ $ sentinel apply policy.sentinel
+
+ policy.sentinel:2:7: requires value for parameter foo
+
+ An example parameter. Must be supplied or the policy will fail.
+
+
+ Values can be strings, floats, or JSON array or object values. To
+ force
+
+ strings, use quotes.
+
+
+ Enter a value: bar
+
+
+
+ Pass
+
+
+
+
+### CLI Value Format
+
+-> **NOTE:** This section contains details for the parameter features supported by `sentinel apply`.
+
+The CLI takes either strings, or JSON numbers, arrays, or maps. If you need a
+literal string value, quote the value.
+
+```sentinel
+foo // string
+42 // number (float)
+"42" // string ("42", without quotes)
+[1, 2] // array (list)
+{"a": "b"} // object (map)
+```
+
+-> **NOTE:** Boolean values are not supported by this method.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/language/rules.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/language/rules.mdx
new file mode 100644
index 0000000000..e31924d414
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/language/rules.mdx
@@ -0,0 +1,204 @@
+---
+page_title: Sentinel Language - Rules
+sidebar_current: docs-language-rules
+description: >-
+ Rules form the basis of a policy by representing behavior that is either
+ passing or failing (true or false).
+layout: docs
+---
+
+# Language: Rules
+
+Rules form the basis of a policy by representing behavior that is either passing
+or failing. A policy can be broken down into a set of rules. Breaking down a
+policy into a set of rules can make it more understandable and aids with
+testing.
+
+An example is shown below:
+
+```sentinel playground
+weather = "sunny"
+day = "wednesday"
+is_sunny = rule { weather is "sunny" }
+is_wednesday = rule { day is "wednesday" }
+
+main = rule {
+ is_sunny and
+ is_wednesday
+}
+```
+
+A rule contains a single expression. This can be a simple [boolean
+expression](/sentinel/language/boolexpr), or an expression representing the
+discovery of a set of violations using more in-depth expressions like [`filter`
+and `map`](/sentinel/language/collection-operations). You can split expressions
+into multiple lines for readability, as you would with any other expression
+within Sentinel.
+
+A rule is also _lazy_ and _memoized_. _Lazy_ means that the rule is only
+evaluated when it is actually required, not at the point that it is
+created. This is covered in more detail in the [lazy](#lazy-and-memoized) section.
+_Memoized_ means that the value is only computed once and then saved. Once
+a rule is evaluated, the result of it is reused anytime the rule is referenced
+again.
+
+## Making rules easier to understand
+
+Rules are an abstraction that make policies much more understandable
+by making it easier for humans to see what the policy is trying to do.
+
+For example, consider the policy before which doesn't abstract into rules:
+
+```sentinel
+main = rule {
+ ((day is "saturday" or day is "sunday") and homework is "") or
+ (day in ["monday", "tuesday", "wednesday", "thursday", "friday"] and
+ not school_today and homework is "")
+}
+```
+
+Assume that `day`, `homework`, and `school_day` are available.
+
+In plain English, this policy is trying to say: you can play with your friends
+only on the weekend as long as there is no homework, or during the week if
+there is no school and no homework.
+
+Despite the relatively simple nature of this policy (a few lines, about 5
+logical expressions), it is difficult to read and understand, especially if
+you've not seen it before.
+
+The same policy using rules as an abstraction:
+
+```sentinel
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+is_valid_weekend = rule { is_weekend and homework is "" }
+is_valid_weekday = rule { not is_weekend and not school_today and homework is "" }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+By reading the names of the rules, its much clearer to see what each individual
+part of the policy is trying to achieve. The readability is improved even
+further when adding comments to the rules to explain them further, which is
+a recommended practice:
+
+```sentinel
+// A weekend is Sat or Sun
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+// A valid weekend is a weekend without homework
+is_valid_weekend = rule { is_weekend and homework is "" }
+
+// A valid weekday is a weekday without school
+is_valid_weekday = rule { not is_weekend and not school_today and homework is "" }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+## "When" Predicates
+
+A rule may have an optional `when` predicate attached to it. When this is
+present, the rule is only evaluated when the predicate results in `true`.
+Otherwise, the rule is not evaluated and the result of the rule is always
+`true`.
+
+"When" predicates should be used to define the context in which the rule
+is semantically meaningful. It is common when translating human language
+policy into Sentinel to have scenarios such as "when the key has a prefix
+of `/account/`, the remainder must be numeric." In that example, when the
+key _does not_ have the specified prefix, the policy doesn't apply.
+
+Assume you have rules `is_prefix` and `is_numeric` to check both the
+conditions in the example above. An example is shown with and without
+the "when" predicate:
+
+```sentinel
+example_no_when = rule { (is_prefix and is_numeric) or not is_prefix }
+
+example_when = rule when is_prefix { is_numeric }
+```
+
+The rules are equivalent in behavior for all values of `is_prefix` and
+`is_numeric`, but the second rule is more succint and easier to understand.
+
+## Testing
+
+To verify a policy works correct, the
+[built-in Sentinel test framework](/sentinel/writing/testing)
+uses rules as the point where you can assert behavior. You say that
+you expect certain rules to be true or false. By doing so, you ensure that
+the policy results in the value you expect using the rule flow that you
+expect.
+
+Therefore, in addition to readability, we recommend splitting policies into
+rules to aid testability.
+
+## Non-Boolean Values
+
+Sentinel supports non-boolean values in rules. This can be useful for passing
+more detail along to a calling integration when more detail is required than a
+boolean value would be able to communicate. This data shows up in the [policy
+trace](/sentinel/writing/tracing) and can be utilized in different ways
+depending on the integration you are using Sentinel with.
+
+Consider a different take on our policy above:
+
+```sentinel
+// A policy to check a set of scheduled days to determine what days you can't
+// come out and play, based on the day not being a weekend day and there being
+// homework to do.
+
+param days
+
+main = rule {
+ filter days as d {
+ d.day not in ["saturday", "sunday"] and
+ d.homework is not ""
+ }
+}
+```
+
+When given a value in the set of `days` that has a `day` that is a weekday, and
+`homework` present, the policy will fail. Additional, when tracing was enabled,
+the days that were detected as violating the policy would be given in the trace
+for the `main` rule.
+
+Generally, for non-boolean values, a policy fails on non-zero data. See the
+details on [the `main` rule](/sentinel/language#main) for more details.
+
+The result of a rule must be either a boolean, string, integer, float, list, or
+map. All other types will result in a runtime error.
+
+## Lazy and Memoized
+
+A rule is _lazy_ and _memoized_.
+
+_Lazy_ means that the rule is only evaluated when it is actually required,
+not at the point that it is created. And _memoized_ means that the value is
+only computed once and then saved. Once a rule is evaluated, the result of it
+is reused anytime the rule is referenced again.
+
+Both of these properties have important implications for Sentinel
+policies:
+
+**Performance:** Rules are a way to improve the performance of a Sentinel
+policy. If you have a boolean expression that is reused a lot, a rule will
+only be evaluated once. In the example shown above, `is_weekend` is used
+multiple times, but will only have to be evaluated once.
+
+**Behavior:** Rules accessing variables see the value of those variables
+at _the time they are evaluated_. This can lead to surprising behavior
+in some cases. For example:
+
+```sentinel playground
+a = 1
+b = rule { a == 1 }
+a = 2
+main = b
+```
+
+In this example, `main` will actually result to `false`. It is evaluated
+when it is needed, which is when the policy executes `main`. At this point,
+`a` is now 2 and `b` has not been evaluated yet. Therefore, `b` becomes `false`.
+All future references to `b` will return `false` since a rule is memoized.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/language/scope.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/language/scope.mdx
new file mode 100644
index 0000000000..f498e26dc2
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/language/scope.mdx
@@ -0,0 +1,47 @@
+---
+page_title: Sentinel Language - Scope
+sidebar_current: docs-language-scope
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Scope
+
+Scope is the context in which variables are created and accessed. If a
+variable exists in a parent scope, it is accessed and can be modified.
+Otherwise, the variable is created in your current scope.
+
+Scopes are created with _blocks_. A block is a possibly empty sequence
+of statements within matching brace brackets `{}`. You may nest blocks
+to create nested scopes.
+
+## Package Scope
+
+The package scope is the top level scope within a policy file and encapsulates
+the entire file contents. Imports, parameters and named functions must be
+declared within the package scope.
+
+## Implicit Scopes
+
+Each `any`, `all`, and `for` statement is considered to be in its own block.
+Note that `if` statements _do not_ create their own block.
+
+## Examples
+
+The example policy below shows various effects of scopes:
+
+```sentinel
+a = 1
+print(a) // 1
+
+f = func() {
+ print(a) // 1
+ a = 12
+ b = 1
+ return undefined
+}
+f()
+
+print(a) // 12
+print(b) // undefined
+```
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/language/slices.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/language/slices.mdx
new file mode 100644
index 0000000000..82dc7f4c18
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/language/slices.mdx
@@ -0,0 +1,45 @@
+---
+page_title: Sentinel Language - Slices
+sidebar_current: docs-language-slices
+description: >-
+ Slices are an efficient way to create a substring or sublist from an existing
+ string or list, respectively.
+layout: docs
+---
+
+# Language: Slices
+
+Slices are an efficient way to create a substring or sublist from an
+existing string or list, respectively.
+
+The syntax for creating a slice is:
+
+```sentinel
+a[low : high]
+```
+
+`low` is the lowest index to slice from. The resulting slice includes
+the value at `low`. `high` is the index to slice to. The resulting slice
+will not include the value at `high`. The length of the resulting list
+or string is always `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+
+a = "hello"
+b = a[1:4] // "ell"
+```
+
+## Convenience Shorthands
+
+Some convenience shorthands are available by omitting the value for either the
+low or high index in the slice expression: omitting `low` implies a low index of
+0, and omitting `high` implies a high index of the length of the list.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+
+b = a[:2] // [1, 2] (same as a[0:2])
+b = a[2:] // [3, 4, 5] (same as a[2:length(a)])
+```
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/language/spec.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/language/spec.mdx
new file mode 100644
index 0000000000..0877897422
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/language/spec.mdx
@@ -0,0 +1,1330 @@
+---
+page_title: Sentinel Language Specification
+sidebar_current: docs-language-spec
+description: This is the specification for the Sentinel policy language.
+layout: docs
+---
+
+# Sentinel Language Specification
+
+This is the specification for the Sentinel policy language.
+
+The Sentinel language is designed with policy enforcement in mind. It is dynamically typed and garbage collected and has explicit support for _rule_ construction representing boolean logic.
+
+The language is designed to be easy to learn and use by non-programmers. It is expected to be embedded within applications.
+
+## Table of Contents
+
+
+
+
+- [Source code representation](#source-code-representation)
+- [Declarations and Scope](#declarations-and-scope)
+- [Blocks](#blocks)
+- [Lexical Elements](#lexical-elements)
+ - [Comments](#comments)
+ - [Identifiers](#identifiers)
+ - [Keywords](#keywords)
+ - [Operators and Delimiters](#operators-and-delimiters)
+ - [Integer Literals](#integer-literals)
+ - [Floating-point Literals](#floating-point-literals)
+ - [String Literals](#string-literals)
+ - [Implicit Line Joining](#implicit-line-joining)
+ - [Whitespace](#whitespace)
+ - [Semicolons](#semicolons)
+- [Variables](#variables)
+- [Undefined](#undefined)
+- [Expressions](#expressions)
+ - [Operand](#operand)
+ - [Primary Expressions](#primary-expressions)
+ - [Null](#null)
+ - [Booleans](#booleans)
+ - [Boolean Literals](#boolean-literals)
+ - [Boolean Expressions](#boolean-expressions)
+ - [List Literals](#list-literals)
+ - [Map Literals](#map-literals)
+ - [Function Literals](#function-literals)
+ - [Rule Expressions](#rule-expressions)
+ - [Index Expressions](#index-expressions)
+ - [Selectors](#selectors)
+ - [Slice Expressions](#slice-expressions)
+ - [Calls](#calls)
+ - [Operators](#operators)
+ - [Operator Precedence](#operator-precedence)
+ - [Arithmetic Operators](#arithmetic-operators)
+ - [Integer operators](#integer-operators)
+ - [Integer overflow](#integer-overflow)
+ - [Floating-point operators](#floating-point-operators)
+ - [String Concatenation](#string-concatenation)
+ - [List Concatenation](#list-concatenation)
+ - [Comparison Operators](#comparison-operators)
+ - [Logical Operators](#logical-operators)
+ - [Set Operators](#set-operators)
+ - [Matches Operator](#matches-operator)
+ - [Else Operator](#else-operator)
+ - [Quantifier Expressions (any, all, filter, map)](#quantifier-expressions-any-all-filter-map)
+- [Statements](#statements)
+ - [Expression Statements](#expression-statements)
+ - [Assignments](#assignments)
+ - [If Statements](#if-statements)
+ - [Case Statements](#case-statements)
+ - [For Statements](#for-statements)
+ - [Break Statements](#break-statements)
+ - [Continue Statements](#continue-statements)
+ - [Return Statements](#return-statements)
+- [Imports](#imports)
+- [Parameters](#parameters)
+- [Built-in Functions](#built-in-functions)
+ - [Length](#length)
+ - [Collections](#collections)
+ - [List Append](#list-append)
+ - [Map Delete](#map-delete)
+ - [Keys and Values](#keys-and-values)
+ - [Range](#range)
+ - [Type Conversion](#type-conversion)
+ - [Printing](#printing)
+ - [Errors](#errors)
+- [Program Execution](#program-execution)
+
+
+
+## Source code representation
+
+Source code is Unicode text encoded in UTF-8. A "character" referenced in this document refers to a Unicode code point. Each code point is distinct: upper and lower case letters are different characters.
+
+The underscore character \_ (U+005F) is considered a "letter".
+
+```ebnf
+letter = unicode_letter | "_" .
+decimal_digit = "0" … "9" .
+octal_digit = "0" … "7" .
+hex_digit = "0" … "9" | "A" … "F" | "a" … "f" .
+```
+
+## Declarations and Scope
+
+A declaration binds an identifier to a value. An identifier is declared at the point it is first assigned. An assignment is considered a first assignment if the identifier hasn't already been previously declared in the current scope or any parent scopes.
+
+The scope of an identifier is the extent of source text in which the identifier denotes the specified value. Sentinel is lexically scoped using blocks.
+
+An identifier declared in a block may be redeclared in an inner block. While the identifier of the inner declaration is in scope, it denotes the value assigned in the inner declaration.
+
+## Blocks
+
+A block is a possibly empty sequence of statements within matching brace brackets.
+
+```ebnf
+Block = "{" StatementList "}" .
+StatementList = { Statement ";" } .
+```
+
+Blocks nest and affect scoping.
+
+In addition to explicit blocks in the source code, there are implicit blocks:
+
+1. The universe block encompasses all source text.
+2. Each program has a program block containing all source text for that program.
+3. Each "any", "all", and "for" statements is considered to be in its own implicit block. "if" does not create an implicit block.
+
+## Lexical Elements
+
+### Comments
+
+Comments are sections of source text used for documentation.
+
+```plaintext
+# Single line comment
+// Single line comment
+/* multi
+line
+ comment */
+```
+
+Three forms of comments are supported: two single-line forms and one multi-line form. A single-line comment begins with the token `//` or `#`. Everything between the starting token and the end of the line is ignored.
+
+A multi-line comment begins with the token `/*` and ends with the token `*/`. Everything between `/*` and `*/` is ignored.
+
+A comment may not start inside a string literal or inside another comment.
+
+A multi-line comment containing no newlines acts like a space.
+
+### Identifiers
+
+Identifiers name program entities such as rules, variables, and functions. An identifier is a sequence of one or more letters and digits. The first character in an identifier must be a letter.
+
+```ebnf
+identifier = letter { letter | unicode_digit } .
+```
+
+```sentinel
+a
+_a
+_A
+αβ
+```
+
+#### Pre-Declared Identifiers
+
+The following identifiers are implicitly declared in the [universe block](#blocks):
+
+```plaintext
+Constants:
+ false null true undefined
+
+Functions:
+ append bool delete error float int keys length print range string values
+```
+
+### Keywords
+
+The following keywords are reserved and may not be used as identifiers:
+
+```plaintext
+all
+any
+as
+break
+case
+continue
+default
+else
+empty
+filter
+for
+func
+if
+import
+map
+param
+return
+rule
+when
+```
+
+### Operators and Delimiters
+
+The following character sequences represent operators, delimiters, and other special tokens:
+
+```plaintext
++
+-
+*
+/
+%
++=
+-=
+*=
+/=
+%=
+==
+<
+>
+=
+!
+!=
+<=
+>=
+( )
+[ ]
+{ }
+,
+.
+:
+;
+and
+contains
+else
+in
+is
+matches
+not
+or
+xor
+```
+
+As a special case, `else` is both a keyword and operator, depending on the context. See [Else Operator](#else-operator) for more details.
+
+### Integer Literals
+
+An integer literal is a sequence of digits representing an integer constant. An optional prefix sets a non-decimal base: `0` for octal, `0x` or `0X` for hexadecimal. In hexadecimal literals, letters `a-f` and `A-F` represents values 10 through 15.
+
+Integers are signed 64-bit values (-9223372036854775808 to 9223372036854775807).
+
+```ebnf
+int_lit = decimal_lit | octal_lit | hex_lit .
+decimal_lit = ( "1" … "9" ) { decimal_digit } .
+octal_lit = "0" { octal_digit } .
+hex_lit = "0" ( "x" | "X" ) hex_digit { hex_digit } .
+```
+
+```
+42
+0600
+0xBadFace
+170141183460469231731687303715884105727
+```
+
+### Floating-point Literals
+
+A floating-point literal is a decimal representation of a floating-point constant. It has an integer part, a decimal point, a fractional part, and an exponent part. The integer and fractional part comprise decimal digits; the exponent part is an e or E followed by an optionally signed decimal exponent. One of the integer part or the fractional part may be elided; one of the decimal point or the exponent may be elided.
+
+Floating-point numbers are IEEE-754 64-bit floating numbers.
+
+```ebnf
+float_lit = decimals "." [ decimals ] [ exponent ] |
+ decimals exponent |
+ "." decimals [ exponent ] .
+decimals = decimal_digit { decimal_digit } .
+exponent = ( "e" | "E" ) [ "+" | "-" ] decimals .
+```
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+### String Literals
+
+String literals are character sequences between double quotes, as in "bar". Within the quotes, any character may appear except newline and unescaped double quote. The text between the quotes forms the value of the literal.
+
+A multi-character sequence beginning with a backslash encode values in various formats.
+
+Backslash escapes allow arbitrary values to be encoded as ASCII text. There are four ways to represent the integer value as a numeric constant: `\x` followed by exactly two hexadecimal digits; `\u` followed by exactly four hexadecimal digits; `\U` followed by exactly eight hexadecimal digits, and a plain backslash `\` followed by exactly three octal digits. In each case the value of the literal is the value represented by the digits in the corresponding base.
+
+After a backslash, certain single-character escapes represent special values:
+
+```plaintext
+\a U+0007 alert or bell
+\b U+0008 backspace
+\f U+000C form feed
+\n U+000A line feed or newline
+\r U+000D carriage return
+\t U+0009 horizontal tab
+\v U+000b vertical tab
+\\ U+005c backslash
+\" U+0022 double quote
+```
+
+The three-digit octal (\nnn) and two-digit hexadecimal (\xnn) escapes represent individual bytes of the resulting string; all other escapes represent the (possibly multi-byte) UTF-8 encoding of individual characters. Thus inside a string literal \377 and \xFF represent a single byte of value 0xFF=255, while ÿ, \u00FF, \U000000FF and \xc3\xbf represent the two bytes 0xc3 0xbf of the UTF-8 encoding of character U+00FF.
+
+A string value is a (possibly empty) sequence of bytes. Strings are immutable: once created, it is impossible to change the contents of a string.
+
+The length of a string s (its size in bytes) can be discovered using the built-in function `length`. An individual character (of type string) can be accessed by integer indices 0 through `length(s)-1`.
+
+```ebnf
+string_lit = `"` { unicode_value | byte_value } `"` .
+unicode_value = unicode_char | little_u_value | big_u_value | escaped_char .
+byte_value = octal_byte_value | hex_byte_value .
+octal_byte_value = `\` octal_digit octal_digit octal_digit .
+hex_byte_value = `\` "x" hex_digit hex_digit .
+little_u_value = `\` "u" hex_digit hex_digit hex_digit hex_digit .
+big_u_value = `\` "U" hex_digit hex_digit hex_digit hex_digit
+ hex_digit hex_digit hex_digit hex_digit .
+escaped_char = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | `"` ) .
+```
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+### Implicit Line Joining
+
+Expressions can be split over more than one line. For example:
+
+```sentinel
+a or
+b or # This is a comment
+c
+```
+
+Implicitly continued lines can have trailing comments. Blank continued lines are allowed.
+
+### Whitespace
+
+Whitespace is needed to separate tokens, but no distinction is made between the number and combination of whitespace characters. Whitespace characters can be space (U+0020), horizontal tabs (U+0009), carriage returns (U+000D), and newlines (U+000A).
+
+```sentinel
+a or b
+
+a or b
+
+a or
+ b
+
+rule { a or b }
+
+rule {
+ a or b }
+
+rule {
+ a or
+ b
+}
+```
+
+### Semicolons
+
+The formal grammar uses semicolons ";" as terminators in a number of productions.
+It is idiomatic Sentinel source to omit semicolons in most cases.
+
+A semicolon is automatically inserted into the token stream immediately after
+a line's final token if that token is:
+
+- An identifier
+- An integer, float, or string literal
+- The keyword `break`, `continue`, or `return`
+- The delimiter `)`, `]`, or `}`
+
+## Variables
+
+A variable is a storage location for holding a value.
+
+A variable has a dynamic type, which is the concrete type of the value assigned to the variable at run time. The dynamic type may vary during execution and change as a result of further assignments.
+
+A variable's value is retrieved by referring to the variable in an expression; it is the most recent value assigned to the variable. If a variable has not yet been declared (initially assigned), it is an error.
+
+```sentinel
+x = 7 // x is type int
+x = "foo" // x is now type string
+
+x = y // error if y is not previously assigned
+```
+
+## Undefined
+
+The value denoted by the keyword `undefined` represents undefined behavior or values. It can be created directly using the keyword `undefined`. It is also returned as a result of expressions in specified cases.
+
+`undefined` is a valid operand for any operations. Only `undefined or true` will result in true. All other operations result in `undefined`. An exception is if `undefined` is not reached as a result of short-circuit operations.
+
+```sentinel
+undefined OR true = true
+undefined OR false = undefined
+undefined OR undefined = undefined
+undefined AND true = undefined
+undefined AND false = undefined
+undefined AND undefined = undefined
+undefined XOR true = undefined
+undefined XOR false = undefined
+undefined XOR undefined = undefined
+
+// Short-circuit examples
+false OR true OR undefined = true
+false OR undefined OR true = true
+true AND false AND undefined = false
+true AND undefined AND false = undefined
+
+// Non-logical operators
+undefined + 5 = undefined
+-undefined = undefined
+!undefined = undefined
+```
+
+If the result of the main rule is `undefined`, it is treated as `false` but is indicative of erroneous logic.
+
+## Expressions
+
+### Operand
+
+Operands denote the elementary values in an expression. An operand may be a literal, an identifier denoting a variable, rule, or function, or a parenthesized expression.
+
+```ebnf
+Operand = Literal | OperandName | MethodExpr | "(" Expression ")" .
+Literal = BasicLit | CompositeLit | FunctionLit | RuleLit .
+BasicLit = int_lit | float_lit | string_lit .
+OperandName = identifier | QualifiedIdent.
+```
+
+### Primary Expressions
+
+Primary expressions are the operands for unary and binary expressions.
+
+```ebnf
+PrimaryExpr =
+ Operand |
+ PrimaryExpr Selector |
+ PrimaryExpr Index |
+ PrimaryExpr Slice |
+ PrimaryExpr Arguments .
+
+Selector = "." identifier .
+Index = "[" Expression "]" .
+Slice = "[" [ Expression ] ":" [ Expression ] "]" |
+ "[" [ Expression ] ":" Expression ":" Expression "]" .
+Arguments = "(" [ ( ExpressionList | Type [ "," ExpressionList ] ) [ "..." ] [ "," ] ] ")" .
+```
+
+```sentinel
+x
+2
+(s + ".txt")
+f(3.1415, true)
+m["foo"]
+s[i : j + 1]
+obj.color
+f.p[i].x()
+x is empty
+x is not empty
+```
+
+### Null
+
+The reserved word `null` denote the singleton value `null`. Null represents the explicit absense of a value. Behavior of `null` within expressions is specified explicitly for each expression.
+
+### Booleans
+
+### Boolean Literals
+
+The reserved words `true` and `false` denote objects that represent the boolean values `true` and `false`, respectively. These are boolean literals.
+
+```ebnf
+BoolLit = "true" | "false" .
+```
+
+#### Boolean Expressions
+
+Boolean expressions are expressions that must result in a boolean value. Any other value type becomes the `undefined` value.
+
+```ebnf
+BoolExpr = Expression .
+```
+
+### List Literals
+
+A list literal denotes a list, which is an integer indexed collection of values.
+
+```ebnf
+ListLit = "[" [ ElementList [ "," ] ] "]" .
+ElementList = Element { "," Element } .
+Element = Expression | LiteralValue .
+```
+
+A list may contain zero or more values. The number of values in a list is its length.
+
+A list has a set of indices. An empty list has an empty set of indices. A non-empty list has the index set `{0...n - 1}` where `n` is the length of the list. Attempting to access a list using an index that is not a member of its set of indices results in the `undefined` value.
+
+### Map Literals
+
+A map literal denotes a map, which is an unordered group of elements indexed by a set of unique keys.
+
+```ebnf
+MapLit = "{" [ KeyedElementList [ "," ] ] "}" .
+KeyedElementList = KeyedElement { "," KeyedElement } .
+KeyedElement = Element ":" Element .
+```
+
+Keys can only be a boolean, numeric, or string type.
+
+The value of a non-existent key is the `undefined` value.
+
+### Function Literals
+
+A function literal represents a function.
+
+```ebnf
+FunctionLit = "func" Function .
+Function = Parameters FunctionBody .
+FunctionBody = Block .
+Parameters = "(" [ IdentifierList [ "," ] ] ")" .
+IdentifierList = identifier { "," identifier } .
+```
+
+```sentinel
+func(a, b) { return b }
+```
+
+Function literals are only allowed in the file scope. They may refer to variables defined in surrounding blocks.
+
+A function must terminate with a return statement. If a function has no meaningful return value, it should return `undefined`.
+
+### Rule Expressions
+
+A rule is an expression that is evaluated lazily and the result is memoized.
+
+If the optional "when" predicate is present, the rule is evaluated only when
+the "when" boolean expression results in true. If the predicate is false,
+the rule is not evaluated and returns true. The predicate is evaluated when
+the rule would be evaluated; it is also lazy and memoized in the same way.
+
+```ebnf
+RuleExpr = "rule" [ "when" BoolExpr ] "{" Expr "}" .
+```
+
+```sentinel
+rule { x is y }
+
+rule {
+ x == "value" or
+ y == "other"
+}
+
+rule when x is y { y > 42 }
+
+rule { map ["a", "b", "c"] as _, id {
+ { "id": id }
+}}
+```
+
+### Index Expressions
+
+A primary expression of the form `a[x]` denotes the element of a list or map indexed by x. The value x is called the index or key.
+
+For `a` of type map:
+
+- If `a` contains a key `x`, `a[x]` is the map value with key `x`.
+- If `a` does not contain key `x`, `a[x]` is the `undefined` value.
+
+For `a` of type list:
+
+- `x` must be an integer
+- `x` must be in the range `[-1 * length(a), length(a)-1]`
+- If `x` is contained in the set of indices of `a`, `a[x]` is the list element at index `x`. If `x` is negative, `a[x]` is equivalent to `a[length(a)+x]`.
+- If `x` is not contained in the set of indices of `a`, `a[x]` is the `undefined` value.
+
+For `a` of value `null`:
+
+- `a[x]` is the `undefined` value.
+
+Otherwise `a[x]` is an error.
+
+### Selectors
+
+For a primary expression x, the selector expression `x.f` denotes the field `f` of the value `x`. The identifier `f` is called the selector. The type of the selector expression is the type of the selector.
+
+As a special case, selectors can be [reserved words](#keywords) and keyword [operators](#operators-and-delimiters), but cannot be any other non-identifier element.
+
+Selectors are used to access data from [imports](#imports). The first primary expression `x` in `x.f` denotes the import name. The field `f` is the selector to access data from the import.
+
+Selectors may also be used to access map data with static keys. They are syntactic sugar over index expressions. `math.pi` is equivalent to `math["pi"]` and exhibit the same limitations and behavior as an index expression. Selectors cannot, however, be used to assign values to map data.
+
+Selectors on undefined result in undefined.
+
+```sentinel
+math.pi
+time.pst.hour
+```
+
+### Slice Expressions
+
+Slice expressions construct a substring or list from a string or list.
+
+The primary expression
+
+```sentinel
+a[low : high]
+```
+
+constructs a substring or list. The indices `low` and `high` select which elements of operand `a` appear in the result. The result has indices starting at 0 and length equal to `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+```
+
+For convenience, any of the indices may be omitted. A missing `low` index defaults to zero; a missing `high` index defaults to the length of the sliced operand:
+
+```sentinel
+a[2:] // same as a[2 : length(a)]
+a[:3] // same as a[0 : 3]
+a[:] // same as a[0 : length(a)]
+```
+
+The indices are in range if `0 <= low <= high <= length(a)`, otherwise they are out of range. If the indices are out of range at run time, the result is the `undefined` value.
+
+If `a` is the value `null`, the result is the `undefined` value.
+
+If `a` is any other value type, it is an error.
+
+### Calls
+
+Given an expression `f` where `f` is a function value:
+
+```sentinel
+f(a1, a2, … an)
+```
+
+calls `f` with arguments `a1, a2, ... an`. The type of the expression is the result of `f`. The arguments are evaluated left to right. Arguments are passed by value.
+
+### Operators
+
+```ebnf
+Expression = UnaryExpr | Expression binary_op Expression .
+UnaryExpr = PrimaryExpr | unary_op UnaryExpr .
+
+binary_op = logical_op | set_op | rel_op | add_op | mul_op | else_op .
+logical_op = "and" | "or" | "xor" .
+set_op = ["not"] ( "contains" | "in" ).
+rel_op = "==" | "!=" | "<" | "<=" | ">" | ">=" |
+ "is" | "is not" | "matches" | "not matches" .
+add_op = "+" | "-" .
+mul_op = "*" | "/" | "%" .
+else_op = "else" .
+unary_op = "+" | "-" | "!" | "not" | empty_op | defined_op .
+empty_op = "is empty" | "is not empty" .
+defined_op = "is defined" | "is not defined" .
+```
+
+#### Operator Precedence
+
+Unary operators have the highest precedence.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Binary operators of the same precedence associate from left to right. For instance, x / y _ z is the same as (x / y) _ z.
+
+### Arithmetic Operators
+
+Arithmetic operators apply to numeric values and yield a result of the same type when both operands are the same.
+
+Arithmetic operations between integer and floating-point types result in a floating-point value with the integer operand treated as a floating-point value for purposes of calculation.
+
+Arithmetic operations between numeric values (integer, floating-point) and non-numeric values (strings, lists) are not permitted.
+
+All five supported arithmetic operators (+, -, \*, /, %) apply to both integer and floating-point types; + also applies to strings.
+
+```plaintext
++ sum integers, floats, strings, lists
+* difference integers, floats
+* product integers, floats
+/ quotient integers, floats
+% remainder integers, floats
+```
+
+#### Integer operators
+
+For two integer values x and y, the integer quotient `q = x / y` and remainder `r = x % y` satisfy the following relationships:
+
+```sentinel
+x = q*y + r and |r| < |y|
+```
+
+with x / y truncated towards zero.
+
+```plaintext
+ x y x / y x % y
+ 5 3 1 2
+-5 3 -1 -2
+ 5 -3 -1 2
+-5 -3 1 -2
+```
+
+As an exception to this rule, if the dividend x is the most negative value for the int type of x (-9223372036854775808), the quotient `q = x / -1` is equal to x (and r = 0).
+
+If the divisor is a constant, it must not be zero. If the divisor is zero at run time, an error occurs.
+
+#### Integer overflow
+
+For signed integers, the operations `+`, `-`, and `*` may legally overflow and the resulting value exists and is deterministically defined by the signed integer representation, the operation, and its operands. No exception is raised as a result of overflow.
+
+#### Floating-point operators
+
+For floating-point numbers, +x is the same as x, while -x is the negation of x. The result of a floating-point division by zero is not specified beyond the IEEE-754 standard.
+
+#### String Concatenation
+
+Strings can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = "hi"
+y = "hello"
+x = x + ", " + y // "hi, hello"
+x += " and good bye" // "hi, hello and good bye"
+```
+
+String addition creates a new string by concatenating the operands.
+
+#### List Concatenation
+
+Lists can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = [1, 2]
+x = x + [2, 3] // [1, 2, 2, 3]
+x += [4] // [1, 2, 2, 3, 4]
+```
+
+List addition creates a new list by concatenating the operands.
+
+### Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+In any comparison, the two operands must be equivalent types. The only exception are integers and floats, which are considered numeric and may be compared. Any comparison between other non-matching types results in the `undefined` value.
+
+The equality operators `==`, `!=`, `is`, and `is not` apply to operands that are comparable. The ordering operators `<`, `<=`, `>`, and `>=` apply to operands that are ordered. The behavior of `is` with `==` and `is not` with `!=` is identical. These terms and the result of the comparisons are defined as follows:
+
+- Boolean values are comparable. Two boolean values are equal if they are either both true or both false.
+- Integer values are comparable and ordered, in the usual way.
+- Floating point values are comparable and ordered, as defined by the IEEE-754 standard.
+- An integer compared with floating point value treats the integer as the converted floating point value.
+- String values are comparable and ordered, lexically byte-wise.
+- Lists are comparable. Lists are equal if they are of equal length and their
+ corresponding elements are comparable and equal.
+- Maps are comparable. Maps are equal if they are of equal length and both their
+ corresponding keys and values are comparable and equal.
+
+If either operand is the `undefined` value, the result of the expression is the `undefined` value.
+
+### Emptiness Comparisons
+
+Checking the emptiness of an object can be achieved by using one of the emptiness expressions, `is empty` or `is not empty`. Although similar to [Comparison Operators](#comparison-operators) in that they yield a boolean value and read as though a comparison is taking place, both `is empty` and `is not empty` are evaluated as a multi-word expression.
+
+An emptiness comparison can only be performed against collections, strings or `undefined`, with the same rules as the [built-in length](/sentinel/functions/length) function.
+
+```sentinel
+"" is empty // true
+"foo" is empty // false
+[] is empty // true
+[1] is empty // false
+{} is empty // true
+{"a": "b"} is empty // false
+
+"" is not empty // false
+"foo" is not empty // true
+[] is not empty // false
+[1] is not empty // true
+{} is not empty // false
+{"a": "b"} is not empty // true
+
+undefined is empty // undefined
+undefined is not empty // undefined
+```
+
+### Logical Operators
+
+Logical operators apply to boolean values and yield a boolean result.
+
+Logical operators are evaluated left-to-right and perform short-circuit logic. The right-hand side is not guaranteed to be evaluated if a short-circuit can be performed.
+
+```plaintext
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+### Set Operators
+
+The set operators `contains` and `in` test for set inclusion for lists
+and maps, and substring inclusion for strings.
+
+Set operators may be negated by prefixing the operator with `not`: `not contains` and `not in`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+`contains` tests if the left-hand collection contains the right-hand value. For lists, this tests equality of any value. For maps, this tests equality of any key. For strings, this tests for substring existence.
+
+`in` tests if the right-hand collection contains the left-hand value. The behavior is equivalent to `contains`.
+
+The collection must be a list, map, or string. If it is the `undefined` value, the result is the `undefined` value. For any other value, the result is an error.
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+
+"test" contains "est" // true
+"test" contains "best" // false
+"test" in "testing" // true
+"best" in "testing" // false
+```
+
+### Matches Operator
+
+The matches operator tests if a string matches a regular expression.
+
+The matches operators may be negated by prefixing the operator with `not`: `not matches`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+The left-hand value is the string to test. The right-hand value is a string value representing a regular expression.
+
+The syntax of the regular expression is the same general syntax used by Python, Ruby, and other languages. More precisely, it is the syntax accepted by RE2. The regular expression is not anchored by default; any anchoring must be explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+If either operand is `undefined`, the result is the `undefined` value. For any other non-string value, the result is an error.
+
+### Else Operator
+
+Else operators can be used to provide a default value for an expression that may evaluate to the `undefined` value. Else operators return their left-hand value unless it is `undefined`, otherwise they return their right-hand value.
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else null
+```
+
+### Quantifier Expressions (any, all, filter, map)
+
+Quantifiers are expressions that apply a predicate to a collection (list or map). The purpose of the quantifier is to produce a result based on its particular type.
+
+`any` and `all` expressions are existential and universal quantifiers, respectively. `any` expressions are equivalent to a chain of `or` and `all` expressions are equivalent to a chain of `and`. Both expressions implement short-circuiting equivalent to logical operators.
+
+The `map` expression is an apply-to-all quantifier and returns a new list for all values in the input collection. The output list will be the same length as the input collection.
+
+The `filter` expression is a subset quantifier and returns the subset of all values in the input collection for which the supplied predicate applies. This will be zero or more elements, up to the length of the input.
+
+The body of a quantifier expression is a boolean expression.
+
+`any` returns the boolean value `true` if any value in the collection expression results in the body expression evaluating to `true`. If the body expression evaluates to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression result in the body expression evaluating to `true`. If any value in the collection expression result in the body expression evaluating to `false`, the all expression returns `false`.
+
+For empty collections, `any` returns false and `all` returns `true`.
+
+`map` always returns a list, regardless of if the input collection is a list itself; maps (as in the data type) also return lists when processed through `map`. Each element is the result of the supplied expression body.
+
+`filter` returns a list or map, the same type as its input collection. Only the elements for which the expression body evaluated to `true` will be returned. If an iteration of the expression body results in `undefined`, the entire result set is `undefined`.
+
+When the collection is a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When the collection is a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+QuantExpr = QuantOp Expression "as" [ identifier "," ] identifier "{" BoolExpr "}" .
+QuantOp = "all" | "any" | "filter" | "map" .
+```
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" } // true if every iterations is true
+any group.tasks as t { t.driver is "vmware" } // true if any iteration is true
+map group.tasks as t { { "driver": t.driver } } // [{"driver": "vmware"}, ...]
+filter group.tasks as t { t.driver is "vmware" } // subset of tasks that is true
+```
+
+## Statements
+
+### Expression Statements
+
+Function call expressions may exist in the statement context.
+
+```ebnf
+ExpressionStmt = Expression .
+```
+
+### Assignments
+
+Assignments set the value of an expression to the value of another expression.
+
+```ebnf
+Assignment = AssignExpr assign_op Expression .
+AssignExpr = identifier | IndexExpr .
+assign_op = [ add_op | mul_op ] "=" .
+```
+
+The assignment `x op= y` is equivalent to `x = x op (y)` for supported values of `op`.
+
+```sentinel
+a = 1
+b[12] = 42
+c["key"] = "value"
+
+a *= 12
+b[12] += 8
+```
+
+Assignments to lists and maps can be carried out using [index expressions](#index-expressions), with the operands of the index expression being evaluated alongside the right hand side expression in a right-to-left (RHS, LHS) order.
+
+In addition to the rules detailed in the section describing their use, the following rules apply when assigning to maps or lists using index expressions:
+
+- The [variable](#variables) must exist and be a list or map. Attempts to use an index assignment on an unknown variable, or a variable that not a list or map, results in a runtime error.
+- Assigning to a list or map index that already exists overwrites that index's data with evaluated right hand side's value.
+- Assigning to an unknown key in a map creates a key for that map with the evaluated right hand side's value assigned to that key.
+- Attempting to assign a value to a list index that is out of range results in a runtime error.
+
+### If Statements
+
+If statements specify the conditional execution of two branches according to the value of a boolean expression. If the expression evaluates to true, the "if" branch is executed, otherwise, if present, the "else" branch is executed.
+
+```ebnf
+IfStmt = "if" BoolExpr Block [ "else" ( IfStmt | Block ) ] .
+```
+
+```sentinel
+if x < y {
+ return x
+} else if x > z {
+ return z
+} else {
+ return y
+}
+```
+
+### Case Statements
+
+Case statements specify a selection control mechanism to conditionally execute a branch based on expression equality.
+
+Each clause that wishes to provide an expression must use the "when" keyword. Multiple expressions can be provided, separated with a comma (",").
+
+There can be one, optional, "else" clause within a `case` statement. The "else" clause is executed if all other clauses failed to run.
+
+The clauses are evaluated in the order they are listed, with the first successful evaluation being executed.
+
+A `case` statement can optionally provide an expression. If no expression is provided, it is the equivalent of writing `case true {`.
+
+```ebnf
+CaseStmt = "case" [ Expression ] "{" { CaseWhenClause } "}" .
+CaseWhenClause = CaseWhenCase ":" StatementList .
+CaseWhenCase = "when" Expression { "," Expression } | "else" .
+```
+
+```sentinel
+case x {
+when y, z:
+ return true
+else:
+ return false
+}
+
+case {
+when x > 42:
+ return true
+else:
+ return false
+}
+```
+
+### For Statements
+
+A `for` statement specifies repeated execution of a block.
+
+The expression must evaluate to a list or a map.
+
+When iterating over a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When iterating over a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+ForStmt = "for" Expressions "as" [ identifier "," ] identifier Block .
+```
+
+```sentinel
+for [1, 2, 3] as v { count += v } // basic sum
+
+for [1, 2, 3] as idx, v {
+ if idx > 1 { count += v }
+}
+
+data = { "a": 12, "b": 32 }
+for data as k { count += data[k] } // sum map values
+for data as k, v { count += v }
+```
+
+### Break Statements
+
+A `break` statement terminates execution of the innermost `for` statement
+within the same function.
+
+```ebnf
+BreakStmt = "break" .
+```
+
+```sentinel
+// Will only print "1"
+for [1, 2, 3] as v {
+ print(v)
+ break
+}
+```
+
+### Continue Statements
+
+A `continue` statement begins the next iteration of the innermost `for` loop.
+The `for` loop must be within the same function.
+
+```ebnf
+ContinueStmt = "continue" .
+```
+
+```sentinel
+// Will print 1, 3
+for [1, 2, 3] as v {
+ if v == 2 {
+ continue
+ }
+
+ print(v)
+ break
+}
+```
+
+### Return Statements
+
+A return statement in a function F terminates the execution of F and provides the result value.
+
+```ebnf
+ReturnStmt = "return" Expression .
+```
+
+A function must terminate with a return statement.
+
+The return statement can be invoked at any time during a function to terminate execution.
+
+## Imports
+
+An import adds an assignment to the global scope to external definitions.
+
+The import declarations must only appear at the top of the source file, before any other statements. Comments may appear before imports.
+
+```ebnf
+ImportDecl = "import" Name ["as" identifier] .
+Name = string_lit .
+```
+
+An import name and identifier must be distinct. Two names may not repeated even if they're declared with different identifiers.
+
+If an identifier is not specified, the identifier is the name of the import itself.
+
+An import is not a valid value. The identifier representing the import may not be used as an expression, such as in assignment statements or call expressions.
+
+Values in imports are not assignable directly via their selectors. Function calls may be used to alter the state of the external data represented by the import. Whether or not this is supported is specific to the import implementation. The exception to this assignment restriction is the memoized data returned by a call to an import (see below).
+
+Data returned by imports can be memoized, meaning that data is returned by the import in the form of a map which is then used as much as possible without having to return to the import for more data. For example, `t = time.now` returns a map so that `t.hour` will be able to be looked up without having to go back to the import for that data.
+
+Data returned by imports may be callable, meaning that methods may be called on the memoized data returned by an import. For example, given `t = time.now`, `t.after(some_previous_time)` is valid, with the receiver data being set to the local memoized data stored in `t`. It is a valid case to accept modifications to the receiver data (example: assignment to the memoized data via index expressions), as long as all data in in the receiver continues to be string-keyed. It is an invalid case to accept receiver data with non-string-typed keys. Methods may also alter the contents of receiver data so that it is different after the call is finished.
+
+As with methods, imports may also have callable keys where the data may not be memoized. Example: in the event of `v = foo.bar`, if `v.baz` is not included in the memoized data, a call will be made to the import to fetch it. The same rules and restrictions to receiver data apply to callable keys as do to method calls.
+
+The support for callable methods and non-memoized keys on data returned by imports is specific to the import implementation.
+
+The handling of import loading is specific to the runtime implementation.
+
+Implicit imports may be added by the runtime, for example for standard library definitions. An explicit import of the same name should override a matching implicit import. For example, if "time" is an implicit import and the program specifies `import "time" as cal`, then only `cal` is defined.
+
+```sentinel
+import "time"
+import "math" as m
+```
+
+## Parameters
+
+```ebnf
+ParamDecl = "param" identifier [ "default" Expression ]
+```
+
+A parameter is a way for a policy to describe variables that are expected to be supplied by the calling application, in addition to any default value.
+
+Parameter declarations must appear at the top of the source file, following imports.
+
+Like imports, comments may appear before parameters; this is mainly pertinent when the policy is not importing anything, which would make parameters the first declarations that appear in a policy.
+
+A parameter declaration describes a valid variable that is added to the scope of a policy at runtime. This variable has the same properties and rules as other variables assigned during the course of policy evaluation as defined by the specification. This includes having a dynamic type and being able to be re-assigned during execution.
+
+Parameters may only be strings, integers, floating point numbers, booleans, lists, or maps. More complex types such as rules or functions are not allowed.
+
+A parameter can specify a default value by adding one in the declaration via use of the "default" keyword, and supplying a literal for the default value. The literal for a default is a very limited expression, comprising of:
+
+- For strings, a simple string literal;
+- For integers and floating-point numbers, either a literal denoting the value, or a unary expression with the literal, and the operators - or +;
+- For booleans, the pre-declared identifiers true or false;
+- For lists and maps, their respective literals composed of values and keys (for maps) of the above values only.
+
+Any other type of expression or identifier is not allowed.
+
+A parameter that does not have a default value defined is required by the policy. Evaluating a policy with a required parameter that has not been supplied at execution results in a runtime error.
+
+Parameters must not conflict with imports or any other identifiers currently defined in the global scope, including any reserved or pre-declared identifiers. Attempting to assign a parameter to an existing value results in a runtime error.
+
+```sentinel
+import "time"
+
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+param time default "11:00" // error, conflicts with "time" import
+param undefined // error, reserved identifier
+```
+
+## Built-in Functions
+
+Built-in functions are predeclared. They are called like any other function.
+
+### Length
+
+The built-in function `length` returns the length of a collection of string.
+
+The length of a string is the number of bytes in the string. It is not necessarilly the number of characters.
+
+The length of a collection is the number of elements in that collection.
+
+The length of `undefined` is `undefined`.
+
+### Collections
+
+#### List Append
+
+The built-in function `append` appends a value to the end of a list in-place.
+
+Appending to a non-list value results in an immediate `fail`.
+
+The return value of `append` is always the `undefined` value.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+#### Map Delete
+
+The built-in function `delete` deletes elements from a map by key. The map is modified in-place.
+
+Deleting a key that does not exist does nothing.
+
+Calling delete for a non-map value results in an immediate `fail`.
+
+The return value of `delete` is always the `undefined` value.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
+
+#### Keys and Values
+
+The built-in function `keys` and `values` return the keys and values of a map, respectively. The return value is a list. Both functions return values in an unspecified order.
+
+The `keys` or `values` of `undefined` is `undefined`.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+### Range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it defaults to `1`.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4]
+range(1, 5, 2) // [1,3]
+range(0, -3, -1) // [0,-1,-2]
+```
+
+### Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a value of that type according to the rules below.
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
+
+### Printing
+
+The built-in function `print` can be used to output formatted debug information. The runtime may omit print function calls depending on its execution mode. The runtime may choose where the output of a print function goes.
+
+`print` is a variadic function, accepting one or more values to be printed; values are separated by a single whitespace character (`" "`).
+
+The return value of `print` is always `true` to allow it to be used in boolean expressions.
+
+```sentinel
+print("hello") // "hello"
+print("hello", "world") // "hello world"
+print("The", "number", "is", 42) // "The number is 42"
+print([1, 2, 3]) // "[1, 2, 3]"
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // "false"
+```
+
+### Errors
+
+The built-in function `error` is equivalent to `print` but immediately causes execution to halt and the main rule to evaluate to `false`. The program execution is considered to have failed.
+
+## Program Execution
+
+Program execution begins by evaluating the source top to bottom. If evaluation is successful, the `main` rule is evaluated and its result is returned. Execution can be interrupted at any time by an error, which can be called explicitly or implicitly through illegal or undefined behavior.
+
+---
+
+> The content of this page is licensed under the [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
+>
+> Based on [The Go Programming Language Specification](https://golang.org/ref/spec) licensed under [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/language/undefined.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/language/undefined.mdx
new file mode 100644
index 0000000000..dc51531783
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/language/undefined.mdx
@@ -0,0 +1,97 @@
+---
+page_title: Sentinel Language - Undefined
+sidebar_current: docs-language-undefined
+description: >-
+ The value `undefined` is a special value representing undefined behavior or an
+ unknown value. Any operation on an `undefined` value results in `undefined`.
+layout: docs
+---
+
+# Language: Undefined
+
+The value `undefined` is a special value representing undefined behavior
+or an unknown value.
+
+Undefined is not an error because there are situations where the undefined
+value can be safely ignored and the language also provides construts for
+recovering from an undefined value.
+
+The undefined value can be created manually with `undefined`. In most cases,
+undefined is created by accessing a non-existent list element or value.
+The undefined value is created in a number of other circumstances. The documentation
+will note when an undefined value may be created.
+
+Almost any operation with `undefined` results in `undefined`. For example,
+performing math on undefined, attempting to call a function that is undefined,
+etc.
+
+## Main and Undefined
+
+If the value `undefined` is returned from the top-level `main` rule, then
+the policy is considered `false`. However, the runtime provides mechanisms
+for the host application to determine the policy failure was caused by
+an undefined value and report that to the user.
+
+The `main` rule returning the undefined value should be considered a logical
+error in most cases and should probably be corrected by the policy writer.
+
+## Debugging Undefined
+
+When an `undefined` value is first created (either explicitly or implicitly),
+the runtime will record the position where the undefined was created. This
+location can be used to find logic that could be erroneous.
+
+## Boolean Logic and Undefined
+
+Boolean logic is one way to safely recover from `undefined`. If boolean
+logic can safely determine a result despite an undefined value, that result
+will be returned.
+
+For example: `undefined or true` will result in `true`, because no matter
+what actual value `undefined` could've been if the policy behaved properly,
+the boolean expression would still be true.
+
+Another scenario where `undefined` can be safely ignored is short-circuit
+logic with `and`. `false and undefined` will result in `false`.
+
+The full table of logical operations on `undefined` is below:
+
+```sentinel
+undefined or true = true
+undefined or false = undefined
+undefined or undefined = undefined
+undefined and true = undefined
+undefined and false = undefined
+undefined and undefined = undefined
+undefined xor true = undefined
+undefined xor false = undefined
+undefined xor undefined = undefined
+
+// Short-circuit examples
+false or true or undefined = true
+false or undefined or true = true
+true and false and undefined = false
+true and undefined and false = undefined
+```
+
+## Else Expressions
+
+The `else` expression allows explicit recovery from undefined and is useful
+for setting default values.
+
+`else` is an operator where the left and right hand side are values. If the
+left-hand value is `undefined`, the right-hand value is returned. Otherwise,
+the left-hand value is returned.
+
+Examples:
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else "default"
+
+// In more complex scenarios
+
+foo() else 42 == 12
+a = config.value else "default"
+```
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/language/values.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/language/values.mdx
new file mode 100644
index 0000000000..f932480340
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/language/values.mdx
@@ -0,0 +1,210 @@
+---
+page_title: Sentinel Language - Values
+sidebar_current: docs-language-values
+description: Values are the data that Sentinel policies operate on.
+layout: docs
+---
+
+# Language: Values
+
+Values are the _data_ that Sentinel policies operate on. You can create this
+data yourself, for example by just typing the number `42`, or you may access
+this data from an external source to make policy decisions.
+
+Values have a _type_, which determines what kind of operations can be performed
+on the data. Example types are booleans (true and false), numbers,
+and strings (text).
+
+This page documents all the available value types. You can also
+[convert between types](#type-conversion).
+
+## Boolean
+
+A boolean is a value that is either true or false.
+
+A boolean value is created literally with `true` or `false`.
+
+As a policy language, booleans are central to the behavior of Sentinel.
+Booleans are used as conditions in [if statements](/sentinel/language/conditionals),
+are the result of [rules](/sentinel/language/rules), and more. The ultimate
+result of a Sentinel policy is true or false.
+
+## Integer
+
+An integer is a whole number.
+
+An integer is a 64-bit value. This means it can represent numbers from
+-9,223,372,036,854,775,808 to 9,223,372,036,854,775,808.
+
+Integers are created by typing them out literally with no
+separators (such as a comma). An optional prefix can set a non-decimal base:
+`0` for octal, and `0x` for hexadecimal. In hexadecimal literals, letters
+`a-f` and `A-F` represent values 10 to 15.
+
+Example integers are shown below:
+
+```sentinel
+42
+0600
+0xBadFace
+170141183460469
+```
+
+Integers are used for math and numerical comparison.
+
+## Float
+
+A float is a number with a decimal point. It has an integer part, a decimal
+point, a fractional part, and optionally an exponent part. Example
+floats are shown below:
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+Floats are IEEE-754 64-bit floating point numbers.
+
+## String
+
+Strings are text values.
+
+Strings are created by wrapping text in double quotes, such as `"hello"`.
+Within the quotes, any character may appear except newline and an unescaped
+double quote. The text between the quotes is the value.
+
+Because Sentinel policies must be UTF-8 encoded text, strings themselves are
+UTF-8 encoded text. This means you can put any value UTF-8 character into
+a string, such as `"日本語"`.
+
+You can have a string with a literal double-quote by _escaping_ it with
+a backslash. For example: `"they said \"hello\""` turns into the value
+`they said "hello"`.
+
+Backslash escapes can be used for much more than only escaping a double quote.
+Newlines are `\n`, a literal backslash is `\\`, and many more. The full list
+of available backslash escapes can be found
+[in the language specification](/sentinel/language/spec#string-literals).
+
+Example strings:
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+Strings do not support indexing, however it is possible to achieve a similar
+result through the combination of the [strings](/sentinel/imports/strings)
+standard import and list indexing.
+
+```sentinel playground
+import "strings"
+
+asciistr = "Hello, world!"
+utf8str = "日本語"
+
+utf8split = rule {
+ strings.split(utf8str, "")[2] == "語"
+}
+
+asciisplit = rule {
+ strings.split(asciistr, "")[5] == ","
+}
+
+main = rule { utf8split and asciisplit }
+```
+
+Strings support [slicing](/sentinel/language/slices) to efficiently create
+substrings.
+
+## List
+
+See [Lists](/sentinel/language/lists).
+
+## Map
+
+See [Maps](/sentinel/language/maps).
+
+## Rule
+
+See [Rules](/sentinel/language/rules).
+
+## Function
+
+See [Functions](/sentinel/language/functions).
+
+## Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a
+value of that type. Some examples are shown below followed by a list of exact
+rules for type conversion.
+
+```sentinel
+int(42) // 42
+int("42") // 42
+int(42.8) // 42
+int(true) // 1
+
+float(1.2) // 1.2
+float(1) // 1.0
+float("4.2") // 4.2
+float(true) // 1.0
+
+string("foo") // "foo"
+string(88) // "88"
+string(0xF) // "15"
+string(true) // "true"
+
+bool("true") // true
+bool(1) // true
+bool(-1) // true
+bool(0.1) // true
+bool("false") // false
+bool(0) // false
+```
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/language/variables.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/language/variables.mdx
new file mode 100644
index 0000000000..7516890212
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/language/variables.mdx
@@ -0,0 +1,99 @@
+---
+page_title: Sentinel Language - Variables
+sidebar_current: docs-language-vars
+description: Variables store values that can be used later.
+layout: docs
+---
+
+# Language: Variables
+
+Variables store values that can be used later.
+
+Most notably, a variable assignment of `main` is required for all
+Sentinel policies. This is the main [rule](/sentinel/language/rules) that
+is executed to determine the result of a policy. The `main` rule may
+use other variables that are assigned in the policy.
+
+Example:
+
+```sentinel
+a = 1
+b = a + 1
+```
+
+In this example, two variables are assigned: `a` and `b`. `a` is given
+the value of "1" and `b` uses the `a` to calculate its own value.
+
+## Syntax
+
+The syntax for assigning a variable is:
+
+```sentinel
+IDENTIFIER = VALUE
+```
+
+On the left of the equal sign is an _identifier_. This is a name for your
+variable and will be how you reference it later. An identifier is any
+combination of letters and digits, but must start with a letter. An
+underscore `_` is also a valid letter.
+
+On the right is any valid Sentinel value or expression. A value is a
+literal value such as a number or string. An expression is some computed
+value such as doing math, calling a function, etc.
+
+## Assignment and Reassignment
+
+A variable is _assigned_ when it is given a value. You can also _reassign_
+variables at any time by setting it to a new value. The new value takes
+effect for any subsequent use of that variable. A variable can be reassigned
+to a different type.
+
+For example:
+
+```sentinel
+a = 1 // a = 1
+b = a // b = 1
+a = "value" // a = "value", b = 1
+c = a // c = "value", b = 1
+```
+
+In the above example you can see that the variables are set and reassigned.
+Notice that the value of a variable is the _current_ value, and that reassigning
+a variable only affects _future_ uses of that variable. You can see this with
+`c` and `b` being different values.
+
+## Unassigned Variables
+
+Using a variable that is _unassigned_ is an error.
+
+In the example below, the first line would result in the policy erroring.
+Sentinel is executed top-down and the value of `c` is not available yet
+on the first line.
+
+```sentinel
+a = c // Error!
+c = 1
+```
+
+## Type Conversion
+
+While a topic more related to [values][lang-values], variables can be assigned
+(or-reassigned) a different value type via type conversion.
+
+[lang-values]: /sentinel/language/values
+
+```sentinel
+s = "1.1"
+a = int(s) // a = 1
+a = float(s) // a = 1.1
+
+a = 1
+s = string(a) // s = "1"
+```
+
+For more details, see the type conversion sections in the
+[values][lang-values-type-conversion] page, or the [Sentinel Language
+Specification][lang-spec-type-conversion].
+
+[lang-values-type-conversion]: /sentinel/language/values#type-conversion
+[lang-spec-type-conversion]: /sentinel/language/spec#type-conversion
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/nomad.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/nomad.mdx
new file mode 100644
index 0000000000..a105b0337d
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/nomad.mdx
@@ -0,0 +1,51 @@
+---
+page_title: Nomad and Sentinel
+sidebar_current: docs-app-nomad
+description: >-
+ Nomad Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies can currently execute on job
+ submission (creation, update).
+layout: docs
+---
+
+# Nomad
+
+Nomad is a simple, flexible scheduler and workload orchestrator.
+[Nomad Enterprise](https://www.hashicorp.com/products/nomad/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies can currently execute on job submission (creation, update).
+
+Sentinel policies have full access to the job structure. This allows the
+Sentinel policy to control behavior based on any attribute within a job,
+such as the driver, resource requests, network configuration, volume
+configuration, and more. The information that Sentinel policies have access
+to will expand over time.
+
+Nomad fully supports all [enforcement levels](/sentinel/concepts/enforcement-levels).
+For soft mandatory policies, the `sentinel-override` capability must be
+available on the user's ACL policy to allow override. Overrides are always
+logged.
+
+The Nomad integration with Sentinel is documented in depth in the
+[Nomad Enterprise documentation](https://www.nomadproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Only allow Docker-based jobs.**
+
+```sentinel
+# Test policy only allows Docker based tasks
+main = rule { all_drivers_docker }
+
+# all_drivers_docker checks that all the drivers in use are Docker
+
+all_drivers_docker = rule {
+ all job.task_groups as tg {
+ all tg.tasks as task {
+ task.driver is "docker"
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/terraform.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/terraform.mdx
new file mode 100644
index 0000000000..5922fd0cfb
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/terraform.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Terraform and Sentinel
+sidebar_current: docs-app-terraform
+description: >-
+ Sentinel can be used with Terraform and Terraform Enterprise to enforce policy
+ on Terraform configurations, states, and plans.
+layout: docs
+---
+
+# Terraform
+
+[Terraform Enterprise](https://www.hashicorp.com/products/terraform/) uses Sentinel to enforce
+policy on [Terraform](https://www.terraform.io) configurations, states, and plans.
+
+The Sentinel integration with Terraform runs within
+[Terraform Enterprise](https://www.hashicorp.com/products/terraform/)
+after a `terraform plan` and before a `terraform apply`. The policies
+have access to the created plan, the state at the time of the plan,
+and the configuration at the time of the plan.
+
+The Terraform integration with Sentinel is documented in depth in the [Terraform Enterprise documentation](https://www.terraform.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show basic examples.
+
+### Examples
+
+**Example: All AWS instances must have a tag**
+
+```sentinel
+import "tfplan"
+
+main = rule {
+ all tfplan.resources.aws*instance as *, instances {
+ all instances as \_, r {
+ (length(r.applied.tags) else 0) > 0
+ }
+ }
+}
+```
+
+**Example: Only allow GCP instance sizes smaller than `n1-standard-16`**
+
+```sentinel
+import "tfplan"
+
+allowed_machine_types = [
+ "n1-standard-1",
+ "n1-standard-2",
+ "n1-standard-4",
+ "n1-standard-8",
+]
+
+main = rule {
+ all tfplan.resources.google_compute_instance as _, instances {
+ all instances as _, r {
+ r.applied.machine_type in allowed_machine_types
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/vault.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/vault.mdx
new file mode 100644
index 0000000000..6db2f8af22
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/vault.mdx
@@ -0,0 +1,64 @@
+---
+page_title: Vault and Sentinel
+sidebar_current: docs-app-vault
+description: >-
+ Vault Enterprise uses Sentinel to augment the built-in policy system to
+ provide Role Governing Policies (RGPs) and Endpoint Governing Policies (EGPs)
+ to enable complex, flexible policies across identities and endpoints.
+layout: docs
+---
+
+# Vault
+
+[Vault Enterprise](https://www.hashicorp.com/products/vault/) uses Sentinel
+to augment the built-in policy system to provide Role Governing Policies (RGPs)
+and Endpoint Governing Policies (EGPs) to enable complex, flexible policies
+across identities and endpoints.
+
+**Role Governing Policies (RGPs)** are Sentinel policies that are tied to
+particular tokens, Identity entities, or Identity groups. They have access to
+a rich set of controls across various aspects of Vault. These are evaluated
+whenever a token they're attached to is used.
+
+**Endpoint Governing Policies (EGPs)** are Sentinel policies that are tied to
+particular paths instead of tokens. They have access to as much request
+information as possible, but they can take effect even on unauthenticated
+paths, such as login paths.
+
+The Vault integration with Sentinel is documented in depth in the
+[Vault Enterprise documentation](https://www.vaultproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Endpoint policy that requires MFA authentication from a corporate network.**
+
+```sentinel
+import "sockaddr"
+
+# We expect logins to come only from our private IP range
+cidrcheck = rule {
+ sockaddr.is_contained(request.connection.remote_addr, "10.20.0.0/16")
+}
+
+# Require Ping MFA validation to succeed
+ping_valid = rule {
+ mfa.methods.ping.valid
+}
+
+main = rule when request.path is "auth/ldap/login" {
+ ping_valid and cidrcheck
+}
+```
+
+**Example: Endpoint policy that disallows tokens created before a certain time.**
+
+```sentinel
+import "time"
+import "strings"
+
+main = rule when not strings.has_prefix(request.path, "auth/ldap/login") {
+ time.load(token.creation_time).unix > time.load("2017-09-17T13:25:29Z").unix
+}
+```
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/writing/basic.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/writing/basic.mdx
new file mode 100644
index 0000000000..b62533466e
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/writing/basic.mdx
@@ -0,0 +1,113 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-basic
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Policy Basics
+
+Sentinel policies are easy to write while still supporting advanced constructs
+for creating complex policies. This page will explain the basics of writing
+Sentinel policies to get started. You don't need any prior Sentinel knowledge,
+but we do recommend reading the
+[getting started guide](/sentinel/intro/getting-started/first-policy) and
+[language guide](/sentinel/language) after this.
+
+Sentinel policies are text files written using the [Sentinel language](/sentinel/language).
+The policies are evaluated top-to-bottom. The value of `main` after execution
+determines whether a policy passes or fails.
+
+## The Simplest Policy
+
+Sentinel only requires that a policy have a `main` variable that evaluates to
+a boolean value.
+
+A valid example is shown below:
+
+```sentinel playground
+main = 10 > 5
+```
+
+This type of minimal policy is not purely academic. In practice, simple
+policies can often be reduced to a single line logical statement resulting
+in `true` or `false`. However, the expression is usually wrapped in a
+[rule](/sentinel/language/rules) for testing reasons.
+
+You can verify Sentinel will execute this minimal policy using the CLI:
+
+```
+$ sentinel apply minimal.sentinel
+Pass
+```
+
+## Logical Expressions
+
+Policy is at its core a set of logic: you can or can not perform some action
+under a certain set of circumstances. Those circumstances are logical
+expressions. Therefore, Sentinel policies primarily translate into logical
+expressions.
+
+Detailed documentation on boolean expressions is
+[available in the language guide](/sentinel/language/boolexpr).
+
+A simple numerical comparison was seen in the first example on this page.
+Sentinel also provides inclusion operators such as `contains`, `any`, `all`, and
+more. Sentinel allows some operators to have aliases to promote readability
+while remaining programmer-familiar, such as `==` which can equivalently be `is`.
+
+The example below verifies that all numbers in a list are even:
+
+```sentinel playground
+numbers = [6, 22, 8, 4, 12]
+main = all numbers as n { n % 2 is 0 }
+```
+
+## Variables
+
+A policy will very often use variables. Applications such as
+[Nomad](https://www.nomadproject.io) inject variables into the global
+scope of a policy for making policy decisions. For example, Nomad injects
+the job that is being run into the policy scope. Knowing how to use
+variables is critical to effectively using Sentinel.
+
+Detailed documentation on how to define and access variables is
+[available in the language guide](/sentinel/language/variables).
+
+Variables can be defined and used explicitly. For example:
+
+```sentinel playground
+value = 10
+main = value > 5
+```
+
+But they may also be introduced implictly by the host system. Nomad
+injects `job` into policies to describe the job that is being run. The
+policy below is a valid policy that requires a job have two task groups.
+Notice that `job` is not defined anywhere. It is implicitly inserted by
+the host application (in this case Nomad). Refer to the application you're
+writing policy for to determine if it implicitly inserts values.
+
+```json sentinel
+{
+ "policy": "main = length(job.task_groups) is 2",
+ "globals": {
+ "job": {
+ "task_groups": ["foo", "bar"]
+ }
+ }
+}
+```
+
+## And more!
+
+The Sentinel language supports many more features such as functions,
+loops, and more. You can learn about all of this in the
+[complete language guide](/sentinel/language).
+
+The other pages in the [writing policy](/sentinel/writing)
+will cover other information you need to know about writing Sentinel
+policies that isn't simply a language reference.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/writing/debugging.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/writing/debugging.mdx
new file mode 100644
index 0000000000..7dc284d222
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/writing/debugging.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-debugging
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Debugging
+
+As policies become more complex, it becomes harder to understand exactly why
+a policy may be behaving differently than you expect.
+
+Prior to actively debugging, there are multiple best practices we recommend
+following. We recommend breaking your policy down into a set of
+[rules](/sentinel/writing/rules). This will make it easier to understand
+[traces](/sentinel/writing/tracing) and make it easier to
+[test](/sentinel/writing/testing) your policies.
+This should help to debug policies up to a significant complexity.
+
+## Print Logging
+
+The built-in [print function](/sentinel/language/logging-errors#print)
+can be used to log data. The print output is only shown during failures
+or when [tracing is explicitly enabled](/sentinel/writing/tracing).
+
+The `print` function outputs each argument, which can be of any type, converted
+to a human-friendly string format. Arguments are separated with a single space.
+
+Example:
+
+```sentinel playground
+value = 42
+print("the number is", value) // the number is 42
+
+object = { "foo": false }
+print(object) // { "foo": false }
+
+main = rule { true }
+```
+
+The `print` function returns the value `true` so that it can also be
+used within rule expressions. Note that the `print` function should be
+used at the beginning of statements otherwise they may never be
+reached. This is due to internal performance techniques within Sentinel
+like [short-circuiting](https://en.wikipedia.org/wiki/Short-circuit_evaluation).
+
+Example:
+
+```sentinel playground
+// rule_a and rule_b will evaluate to false
+rule_a = rule { print("rule_a is printed") and false } // "rule_a is printed"
+rule_b = rule { false and print("rule_b is printed") } // Nothing is printed
+
+// rule_c and rule_d will evaluate to true
+rule_c = rule { print("rule_c is printed") or true } // "rule_c is printed"
+rule_d = rule { true or print("rule_d is printed") } // Nothing is printed
+
+main = rule { rule_a or rule_b or rule_c and rule_d }
+```
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/writing/imports.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/writing/imports.mdx
new file mode 100644
index 0000000000..74ac751ee4
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/writing/imports.mdx
@@ -0,0 +1,117 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom import](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only local
+context for making policy decisions.
+
+Sentinel also comes with a set of [standard imports](/sentinel/imports).
+Standard imports are available to every Sentinel policy to help policy writers
+with common tasks such as working with the time, network addresses, and more.
+
+-> **This page is about writing policies that _use_ imports.** If you're
+interested in _creating_ a new import, please see the section on [extending
+Sentinel](/sentinel/extending) for information on how to write modules and
+import plugins.
+
+## Using Imports
+
+To use an import, you use the `import` keyword at the top of your policy. This
+specifies the name of the import you want to use. The application you're writing
+the policy for must already be
+[configured](/sentinel/configuration#imports) to provide that
+import.
+
+Details on imports can be found in the
+[import section in the language reference](/sentinel/language/imports).
+
+In the example below, we use the [time](/sentinel/imports/time) import:
+
+```sentinel playground
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+There are two options to develop a policy locally when using imports:
+configure Sentinel to launch the import on `apply`, or mock the import
+using `mock`. The former requires access to the import while the
+latter is faster (doesn't have to launch a process for plugins) and
+doesn't require the import.
+
+## Mocking Imports
+
+The first option to developing policies locally is to mock the import values.
+When mocking an import, you don't need the import to be available. This can be
+useful since some imports may not be available as a plugin and may only be
+available to the application the policy runs in.
+
+Mocks are specified via the [configuration
+file](/sentinel/configuration). Mocks can also be used for
+[testing](/sentinel/writing/testing).
+
+You can supply mock configuration one of two ways, depending on your use case:
+
+- **[Using static data](/sentinel/configuration#mocking-static-data):**
+ Use this method when you can accurately represent your mock data in JSON and
+ do not need to mock complex Sentinel features such as functions.
+- **[Using Sentinel code](/sentinel/configuration#mocking-with-sentinel-code):**
+ Use this method when using a static JSON object is insufficient, such as when
+ you need to mock functions or other complex Sentinel features.
+
+Our example does not require complex data to be mocked, so a static object
+is sufficient:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ hour = 12
+ weekday_name = "Tuesday"
+ }
+ }
+}
+```
+
+This can be used via the CLI:
+
+```
+$ sentinel apply -config=config.hcl policy.sentinel
+Pass
+```
+
+## Launching Import Plugins
+
+If you have access to the plugin binary, you can launch the import. The
+benefit of this is that it is really using the import to test your
+policy. If the import changes, your policies may start failing. If you
+only use mock data and the import changes, your policies will still
+appear to work.
+
+Imports are configured in the configuration file:
+
+```hcl
+import "plugin" "custom_time" {
+ source = "/path/to/sentinel-time-import"
+}
+```
+
+This would require the `sentinel-time-import` binary. For this example
+this doesn't currently exist. We plan on writing one to provide for this
+section of the documentation.
+
+## Custom Imports
+
+You can also [create your own imports](/sentinel/extending).
+
+If your policy decisions could benefit from accessing external information,
+then you can use custom imports as a way to do this.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/writing/index.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/writing/index.mdx
new file mode 100644
index 0000000000..064069d389
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/writing/index.mdx
@@ -0,0 +1,35 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing
+description: >-
+ Sentinel provides a language and workflow for building policy across any
+ system that embeds Sentinel. By learning Sentinel once, you are able to
+ effectively control access to many systems using Sentinel's powerful features.
+ Basic concepts that are important to understand for Vault usage.
+layout: docs
+---
+
+# Writing Sentinel Policy
+
+This section covers how to write Sentinel policies.
+
+It is recommended that you complete the
+[getting started guide](/sentinel/intro/getting-started/first-policy) prior to
+reading this section of the documentation. The getting started guide will
+explain all the basics of writing and testing policies. This section of the
+documentation will serve as more of a reference guide to all available
+features of Sentinel.
+
+Sentinel provides a language and workflow for building policy across any system
+that embeds Sentinel. By learning Sentinel once, you are able to effectively
+control access to many systems using Sentinel's powerful features.
+
+Sentinel also provides a [local CLI](/sentinel/commands) for developing and testing
+Sentinel policies. This CLI can be integrated into a daily developer's workflow
+along with CI to treat [policy as code](/sentinel/concepts/policy-as-code).
+
+Sentinel uses its own [language](/sentinel/language) for writing
+policies. You can view a [language reference](/sentinel/language)
+as well as the [specification](/sentinel/language/spec) for details. You
+don't have to read those documents immediately, since the language should
+be easy enough to pick up throughout this section.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/writing/rules.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/writing/rules.mdx
new file mode 100644
index 0000000000..ab4fc69d08
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/writing/rules.mdx
@@ -0,0 +1,65 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-rules
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Rules
+
+Rules form the basis of a policy by representing behavior that is either passing
+or failing. Rules are a first class language construct in Sentinel. A policy can
+and should be broken down into rules to aid with readability, testability, and
+performance.
+
+Rules provide:
+
+- **Readability:** A policy that is broken down into rules can be read
+ more easily. The logic becomes clearer to see for policy writers when
+ they come back to it.
+
+- **Reportability:** When [tracing](/sentinel/writing/tracing) is enabled,
+ rules form the foundation of the data returned. All evaluated rules are added
+ to the trace with the data the rule evaluated to, and their position within
+ policy or module source files.
+
+- **Testability:** [Policy testing](/sentinel/writing/testing) is
+ built on asserting the values of rules. By breaking logic down into
+ rules, you're able to more effectively test your policies.
+
+- **Performance:** Rules are only evaluated on demand, and are also only
+ evaluated once. This means that a rule referenced multiple times only has a
+ one-time performance cost. For complex logic, this could result in improved
+ performance.
+
+An example usage of rules is shown below:
+
+```json sentinel
+{
+ "policy": "is_sunny = rule { weather is \"sunny\" }\nis_wednesday = rule { day is \"wednesday\" }\nmain = rule { is_sunny and is_wednesday }",
+ "globals": {
+ "weather": "sunny",
+ "day": "wednesday"
+ }
+}
+```
+
+Rules can also contain non-boolean data. This can be useful for passing more
+detail to the caller than an opaque boolean value would be able to communicate.
+
+```sentinel playground
+days = [{"weather": "sunny"}]
+main = rule {
+ filter days as d {
+ d.weather is not "sunny"
+ }
+}
+```
+
+Details about rules and their behavior can be found in
+[the language reference](/sentinel/language/rules). Rules have important
+behavior so we recommend reading the language reference on rules.
+Rules will be used throughout the remainder of the documentation.
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/writing/testing.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/writing/testing.mdx
new file mode 100644
index 0000000000..61ab0ea46c
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/writing/testing.mdx
@@ -0,0 +1,491 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-testing
+description: >-
+ Sentinel has a built-in test framework to validate a policy behaves as
+ expected.
+layout: docs
+---
+
+# Testing
+
+Sentinel has a built-in test framework to validate a policy behaves as expected.
+
+With an ever-increasing amount of automation surrounding technology,
+the guardrails provided by policies are a critical piece towards ensuring
+expected behavior. As a reliance on correct policy increases, it is important
+to test and verify policies.
+
+Testing is a necessary step to fully realize
+[policy as code](/sentinel/concepts/policy-as-code). Just as good software
+is well tested, a good set of policies should be equally well tested.
+
+## Test Folder Structure
+
+Policies are tested by asserting that [rules](/sentinel/writing/rules)
+are the expected values given a pre-configured input. Tests are run
+by executing the [test command](/sentinel/commands/test).
+
+Sentinel is opinionated about the folder structure required for tests.
+This opinionated structure allows testing to be as simple as running
+`sentinel test` with no arguments. Additionally, it becomes simple to test
+in a CI or add new policies.
+
+The structure Sentinel expects is `test//*.[hcl|json]` where ``
+is the name of your policy file without the file extension. Within that folder
+is a list of HCL or JSON files. Each file represents a single test case.
+Therefore, each policy can have multiple tests associated with it.
+
+-> Note that the primary configuration file format for Sentinel is HCL. While
+you can write configuration files in a HCL-equivalent JSON, we only discuss the
+use of HCL on this page.
+
+## Test Case Format
+
+Each HCL file within the test folder for a policy is a single test case.
+
+The file is the same configuration format as the [CLI configuration
+file](/sentinel/configuration). The format lets you define mock data, imports to
+use, and more. This mock data is the key piece in being able to test policies:
+you craft a specific scenario and assert your policy behaves as you expect.
+
+Test cases also use the `test` block within the configuration file to assert the
+value of [rules](/sentinel/writing/rules). If the `test` key is omitted, the
+policy is expected to pass. If the test key is specified, only the rules
+specified in the map will be asserted. This means if you omit `main`, then the
+final policy result is not asserted.
+
+Example with assertions:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+
+test {
+ rules = {
+ main = false
+ is_open_hours = false
+ is_weekday = true
+ }
+}
+```
+
+The configuration above specifies some parameter data, and asserts the result of
+some rules. This is the same configuration used in the example section below.
+
+## Example
+
+Lets use the following file as an example. Save this file to a directory
+and name it `policy.sentinel`. It can be named anything with the `sentinel`
+extension, but by naming it `policy.sentinel` your output should match
+the example output on this page.
+
+```sentinel
+// The day of the week.
+param day
+
+// The hour of the day.
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+### A Passing Test
+
+Next, let's define a single test case. Relative to where you saved
+the policy, create a file at the path `test/policy/good.hcl`.
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 14
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/good.hcl
+```
+
+This passed because the policy passed. We didn't assert any specific
+rules. By not specifying any assertions, `test` expects the policy itself
+to fully pass.
+
+### A Failing Test
+
+Define another test case to fail. We want to verify our policy fails when expected, too.
+
+Save the following as `test/policy/7-am.hcl`:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+FAIL - policy.sentinel
+ FAIL - test/policy/7-am.hcl
+ expected "main" to be true, got: false
+
+ trace:
+ policy.sentinel:9:1 - Rule "main"
+ bool: false
+
+ policy.sentinel:8:1 - Rule "is_open_hours"
+ bool: false
+
+ PASS - test/policy/good.hcl
+```
+
+As you can see, the test fails because "main" is false. This is good
+because the policy _should_ have failed since we specified an invalid
+hour. But, we expect main to be false and don't want our test to fail!
+Update `7-am.hcl` to add test assertions:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+
+test {
+ rules = {
+ main = false
+ is_open_hours = false
+ }
+}
+```
+
+And when we run the tests:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/7-am.hcl
+ PASS - test/policy/good.hcl
+```
+
+The test passes. We asserted that we expect the `main` rule to be false,
+the `is_open_hours` rule to be false, and the `is_weekday` rule to be
+true. By asserting some rules are true, we can verify that our policy
+is failing for reasons we expect.
+
+## Mocking
+
+The above example demonstrates how to test by supplying different
+[parameters](/sentinel/language/parameters). Parameters in a policy can be
+specifically useful when you want to control user-defined input values to a
+policy.
+
+However, generally, when testing, you will need mimic the conditions you will
+see in production. Production implementations of Sentinel will supply data using
+one of two methods:
+
+- **Global data:** Data is injected directly into the policy's scope and is
+ accessible using normal identifiers, similar to variables.
+- **Imports:** Data is stored behind an [import](/sentinel/language/imports)
+ and loaded on demand as needed by the policy author.
+
+Proper testing of a policy requires that these values be able to be _mocked_ -
+or, in other words, simulated in a way that allows the accurate testing of the
+scenarios that a policy could reasonably pass or fail under.
+
+Mocking both globals and imports can be done by setting various parts of the
+configuration file.
+
+### Mocking Globals
+
+Demonstrating the mocking of globals can be seen by making a few modifications to
+our example policy, removing the `param` declarations:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Then, change the `param` section in the configuration file to
+[`global`](/sentinel/configuration#global).
+
+```json
+global "day" {
+ value = "monday"
+}
+
+global "hour" {
+ value = 14
+}
+```
+
+This test should still pass, as if nothing had happened, although what we've
+done is shifted our parameters to globals, simulating an environment where `day`
+and `hour` are already defined for us.
+
+### Mocking Imports
+
+To mock imports, we need to use the
+[`mock`](/sentinel/configuration#mock-imports) section of the
+configuration file.
+
+Let's say the above example is behind an import named `time`.
+
+-> **NOTE:** [`time`](/sentinel/imports/time) is a valid standard import.
+This example may not be accurate to the
+import's syntax.
+
+The code now looks like this:
+
+```sentinel
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To mock this import, we can [mock it as static
+data](/sentinel/configuration#mocking-static-data). The configuration
+file now looks like, without assertions:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ weekday_name = "Monday"
+ hour = 14
+ }
+ }
+}
+```
+
+The policy will now pass, with the `time` import mocked.
+
+Data can also be [mocked as Sentinel
+code](/sentinel/configuration#mocking-with-sentinel-code). In this case,
+the above configuration file would look like:
+
+```hcl
+mock "time" {
+ module {
+ source = "mock-time.sentinel"
+ }
+}
+```
+
+And a file named `mock-time.sentinel` would now hold your mock values:
+
+```sentinel
+day = "monday"
+hour = 14
+```
+
+Mocking as Sentinel code allows more complex details to be mocked as well, such
+as functions. Say we wanted to mock the `time.load()` function. To mock this,
+just add it to the `mock-time.sentinel` file:
+
+```sentinel
+load = func(_) {
+ return {
+ "weekday_name": "Monday",
+ "hour": 14,
+ }
+}
+```
+
+Your code can now be written as:
+
+```sentinel
+import "time"
+
+t = time.load("a_mock_timestamp")
+
+is_weekday = rule { t.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { t.hour > 8 and t.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To see more details, see the [Mock
+Imports](/sentinel/configuration#mock-imports) section in the
+configuration file.
+
+## Asserting Non-Boolean Rules
+
+Non-boolean rules can also be asserted by `sentinel test`.
+
+To assert non-boolean values, simply enter the expected value into the rule
+contents:
+
+```hcl
+param "maintenance_days" {
+ value = [
+ {
+ day = "wednesday"
+ hour = 9
+ },
+ {
+ day = "friday"
+ hour = 1
+ },
+ {
+ day = "sunday"
+ hour = 1
+ },
+ ]
+}
+
+test {
+ rules = {
+ main = [
+ {
+ day = "wednesday"
+ hour = 9
+ },
+ ]
+ }
+}
+```
+
+This would assert a policy checking for violations of a maintenance policy,
+saying that maintenance hours should happen before 6AM on any given day:
+
+```sentinel
+param maintenance_days
+
+main = rule {
+ filter maintenance_days as d {
+ d.hour >= 6
+ }
+}
+```
+
+## Test JSON Output
+
+Running `sentinel test` with the `-json` flag will give you the test results in
+a JSON output format, suitable for parsing by reporting software.
+
+-> **Note:** The JSON output format is currently under development and the
+format may change at a later time. Additionally, support for other well-known
+formats, such as JUnit, may become available in the future.
+
+The current format is an object with the only top-level key being `policies`,
+with each test grouped up by policy being run.
+
+The policy result fields are:
+
+- `path`: The path of the policy and the index of the test result in the
+ `policies` field in the root object.
+- `status`: A string representation of the policy's test status as a whole. Can
+ be one of `PASS`, `FAIL`, `ERROR`, or `?`. The final status, `?`, represents a
+ policy that has no tests to process, and acts like a passing test.
+- `errors`: An array of any error messages encountered during processing the
+ policy for testing. Usually reserved for policy file or parser-related errors.
+ For case-specific errors, see the error field for the particular case.
+- `cases`: A map of case results, indexed by case path.
+
+The case result fields are:
+
+- `path`: The path of the test case and the result's index in the `cases` field
+ in the policy object.
+- `status`: A string representation of the case's test status. Can be one of
+ `PASS`, `FAIL` or `ERROR`.
+- `errors`: An array of any error messages encountered during running this test
+ case.
+- `trace`: The trace for this policy in JSON format. See the
+ [tracing](/sentinel/writing/tracing) page for more details.
+- `rule_detail`: When the `status` of this test case is `FAIL`, contains an
+ object of assertion failure detail, indexed by rule. Only failures are counted
+ here; any rules not found here can be assumed to have passed or not asserted.
+- `config_warnings`: An array of strings denoting any configuration warnings
+ found while processing the configuration for this test case.
+- `config_legacy`: Denotes whether or not the configuration is a legacy JSON
+ configuration and needs to be modernized. This field may be removed in future
+ releases.
+
+A passing example is shown below:
+
+```json
+{
+ "policies": {
+ "policy.sentinel": {
+ "path": "policy.sentinel",
+ "status": "PASS",
+ "errors": null,
+ "cases": {
+ "test/policy/pass.hcl": {
+ "path": "test/policy/pass.hcl",
+ "status": "PASS",
+ "errors": null,
+ "trace": {
+ "description": "A very basic policy to determine if a run is within working hours.",
+ "error": null,
+ "print": "",
+ "result": true,
+ "rules": {
+ "is_open_hours": {
+ "desc": "Passes if run during business hours.",
+ "ident": "is_open_hours",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 290,
+ "line": 13,
+ "column": 1
+ },
+ "value": true
+ },
+ "is_weekday": {
+ "desc": "Passes if the day does not fall on the weekend.",
+ "ident": "is_weekday",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 193,
+ "line": 10,
+ "column": 1
+ },
+ "value": true
+ },
+ "main": {
+ "desc": "",
+ "ident": "main",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 338,
+ "line": 14,
+ "column": 1
+ },
+ "value": true
+ }
+ }
+ },
+ "rule_detail": {},
+ "config_warnings": null,
+ "config_legacy": false
+ }
+ }
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.22.x/content/sentinel/docs/writing/tracing.mdx b/content/sentinel/v0.22.x/content/sentinel/docs/writing/tracing.mdx
new file mode 100644
index 0000000000..f2069bd682
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/docs/writing/tracing.mdx
@@ -0,0 +1,401 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-tracing
+description: >-
+ Sentinel provides execution traces to better understand the execution of a
+ policy.
+layout: docs
+---
+
+# Traces
+
+Sentinel provides execution traces to better understand the execution of a policy.
+
+When using the Sentinel [CLI](/sentinel/commands), traces are shown
+on policy failure during `apply` or `test`, or when the `-trace` flag is
+explicitly specified. For Sentinel-enabled applications, traces are optional
+and may require special configuration to enable.
+
+The availability of the trace may vary from application to application. While
+some applications may only log traces on failure, others, such as [Terraform
+Cloud](https://www.terraform.io/cloud), log the trace on every execution. For
+more details, consult your implementation's documentation.
+
+-> **Note:** We're actively improving Sentinel tracing in each release
+to provide better data and improve readability. The exact format of a
+trace may not match the Sentinel version embedded in the application you're
+using, but the data should be similar. The canonical format for a trace should
+get more stable as we approach a 1.0 release.
+
+## Analyzing a Trace
+
+To show traces, let's use the example policy below with the CLI:
+
+```sentinel
+param day
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+
+main = rule { is_open_hours and is_weekday }
+```
+
+Let's cause the policy to fail so the trace is more interesting.
+If you're on a terminal that supports it, the trace output will be colored
+so you can more clearly see passes, failures, and rules.
+
+
+
+ $ sentinel apply main.sentinel{'\n'}
+
+ main.sentinel:1:7: requires value for parameter day
+
+ {'\n'}
+ {'\n'}
+ {'\n'}
+ {' '}Values can be strings, floats, or JSON array or object values. To force
+ {'\n'}
+ {' '}strings, use quotes.{'\n'}
+ {'\n'}
+ {' '}Enter a value: sunday{'\n'}
+ {'\n'}
+
+ main.sentinel:2:7: requires value for parameter hour
+
+ {'\n'}
+ {'\n'}
+ {'\n'}
+ {' '}Values can be strings, floats, or JSON array or object values. To force
+ {'\n'}
+ {' '}strings, use quotes.{'\n'}
+ {'\n'}
+ {' '}Enter a value: 14{'\n'}
+ {'\n'}
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+
+ Fail - main.sentinel
+
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:7:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}
+ false
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:5:1 - Rule "is_open_hours"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}true{'\n'}
+ {'\n'}
+
+ main.sentinel:4:1 - Rule "is_weekday"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}false{'\n'}
+
+
+
+We can see all of our rules neatly and clearly displayed along with the result
+of the policy. If you are getting the trace due to a failed policy and not
+because you explicitly used `-trace`, an informational message is displayed.
+
+As we can see from our basic example, the `main` rule has failed and its result
+is highlighted in red. All peripheral rules that were also evaluated as part of
+the policy are also displayed below the main rule. Source positions are also
+displayed so that you can find the references to the rules in your code.
+
+Via the trace, we can clearly see that while `is_open_hours` returned a true
+result, `is_weekday` did not, and per the logic in our code, the policy is
+rejected.
+
+## Non-Boolean Rules and the Trace
+
+The trace is particularly important when working with rules that return
+non-boolean data, such as rules where you want to see violations instead of a
+simple opaque boolean value. Let's take our example from the [rules](./rules)
+section, and write a small static policy for it:
+
+```sentinel playground
+days = [
+ {"weekday": "Sunday", "weather": "clouds"},
+ {"weekday": "Monday", "weather": "rain"},
+ {"weekday": "Tuesday", "weather": "sunny"},
+ {"weekday": "Wednesday", "weather": "sunny"},
+ {"weekday": "Thursday", "weather": "clouds"},
+ {"weekday": "Friday", "weather": "rain"},
+ {"weekday": "Saturday", "weather": "sunny"},
+]
+
+main = rule {
+ filter days as d {
+ d.weather is not "sunny"
+ }
+}
+```
+
+Here is the output of our policy:
+
+
+
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+ {'\n'}
+
+ Fail - weather.sentinel
+
+ {'\n'}
+ {'\n'}
+
+ weather.sentinel:11:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+
+ {' [\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Sunday"'}
+ {'\n'}
+ {' "weather": "clouds"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Monday"'}
+ {'\n'}
+ {' "weather": "rain"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Thursday"'}
+ {'\n'}
+ {' "weather": "clouds"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Friday"'}
+ {'\n'}
+ {' "weather": "rain"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' ]'}
+
+
+
+
+We can now see all of the weekdays with non-sunny weather.
+
+Note that to prevent formatting issues and excessive output, the human-readable
+trace is limited in the volume of output it will display for collections.
+
+## JSON Output
+
+The trace can also be displayed in JSON by adding `-json` to `sentinel apply`
+and `sentinel test`, and will display the trace in its entirety, unlike the
+standard CLI output.
+
+Here is the result of running `sentinel apply -json` against our weather policy:
+
+```json
+{
+ "can_override": false,
+ "error": null,
+ "policies": [
+ {
+ "allowed_failure": false,
+ "error": null,
+ "policy": "weather.sentinel",
+ "result": false,
+ "trace": {
+ "description": "",
+ "error": null,
+ "print": "",
+ "result": false,
+ "rules": {
+ "main": {
+ "desc": "",
+ "ident": "main",
+ "position": {
+ "filename": "weather.sentinel",
+ "offset": 328,
+ "line": 11,
+ "column": 1
+ },
+ "value": [
+ {
+ "weather": "clouds",
+ "weekday": "Sunday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Monday"
+ },
+ {
+ "weather": "clouds",
+ "weekday": "Thursday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Friday"
+ }
+ ]
+ }
+ }
+ }
+ }
+ ],
+ "result": false
+}
+```
+
+Note that the trace is always included with this output, regardless of whether
+or not the policy passes or fails; using `-trace` is unnecessary.
+
+Additionally, with `sentinel apply`, you can display the value of a single rule
+in JSON, instead of the entire trace. This is done with the `-json-rule` flag.
+
+-> **Note:** `sentinel apply -json-rule` needs to be run either against a single
+on-disk policy, or against a single policy within a policy set. It is ignored
+in full policy set executions and functions exactly like `-json` in these
+scenarios.
+
+Here is the result of executing `sentinel apply -json-rule main weather.sentinel`:
+
+```json
+[
+ {
+ "weather": "clouds",
+ "weekday": "Sunday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Monday"
+ },
+ {
+ "weather": "clouds",
+ "weekday": "Thursday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Friday"
+ }
+]
+```
+
+## Other Trace Details
+
+There are some other details that are good to know when working with the trace:
+
+- Only rules that get executed are added to the trace. Considering our first
+ example where the `main` rule is the expression `is_open_hours and is_weekday`,
+ if our expression was using an `or` operator instead of `and`, in addition to
+ the policy passing, `is_open_hours` would be present in the trace, but
+ `is_weekday` would not be, as the policy would have never evaluated that rule.
+- Descriptions for both rules and policies will show up in the trace as well if
+ present. Here is the output to our first policy with the appropriate
+ annotations:
+
+
+
+ ...{'\n\n'}
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+
+ Fail - main.sentinel
+
+ {'\n'}
+ {'\n'}
+ Description:{'\n'}
+ {' A very basic policy to determine if a run is within working\n'}
+ {' hours.\n'}
+ {'\n'}
+
+ main.sentinel:7:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}
+ false
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:5:1 - Rule "is_open_hours"
+
+ {'\n'}
+ {' '}
+ Description:
+ {'\n'}
+ {' Passes if run during business hours.\n'}
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}true{'\n'}
+ {'\n'}
+
+ main.sentinel:4:1 - Rule "is_weekday"
+
+ {'\n'}
+ {' '}
+ Description:
+ {'\n'}
+ {' Passes if the day does not fall on the weekend.\n'}
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}false{'\n'}
+
+
diff --git a/content/sentinel/v0.22.x/content/sentinel/intro/getting-started/first-policy.mdx b/content/sentinel/v0.22.x/content/sentinel/intro/getting-started/first-policy.mdx
new file mode 100644
index 0000000000..38d7b22c9a
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/intro/getting-started/first-policy.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Your First Sentinel Policy
+sidebar_current: intro-getting-started-first
+description: Let's begin by writing a working Sentinel policy.
+layout: intro
+---
+
+# Your First Sentinel Policy
+
+Sentinel is a system to enforce complex policies on an integrated application.
+
+Writing Sentinel policy requires minimal programming experience. The Sentinel
+language is designed to be approachable and learned quickly and easily. Whether
+you're a professional programmer or someone who uses SQL and Excel, you can
+learn to write Sentinel policies.
+
+Let's begin by writing a simple, working Sentinel policy:
+
+```sentinel playground
+hour = 4
+main = rule { hour >= 0 and hour < 12 }
+```
+
+This is a valid Sentinel policy. It will pass since we hardcoded the
+`hour` to be 4. In a real system, `hour` may be something that is provided
+to us and actually set to the current hour. We'll learn more about that later.
+
+For now, try running this policy locally. Save the above example to a file
+named `policy.sentinel` and execute it. Then, modify the policy to make it
+fail. Play around more if you'd like.
+
+```
+$ sentinel apply policy.sentinel
+Pass
+```
+
+## Main
+
+Every Sentinel policy must have a `main` rule. This is the rule that
+is evaluated to determine the result of a policy.
+
+A rule describes an expression that generally means one of two things:
+
+- Does a policy _pass a condition_ that would authorize an operation? In our
+ above example, describe a policy that checks the supplied hour (4) is within an
+ authorized time window (between 0 - midnight, and 12 noon).
+- Conversely, can a policy find any _violations_ that would block authorization
+ of the operation? Building on the above, consider a policy that takes a
+ schedule, and finds all time blocks that fall outside of the example time window
+ supplied in the above policy.
+
+It is easy to imagine that such a rule might be used in a system such
+as [Nomad](https://www.nomadproject.io) to restrict the times when a
+deploy can occur. The power of arbitrary logical statements within Sentinel
+allows Sentinel policies to restrict almost any behavior.
+
+Next, we'll
+[introduce and explain rules](/sentinel/intro/getting-started/rules)
+so we can use them in our policies.
diff --git a/content/sentinel/v0.22.x/content/sentinel/intro/getting-started/imports.mdx b/content/sentinel/v0.22.x/content/sentinel/intro/getting-started/imports.mdx
new file mode 100644
index 0000000000..dbbb146ecf
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/intro/getting-started/imports.mdx
@@ -0,0 +1,58 @@
+---
+page_title: Imports
+sidebar_current: intro-getting-started-imports
+description: Imports enable Sentinel to access external data and functions.
+layout: intro
+---
+
+# Imports
+
+Imports enable Sentinel to access external data and functions.
+
+Sentinel ships with a set of [standard imports](/sentinel/imports).
+Standard imports are available by default to every Sentinel policy. Note that
+the application embedding Sentinel may allow or deny the available
+set of imports.
+
+To use an import, use the `import` statement. Then, access data and
+functions on the import using the import name followed by a period and
+the field you want to access. The example below checks whether the request
+path starts with a prefix. Assume that `request_path` is available.
+
+```sentinel
+import "strings"
+
+main = rule { strings.has_prefix(request_path, "/admin") }
+```
+
+## External Data
+
+The true power in imports is their ability to reference external data.
+Imports can be added to a Sentinel-enabled application through
+[plugins](/sentinel/extending). This enables any Sentinel-enabled
+application to make policy decision based on any source of data.
+
+This ability allows the policy system to enforce almost any necessary
+organizational policy, since the ability of a policy isn't restricted
+purely to the embedding application's data model.
+
+For example, policies in [Nomad](https://www.nomadproject.io) can
+access data in [Consul](https://www.consul.io) to determine attributes
+of a policy. In the example below, we use a hypothetical Consul import:
+
+```sentinel
+import "consul"
+
+main = rule {
+ job.tasks[0].resources.memory <= int(consul.get("policy/nomad/max-memory"))
+}
+```
+
+In this example, a Nomad job's memory usage is limited to the value of
+a Consul key/value item. By simply changing a Consul KV entry, policy
+can be changed. Imports can do anything, you can [write your own import plugins](/sentinel/extending)
+to extend Sentinel.
+
+Next, we'll learn how to
+[test policies](/sentinel/intro/getting-started/testing)
+to ensure our policy logic is correct.
diff --git a/content/sentinel/v0.22.x/content/sentinel/intro/getting-started/index.mdx b/content/sentinel/v0.22.x/content/sentinel/intro/getting-started/index.mdx
new file mode 100644
index 0000000000..5c9b6b43c5
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/intro/getting-started/index.mdx
@@ -0,0 +1,16 @@
+---
+page_title: Install Sentinel CLI - Getting Started
+sidebar_current: intro-getting-started-overview
+description: The first step to using Sentinel is to get the CLI installed.
+layout: intro
+---
+
+# Getting Started With Sentinel
+
+This section covers getting started with Sentinel. Here, we will take you
+through the first steps that you will need to do to get started with installing
+the Sentinel CLI, writing your first policy, and getting familiar with rules,
+boolean logic, imports, and testing.
+
+You can select a topic on the menu to get started. The first step is to [install
+the Sentinel CLI](/sentinel/intro/getting-started/install).
diff --git a/content/sentinel/v0.22.x/content/sentinel/intro/getting-started/install.mdx b/content/sentinel/v0.22.x/content/sentinel/intro/getting-started/install.mdx
new file mode 100644
index 0000000000..e478fd532f
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/intro/getting-started/install.mdx
@@ -0,0 +1,56 @@
+---
+page_title: Install Sentinel CLI - Getting Started
+sidebar_current: intro-getting-started-install
+description: The first step to using Sentinel is to get the CLI installed.
+layout: intro
+---
+
+# Install Sentinel CLI
+
+Sentinel is a policy framework that is embedded in the enterprise versions of
+HashiCorp tools. The policies you write are deployed to these applications and
+enforced there.
+
+The Sentinel CLI is a command-line interface for local development and testing.
+For the getting started guide, we'll use the CLI to learn how to write policies
+for Sentinel-enabled applications. The Sentinel CLI is distributed as a [binary
+package](/sentinel/downloads) for all supported platforms and architectures.
+
+## Installation Instructions
+
+To install the Sentinel CLI, find the [appropriate package](/sentinel/downloads) for your
+system and download it. The CLI is packaged as a zip archive.
+
+After downloading Sentinel, unzip the package. The CLI runs as a single binary
+named `sentinel`. Any other files in the package can be safely removed and
+Sentinel will still function.
+
+The final step is to make sure that the `sentinel` binary is available on the `PATH`.
+See [this page](https://stackoverflow.com/questions/14637979/how-to-permanently-set-path-on-linux)
+for instructions on setting the PATH on Linux and Mac.
+[This page](https://stackoverflow.com/questions/1618280/where-can-i-set-path-to-make-exe-on-windows)
+contains instructions for setting the PATH on Windows.
+
+## Verifying the Installation
+
+After installing the Sentinel CLI, verify the installation worked by opening a
+new terminal session and checking that the `sentinel` binary is available. By
+executing `sentinel`, you should see help output similar to the following:
+
+```
+$ sentinel
+Usage: sentinel [--version] [--help] []
+
+Available commands are:
+ apply Execute a policy and output the result
+ fmt Format Sentinel policy to a canonical format
+ test Test policies
+ version Prints the Sentinel version
+```
+
+If you get an error that the binary could not be found, then your `PATH`
+environment variable was not setup properly. Please go back and ensure that your
+`PATH` variable contains the directory where Sentinel was installed.
+
+Otherwise, the CLI is installed and ready to go. Let's celebrate by [writing our
+first policy!](/sentinel/intro/getting-started/first-policy)
diff --git a/content/sentinel/v0.22.x/content/sentinel/intro/getting-started/logic.mdx b/content/sentinel/v0.22.x/content/sentinel/intro/getting-started/logic.mdx
new file mode 100644
index 0000000000..d3716a64ea
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/intro/getting-started/logic.mdx
@@ -0,0 +1,54 @@
+---
+page_title: Logic
+sidebar_current: intro-getting-started-logic
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Logic
+
+The core of a policy is a set of logical expressions to determine whether
+a behavior is allowed or not.
+Sentinel makes it easy to write readable logical expressions to express
+the intended policy.
+
+The logical expression syntax will be familiar to anyone who has programmed
+before. Sentinel also supports a couple more unique constructs to assist
+with common policy requirements. Detailed documentation on how to write
+logical expressions and the supported operators is available in
+[the boolean expression reference](/sentinel/language/boolexpr).
+
+Example logic:
+
+```sentinel
+a is b // Equality, you can also use ==
+a is not b // Inequality, you can also use !=
+
+a > b // Relational operators, comparison
+a < b
+a >= b
+a <= b
+
+a contains b // Inclusion check, substrings, etc.
+a not contains b // Negated version of above
+```
+
+The full list of available operators can be found in
+[the boolean expression reference](/sentinel/language/boolexpr).
+
+Logic can be combined with `and` or `or`. When combined with `and`, both
+sides must result in `true`. When combined with `or`, only one side needs
+to be true to result in `true`.
+
+With these building blocks, basic rules can be built up:
+
+```sentinel
+main = rule {
+ hour >= 8 and
+ hour < 17
+}
+```
+
+Next, we'll introduce
+[imports](/sentinel/intro/getting-started/imports)
+so that we can write rules that interact with external data.
diff --git a/content/sentinel/v0.22.x/content/sentinel/intro/getting-started/next-steps.mdx b/content/sentinel/v0.22.x/content/sentinel/intro/getting-started/next-steps.mdx
new file mode 100644
index 0000000000..23b470be59
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/intro/getting-started/next-steps.mdx
@@ -0,0 +1,21 @@
+---
+page_title: Next Steps
+sidebar_current: intro-getting-started-nextsteps
+description: That concludes the getting started guide for Sentinel.
+layout: intro
+---
+
+# Next Steps
+
+That concludes the getting started guide for Sentinel. Hopefully you're excited
+about the possibilities of Sentinel and ready to put this knowledge to use to
+improve the safety of your environments.
+
+We've covered the basics of the core features of Sentinel in this guide.
+We recommend the following as next steps:
+
+- [Documentation](/sentinel) - The documentation is an in-depth
+ reference guide of all the features of Sentinel.
+
+- [Language Reference](/sentinel/language) - The language reference
+ is a complete reference to the features of the Sentine policy language.
diff --git a/content/sentinel/v0.22.x/content/sentinel/intro/getting-started/rules.mdx b/content/sentinel/v0.22.x/content/sentinel/intro/getting-started/rules.mdx
new file mode 100644
index 0000000000..f12a61fe75
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/intro/getting-started/rules.mdx
@@ -0,0 +1,86 @@
+---
+page_title: Rules
+sidebar_current: intro-getting-started-rules
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Rules
+
+Rules in Sentinel are a first-class concept. Within a policy, rules serve a few
+purposes:
+
+- They make complex logic more understandable by allowing said logic to be
+ broken down.
+- They allow assertion of this logic through
+ [testing](/sentinel/intro/getting-started/testing) of the rule's contents.
+- They facilitate reporting of data as rules get published as part of the [policy
+ trace](/sentinel/writing/tracing).
+
+A rule functions in ways similar to both a variable and a function: they hold a
+value, but are lazily evaluated; a rule's value is not assigned until the first
+time it's referenced in a policy. Additionally, while the value of evaluated
+rules will be available within a policy's trace after it's evaluated, values of
+variables - and the return value of functions - are not. Finally, a rule value
+is memoized - further references to the rule will not change its result.
+
+Rules can hold more than just boolean data. For more advanced rule patterns, see
+[the language reference](/sentinel/language/rules).
+
+## Writing Rules
+
+Let's look at the Sentinel policy we wrote in the previous section:
+
+```sentinel playground
+hour = 4
+main = rule { hour >= 0 and hour < 12 }
+```
+
+The logic in this policy is straightforward and easy to understand. However,
+it is common for policy logic to become much more complicated. Rules are the
+key abstraction for making complex logic understandable. We can turn the
+policy above into a set of rules:
+
+```sentinel playground
+hour = 4
+
+past_midnight = rule { hour >= 0 }
+before_noon = rule { hour < 12 }
+
+main = rule {
+ past_midnight and
+ before_noon
+}
+```
+
+This policy is easier to read. Our original policy was already quite
+simple, but it is easy to imagine a more complex policy becoming much
+more readable by extracting logical expressions into a set of rules.
+
+Rules don't just exist to make a policy more readable. They're also a
+testing and debugging point. For testing, you can assert that certain rules
+are certain values given a specific input to verify that your policy works
+as you expect. Testing and debugging are covered in later sections.
+
+## Conditional Rules
+
+In many cases, a rule is only valid when something else is also true.
+
+Consider the human language statement "before you eat, you must wash your hands."
+The rule "you must wash your hands" is only valid "before you eat". In any
+other scenario, the rule is meaningless.
+This is also true in a technical context. Imagine the rule "if the driver
+is Docker, you must not expose any ports." For this example, assume
+rules `is_docker` and `has_no_exposed_ports` exist. You can write this rule
+like this:
+
+```sentinel
+main = rule when is_docker { has_no_exposed_ports }
+```
+
+When `is_docker` is true, the rule is evaluated as normal. However,
+when `is_docker` is false, the rule always returns `true`, because the
+logic of the rule is meaningless.
+
+Let's learn how to create more complex rules with
+[logical expressions](/sentinel/intro/getting-started/logic).
diff --git a/content/sentinel/v0.22.x/content/sentinel/intro/getting-started/testing.mdx b/content/sentinel/v0.22.x/content/sentinel/intro/getting-started/testing.mdx
new file mode 100644
index 0000000000..7149a7966f
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/intro/getting-started/testing.mdx
@@ -0,0 +1,72 @@
+---
+page_title: Testing
+sidebar_current: intro-getting-started-testing
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Testing
+
+It is important to ensure the policies you write are correct. Sentinel
+includes a built-in test framework that can be run locally and in CI
+environments to test that policies behave as expected.
+
+A common pitfall with many simple ACL systems is that they provide no easy
+way to verify their correctness. You basically have to set the ACL and try
+the behaviors against a real system to verify it is working as expected.
+This requires a lot of setup and is unique to each system.
+
+[Sentinel's built-in test framework](/sentinel/writing/testing) has zero
+dependencies. Contained within the [Sentinel CLI](/sentinel/commands), it can
+mock the data that real systems are exposing to the policy. It is designed to be
+CI-friendly and enables continuous testing of your policies. This is necessary
+for [policy as code](/sentinel/concepts/policy-as-code).
+
+Detailed documentation on testing policies is available in [the Sentinel Testing
+reference](/sentinel/writing/testing).
+
+## Writing Tests
+
+For this example, save the following policy as `policy.sentinel`:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Next, let's write a passing test case. This will test that the policy tests
+when we expect it to pass. Save the following in `test/policy/good.hcl`:
+
+```hcl
+global "day" {
+ value = "monday"
+}
+
+global "hour" {
+ value = 14
+}
+```
+
+And run `sentinel test`:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/good.json
+```
+
+The `sentinel test` command will automatically find all Sentinel policies
+and their associated test cases and run them all. The test framework will
+run all HCL files as individual test cases, allowing you to test a variety
+of scenarios for your policies.
+
+Try adding another test case to force the policy to fail. This test case can
+be saved at `test/policy/fail.hcl`. For test cases with intentional
+failures, you'll need to use the [test assertions described in the testing
+reference](/sentinel/writing/testing#a-failing-test).
+
+Now that you have a good grasp of what Sentinel is and how to use it, please feel
+free to look through the
+[next steps](/sentinel/intro/getting-started/next-steps)
+you can take to further your Sentinel knowledge.
diff --git a/content/sentinel/v0.22.x/content/sentinel/intro/index.mdx b/content/sentinel/v0.22.x/content/sentinel/intro/index.mdx
new file mode 100644
index 0000000000..785a442fb8
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/intro/index.mdx
@@ -0,0 +1,32 @@
+---
+layout: intro
+page_title: Documentation
+sidebar_current: docs-home
+description: >-
+ Sentinel is a language and framework for policy built to be embedded in
+ existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+[introduction](/sentinel/intro) to understand what Sentinel is, how it
+compares to other software, and more.
+
+
diff --git a/content/sentinel/v0.22.x/content/sentinel/intro/what.mdx b/content/sentinel/v0.22.x/content/sentinel/intro/what.mdx
new file mode 100644
index 0000000000..36853d366c
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/intro/what.mdx
@@ -0,0 +1,64 @@
+---
+page_title: Introduction
+sidebar_current: intro-what
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: intro
+---
+
+# Introduction to Sentinel
+
+Welcome to the intro guide to Sentinel! This guide is the best
+place to start with Sentinel.
+
+If you are already familiar with the basics of Sentinel, the
+[documentation](/sentinel) provides a better reference
+guide for all available features as well as internals.
+
+## What is Sentinel?
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+Sentinel provides a language for writing policy and a workflow for developing
+and testing policies independent of the software they'll be written to. We
+also provide a framework for developers to build plugins that allow a
+Sentinel-enabled system to access external information to make policy
+decisions.
+
+Most systems today have some degree of access control. You are able to define
+identities and what they have access to. These ACL systems solve an immediate
+and necessary problem of locking down a system in very broad strokes. Sentinel
+is a reusable system for more advanced software policy decisions. Sentinel
+enables:
+
+- **Fine-Grained Policy**: Most ACL systems only enable coarse-grained
+ behaviors: "read", "write", etc. Sentinel enables fine-grained behavior such
+ as disallowing a certain API call when specific parameters are present.
+
+- **Logic-Based Policy**: You can write policy using full
+ conditional logic. For example, you may only allow a certain application behavior
+ on Monday to Thursday unless there is a manager override.
+
+- **Accessing External Information**: Sentinel can source external information
+ to be used in policy decisions. For example, a policy that restricts the size
+ of a payload may read data from [Consul](https://www.consul.io) to determine
+ the payload size limit.
+
+- **Enforcement levels**: Sentinel allows policies to be defined along
+ with an "enforcement level" that dictates the pass/fail behavior of a policy.
+ Advisory policies warn if they fail, soft mandatory policies can have their
+ failures overridden, and hard mandatory policies must pass under all
+ circumstances. Having this as a built-in concept enables you to model
+ policy more accurately and completely for your organization.
+
+## Next Steps
+
+See the page on [Why Sentinel?](/sentinel/intro/why) to learn more
+about the origins of the product. Then, continue onwards with
+the [getting started guide](/sentinel/intro/getting-started/install) to write
+your first policies and see how Sentinel works in practice.
diff --git a/content/sentinel/v0.22.x/content/sentinel/intro/why.mdx b/content/sentinel/v0.22.x/content/sentinel/intro/why.mdx
new file mode 100644
index 0000000000..98b58aab92
--- /dev/null
+++ b/content/sentinel/v0.22.x/content/sentinel/intro/why.mdx
@@ -0,0 +1,49 @@
+---
+page_title: Why Sentinel?
+sidebar_current: intro-why
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: intro
+---
+
+# Why Sentinel?
+
+The growth of infrastructure and applications has been enabled in part
+by an increasing trend towards automation everywhere.
+Configuration as code (such as Chef or Puppet with [Packer](https://www.packer.io))
+has enabled automated machine configuration. Infrastructure
+as code (such as [Terraform](https://www.terraform.io)) has enabled
+automatic infrastructure creation. And schedulers (such as
+[Nomad](https://www.nomadproject.io) and Kubernetes) have enabled
+automatic application deployment.
+Sentinel enables guardrails to be put in place
+on automation while allowing the codification and automatic enforcement
+of business requirements in critical areas of your infrastructure.
+
+Meanwhile, businesses have business requirements and sometimes legal
+requirements which must be expressed in policies. Traditionally, these
+policies are enforced by humans. But in a highly automated world, the
+automation is only as fast as its slowest component. In many cases, this
+is the human verification step.
+
+As an example: before infrastructure as code and autoscaling, if an order
+came through for 5,000 new machines, a human would likely respond to the
+ticket verifying that the user really intended to order 5,000 new machines.
+Today, automation can almost always freely order 5,000 new compute instances
+without any hesitation, which can result in unintended expense or system
+instability.
+
+Sentinel introduces [policy as code](/sentinel/concepts/policy-as-code)
+and a powerful framework built-in to HashiCorp tooling to allow automation
+guardrails, business requirements, legal compliance, and more to be
+actively enforced by the running systems in realtime.
+
+With Sentinel, you can require an override to create certain numbers of
+infrastructure resources. You can disallow unsafe deployment configurations
+with Nomad. You can enforce certain key/value formats in Consul. You
+can restrict secret access by time in Vault. And more.
+
+Sentinel is available today in HashiCorp enterprise products. You can
+try Sentinel immediately with the [getting started guide](/sentinel/intro/getting-started/install) or learn more about the integrations in the
+[documentation](/sentinel).
diff --git a/content/sentinel/v0.22.x/data/docs-nav-data.json b/content/sentinel/v0.22.x/data/docs-nav-data.json
new file mode 100644
index 0000000000..1e281f044a
--- /dev/null
+++ b/content/sentinel/v0.22.x/data/docs-nav-data.json
@@ -0,0 +1,324 @@
+[
+ {
+ "title": "Release Notes",
+ "path": "changelog"
+ },
+ {
+ "title": "Basic Concepts",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "concepts"
+ },
+ {
+ "title": "Policy as Code",
+ "path": "concepts/policy-as-code"
+ },
+ {
+ "title": "Policy Language",
+ "path": "concepts/language"
+ },
+ {
+ "title": "Imports",
+ "path": "concepts/imports"
+ },
+ {
+ "title": "Enforcement Levels",
+ "path": "concepts/enforcement-levels"
+ }
+ ]
+ },
+ {
+ "title": "Configuration File Syntax",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "configuration"
+ },
+ {
+ "title": "Override Files",
+ "path": "configuration/overrides"
+ },
+ {
+ "title": "Remote Sources",
+ "path": "configuration/remote-sources"
+ }
+ ]
+ },
+ {
+ "title": "Commands (CLI)",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "commands"
+ },
+ {
+ "title": "apply",
+ "path": "commands/apply"
+ },
+ {
+ "title": "fmt",
+ "path": "commands/fmt"
+ },
+ {
+ "title": "test",
+ "path": "commands/test"
+ }
+ ]
+ },
+ {
+ "title": "Writing Policy",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "writing"
+ },
+ {
+ "title": "Basics",
+ "path": "writing/basic"
+ },
+ {
+ "title": "Rules",
+ "path": "writing/rules"
+ },
+ {
+ "title": "Traces",
+ "path": "writing/tracing"
+ },
+ {
+ "title": "Testing",
+ "path": "writing/testing"
+ },
+ {
+ "title": "Imports",
+ "path": "writing/imports"
+ },
+ {
+ "title": "Debugging",
+ "path": "writing/debugging"
+ }
+ ]
+ },
+ {
+ "title": "Extending Sentinel",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "extending"
+ },
+ {
+ "title": "Modules",
+ "path": "extending/modules"
+ },
+ {
+ "title": "Plugins",
+ "path": "extending/plugins"
+ },
+ {
+ "title": "Static Imports",
+ "path": "extending/static-imports"
+ },
+ {
+ "title": "Internals",
+ "path": "extending/internals"
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Language",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "language"
+ },
+ {
+ "title": "Variables",
+ "path": "language/variables"
+ },
+ {
+ "title": "Values",
+ "path": "language/values"
+ },
+ {
+ "title": "Lists",
+ "path": "language/lists"
+ },
+ {
+ "title": "Maps",
+ "path": "language/maps"
+ },
+ {
+ "title": "Rules",
+ "path": "language/rules"
+ },
+ {
+ "title": "Imports",
+ "path": "language/imports"
+ },
+ {
+ "title": "Parameters",
+ "path": "language/parameters"
+ },
+ {
+ "title": "Boolean Expressions",
+ "path": "language/boolexpr"
+ },
+ {
+ "title": "Arithmetic",
+ "path": "language/arithmetic"
+ },
+ {
+ "title": "Slices",
+ "path": "language/slices"
+ },
+ {
+ "title": "Conditionals",
+ "path": "language/conditionals"
+ },
+ {
+ "title": "Loops",
+ "path": "language/loops"
+ },
+ {
+ "title": "Collection Operations",
+ "path": "language/collection-operations"
+ },
+ {
+ "title": "Functions",
+ "path": "language/functions"
+ },
+ {
+ "title": "Scope",
+ "path": "language/scope"
+ },
+ {
+ "title": "Undefined",
+ "path": "language/undefined"
+ },
+ {
+ "title": "Logging and Errors",
+ "path": "language/logging-errors"
+ },
+ {
+ "title": "Specification",
+ "path": "language/spec"
+ }
+ ]
+ },
+ {
+ "title": "Builtin Functions",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "functions"
+ },
+ {
+ "title": "append",
+ "path": "functions/append"
+ },
+ {
+ "title": "delete",
+ "path": "functions/delete"
+ },
+ {
+ "title": "error",
+ "path": "functions/error"
+ },
+ {
+ "title": "keys",
+ "path": "functions/keys"
+ },
+ {
+ "title": "length",
+ "path": "functions/length"
+ },
+ {
+ "title": "print",
+ "path": "functions/print"
+ },
+ {
+ "title": "range",
+ "path": "functions/range"
+ },
+ {
+ "title": "values",
+ "path": "functions/values"
+ }
+ ]
+ },
+ {
+ "title": "Standard Imports",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "imports"
+ },
+ {
+ "title": "base64",
+ "path": "imports/base64"
+ },
+ {
+ "title": "decimal",
+ "path": "imports/decimal"
+ },
+ {
+ "title": "http",
+ "path": "imports/http"
+ },
+ {
+ "title": "json",
+ "path": "imports/json"
+ },
+ {
+ "title": "runtime",
+ "path": "imports/runtime"
+ },
+ {
+ "title": "sockaddr",
+ "path": "imports/sockaddr"
+ },
+ {
+ "title": "strings",
+ "path": "imports/strings"
+ },
+ {
+ "title": "time",
+ "path": "imports/time"
+ },
+ {
+ "title": "types",
+ "path": "imports/types"
+ },
+ {
+ "title": "units",
+ "path": "imports/units"
+ },
+ {
+ "title": "version",
+ "path": "imports/version"
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Consul",
+ "path": "consul"
+ },
+ {
+ "title": "Nomad",
+ "path": "nomad"
+ },
+ {
+ "title": "Terraform",
+ "path": "terraform"
+ },
+ {
+ "title": "Vault",
+ "path": "vault"
+ }
+]
diff --git a/content/sentinel/v0.22.x/data/intro-nav-data.json b/content/sentinel/v0.22.x/data/intro-nav-data.json
new file mode 100644
index 0000000000..c95181af70
--- /dev/null
+++ b/content/sentinel/v0.22.x/data/intro-nav-data.json
@@ -0,0 +1,47 @@
+[
+ {
+ "title": "What is Sentinel?",
+ "path": "what"
+ },
+ {
+ "title": "Why Sentinel?",
+ "path": "why"
+ },
+ {
+ "title": "Getting Started",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "getting-started"
+ },
+ {
+ "title": "Installing the CLI",
+ "path": "getting-started/install"
+ },
+ {
+ "title": "Your First Policy",
+ "path": "getting-started/first-policy"
+ },
+ {
+ "title": "Rules",
+ "path": "getting-started/rules"
+ },
+ {
+ "title": "Logic",
+ "path": "getting-started/logic"
+ },
+ {
+ "title": "Imports",
+ "path": "getting-started/imports"
+ },
+ {
+ "title": "Testing",
+ "path": "getting-started/testing"
+ },
+ {
+ "title": "Next Steps",
+ "path": "getting-started/next-steps"
+ }
+ ]
+ }
+]
diff --git a/content/sentinel/v0.22.x/img/sentinel-import-topology.svg b/content/sentinel/v0.22.x/img/sentinel-import-topology.svg
new file mode 100644
index 0000000000..36f7c79bc9
--- /dev/null
+++ b/content/sentinel/v0.22.x/img/sentinel-import-topology.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/content/sentinel/v0.22.x/redirects.jsonc b/content/sentinel/v0.22.x/redirects.jsonc
new file mode 100644
index 0000000000..d9e73f4a49
--- /dev/null
+++ b/content/sentinel/v0.22.x/redirects.jsonc
@@ -0,0 +1,15 @@
+[
+ {
+ "source": "/",
+ "destination": "/sentinel",
+ "permanent": true,
+ },
+ {
+ "source": "/sentinel/commands/config",
+ "destination": "/sentinel/configuration",
+ "permanent": true,
+ },
+ // disallow ".html" or "/index.html" in favor of cleaner, simpler paths
+ { "source": "/:path*/index", "destination": "/:path*", "permanent": true },
+ { "source": "/:path*.html", "destination": "/:path*", "permanent": true },
+]
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/changelog.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/changelog.mdx
new file mode 100644
index 0000000000..2b444e5f25
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/changelog.mdx
@@ -0,0 +1,806 @@
+---
+page_title: Sentinel Runtime Release Notes
+sidebar_current: docs-changelog
+description: >-
+ These are the public release notes for the Sentinel runtime. See this document
+ for the latest updates.
+layout: docs
+---
+
+# Sentinel Runtime Release Notes
+
+These are the release notes for the Sentinel runtime.
+
+Each version of the runtime is released with a corresponding version of
+[Sentinel CLI](/sentinel/commands). To download the CLI, see the [downloads
+page](/sentinel/downloads).
+
+Sentinel integrations and embedded runtimes may not always have the latest
+version installed, depending on the product's individual release cycle. For more
+information, contact the support team for your specific integration.
+
+
+## 0.23.1 (Oct 19, 2023)
+
+BUG FIXES:
+
+- `cmd/apply`: Warnings and errors are included in the JSON output if the json flag is enabled.
+
+## 0.23.0 (Sept 5, 2023)
+
+FEATURES:
+
+- `features/apply_all`: Adds the `apply-all` feature, allowing for all policies to evaluate regardless of result, instead of exiting on first failure.
+
+BUG FIXES:
+
+- `features/terraform`: The `tfplan/v1` import correctly handles nested attribute schemas.
+
+## 0.22.1 (June 22, 2023)
+
+BUG FIXES:
+
+- `sentinel/eval`: Under certain conditions, per-policy parameters would cause
+ Sentinel to panic. This has been resolved.
+
+## 0.22.0 (May 31, 2023)
+
+- `config`: Configuration now supports a `sentinel` block to manage the
+ Sentinel runtime.
+
+## 0.21.1 (May 8, 2023)
+
+BUG FIXES:
+
+- `config`: An issue with certain identifiers being treated as incorrectly invalid
+ has been resolved.
+
+## 0.21.0 (March 8, 2023)
+
+BREAKING CHANGES:
+
+- `lang/ast`: `is empty` and `is not empty` are now treated as `ast.UnaryExpr`
+ expressions, with `ast.IsEmptyExpr` being removed.
+- `lang/ast`: You can now assert if a value is defined or not using the `is defined`
+ and `is not defined` syntax.
+
+FEATURES:
+
+- `config`: Parameter values can now be provided for individual policies within
+ a policy block.
+
+## 0.20.0 (February 16, 2023)
+
+ENHANCEMENTS:
+
+- `cmd/testcmd`: Allows sentinel tests to run concurrently by default. Sequential style testing can
+ be enabled by running with the `-maxConcurrency=1` option.
+- `cmd/testcmd`: Allows sentinel test command to timeout after a certain duration. This can be provided
+ by the user or will default to 5 minutes.
+- `cmd/apply`: The policy enforcement level is now included in the JSON output.
+- `lang/ast`: Functions can now be declared as named statements, providing
+ a safer function declaration.
+
+BREAKING CHANGES:
+
+- `cmd/apply`: Policies provided directly to the apply command will now default their enforcement
+ level to `advisory`, aligning with the `policy` configuration block.
+- `sentinel`: JSON results will no longer return `allowed_failure` or `can_override` fields.
+- `sentinel/result`: A new package has been added which provides additional methods to return
+ supplemental data about the evaluation result.
+
+## 0.19.5 (February 9, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `SelectorExpr` will correctly rewrite `X`.
+
+## 0.19.4 (February 8, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `Compile` function will correctly set the `Original`
+ field when rewriting a `CallExpr` as an `ImportExpr`.
+
+## 0.19.3 (February 3, 2023)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.19.2 (January 17, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `Compile` function will no longer modify `ast.CallExpr`
+ arguments in place.
+
+## 0.19.1 (December 14, 2022)
+
+BUG FIXES:
+
+- `lang/ast`: The `Rewrite` function will no longer modify the provided Node
+ in place.
+
+ENHANCEMENTS:
+
+- `lang/semantic`: The Break semantic check uses the AST walker.
+
+## 0.19.0 (December 6, 2022)
+
+BREAKING CHANGES:
+
+- `config`: Attempts to override a standard import with a custom import will
+ now be met with an error.
+- `imports`: All plugin imports now return `sdk.Plugin` instead of `sdk.Import`,
+ as per the SDK update to v0.4.0.
+
+ENHANCEMENTS:
+
+- `runtime/importer`: An `Import` and `ImportInstance` interface have been
+ added to improve the import capabilities.
+- `runtime/importer`: The `Importer` interface now returns a `Import` interface
+ instead of a `sdk.Import`.
+- `lang/semantic`: The Import Assignment semantic check uses the AST walker.
+- `lang/ast`: Added an AST Walker so the AST rewriter does not need to be used for basic AST tasks.
+
+FEATURES:
+
+- `config`: Configuration syntax now allows for configuration of imports within
+ the standard library.
+- `config`: A new configuration syntax for defining modules and plugins is now
+ available.
+- Static data can now be added via `import` configuration.
+- `cmd/apply`: The `apply` command now supports multiple primary configuration
+ files by default, loading all configuration files within the working
+ directory.
+- `cmd/apply`: The `apply` command now accepts applying configuration overrides
+ to primary configuration.
+
+## 0.18.13 (October 31, 2022)
+
+BUG FIXES:
+
+- `cmd/apply`: `sentinel apply` no longer aborts on advisory policy failure.
+
+## 0.18.12 (September 15, 2022)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where nested `CallExpr` were not rewritten.
+
+## 0.18.11 (June 8, 2022)
+
+NOTES:
+
+- `config`: The `enforcement_level` key for policies is now optional, defaulting to `"advisory"` when not supplied.
+
+## 0.18.10 (May 20, 2022)
+
+NOTES:
+
+- `runtime/initwd`: Improvements to the installer, including timeouts and filesize limits, as well as disabling support
+ for symlinks within `git` or `hg` repositories.
+
+## 0.18.9 (March 23, 2022)
+
+NOTES:
+
+- This release fixes an issue with the Docker container build pipeline. There are no changes to the
+Sentinel runtime or CLI.
+
+## 0.18.8 (March 22, 2022)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue in the installer for Windows paths.
+
+## 0.18.7 (February 24, 2022)
+
+NOTES:
+
+- **Darwin arm64**. This release will be our first to include Darwin arm64 architecture.
+
+BUG FIXES:
+
+- `lang/ast`: Fixed issues where `IndexSpec` and `RuleLit` nodes where returning incorrect end positions.
+- `lang/ast`: Fixed an issue where `ParamSpec` nodes where returning incorrect end positions.
+
+## 0.18.6 (February 2, 2022)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.18.5 (January 14, 2022)
+
+IMPROVEMENTS:
+
+- `runtime/initwd`: Changed the sum algorithm used during remote installation from `md5` to `sha256` to reduce chance of collision.
+
+## 0.18.4 (July 20, 2021)
+
+FEATURES:
+
+- `imports/static`: Improved handling of struct tags when performing encoding, allowing `-` to mark as ignored.
+
+## 0.18.3 (June 1, 2021)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue for remote source installation where duplicate sub-directories resulted in incorrect installation.
+- `imports/static`: Fixed an issue where global configuration that contained an empty map could not be accessed without raising selector issues.
+
+## 0.18.2 (May 18, 2021)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where import expressions inside lists and maps were not being evaluated correctly.
+
+## 0.18.1 (May 11, 2021)
+
+BUG FIXES:
+
+- `imports/json`: Fixed an issue where empty maps where failing when passed at any level to `marshal`.
+
+## 0.18.0 (March 25, 2021)
+
+FEATURES:
+
+- `imports/http`: Added support for POST actions within the `http` import
+ through addition of the `post` function. Additionally, support for adding
+ a body to the `request` object has been added via `with_body`.
+
+## 0.17.4 (February 2, 2021)
+
+BUG FIXES:
+
+- `runtime/format`: Fixed a nesting issue with the rule printer where nesting
+ state leaks were leading to all collection values eventually being truncated,
+ regardless of their nesting level.
+
+## 0.17.3 (January 15, 2021)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue where local file installation failed on Windows
+ when using `sentinel test`.
+
+## 0.17.2 (January 13, 2021)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where duplicate log messages were being printed with
+ `sentinel test`.
+
+## 0.17.1 (January 7, 2021)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where hard-coded path separator caused `sentinel
+ test` issues in Windows.
+
+## 0.17.0 (January 5, 2021)
+
+LANGUAGE CHANGES:
+
+- **Map Expression** `map`. A new quantifier expression, `map` has been added.
+ `map` takes a collection and applies an operation to each element in the
+ collection, returning a list of results with the resulting values.
+- **Emptiness checks** `is empty` and `is not empty`. Two new expressions,
+ `is empty` and `is not empty` have been added. As they are aliases to the
+ `length` built-in, only `string`, `map`, `list` or `undefined` is
+ supported.
+- **Comparable maps** Maps (the data type) are now comparable. Maps are equal if
+ they are of equal length and both their corresponding keys and values are
+ comparable and equal.
+- **Rich Return Types** Rules can now process and return non-boolean data.
+ Scalar, list, and map types are supported. When non-boolean values are used,
+ the presence of a non-zero or non-zero-length `main` rule determines policy
+ failure. More details can be found on
+ https://docs.hashicorp.com/sentinel/language#main.
+
+FEATURES:
+
+- **CLI**: `sentinel apply` and `sentinel test` now have options for JSON
+ output. For more details, see the CLI documentation.
+- `imports/base64`: Added a new import, `base64`, to assist with encoding and
+ decoding base64 strings.
+
+## 0.16.1 (October 21, 2020)
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where `contains` to ensure any instance of `undefined` would correctly result in `undefined`.
+- `runtime/eval`: Fixed an issue in `any` and `all` expressions to ensure correct boolean logic when dealing with a collection containing `undefined`.
+- `imports`: Fixed an issue where import processes were not killed, which caused non-graceful closures for the clients.
+- `lang/scanner`: Fixed an issue where inline `#` style comments were not being consumed.
+
+## 0.16.0 (October 14, 2020)
+
+BREAKING CHANGES:
+
+- `cmd/doc`: Removal of the deprecated `sentinel doc` command.
+- `sentinel`: Replace `whitelist` and `blacklist` with `allowlist` and `denylist`, as per inclusive language changes.
+
+FEATURES:
+
+- `imports/version`: Added a new import, `version`, which supplies helpers for dealing with semantic versioning.
+- `cmd/apply`: Added an `HCL` based configuration file, which will eventually deprecate the legacy `JSON` configuration.
+- `cmd/apply`: Added support for download remote policies and modules.
+- `cmd/test`: Added support for downloading remote test modules.
+- `cmd/apply`: Added support for evaluating a policy based on it's key within the configuration.
+- `cmd/apply`: Added support for running all policies in a configuration by default.
+
+## 0.15.6 (July 7, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.15.5 (May 20, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel integrations. There are no user-facing changes.
+
+## 0.15.4 (May 13, 2020)
+
+BUG FIXES:
+
+- `imports`: Fixed an issue with the standard imports that was affecting the handling of null data within lists.
+
+## 0.15.3 (April 16, 2020)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where `IndexExpr` were not rewritten, as well as ensuring `SelectorExpr` uses any nested rewrites.
+- `lang/printer`: Fixed issue printing deeply nested structures and/or loops.
+
+IMPROVEMENTS:
+
+- `imports/decimal`: Added alias methods to improve consistency of method names within the `decimal` import. The alias methods are `is_nan` for `isnan`, as well as both `is_inf` and `is_infinite` for `isinfinite`.
+- `command/apply`: `sentinel apply` will now output the policy description when a trace is forced via `-trace`.
+- `sentinel`: Improved `String` output formatting of Policy docstring for `EvalPolicyResult`.
+
+## 0.15.2 (April 2, 2020)
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue with the `printer` that was causing a panic
+ when a `#` line comment was used with no text following it.
+- `imports/types`: Fixed an issue with the `types` import where calls to
+ `type_of` for undefined types were incorrectly returning as map types. These
+ will now be correctly be identified as undefined as expected.
+
+## 0.15.1 (March 6, 2020)
+
+BUG FIXES:
+
+- `sentinel`: Fixed how modules are managed internally in the runtime to correct
+ race conditions in concurrent scenarios and to allow for per-evaluation module
+ restrictions. This functionality is only of interest to embedded applications
+ with long-lived runtimes and is currently not implemented in any HashiCorp
+ integration.
+
+## 0.15.0 (March 5, 2020)
+
+NOTES:
+
+- **Windows Digital Signature**. Our windows releases for this version and up will be signed
+ and verified according to Microsoft's requirements.
+
+FEATURES:
+
+- `cmd/config`: Modules can now be loaded off of local disk using the Sentinel
+ CLI. For information on how to do so, see the Sentinel documentation.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: Changed modules so that they now have singleton scope when
+ loaded. Importing a module under two different aliases, or within a nested
+ module, will now share scopes - modification of state in one will alter the
+ other.
+- `lang/object`: Introduced an interface to allow the duplication of various
+ types, like collections.
+- `runtime/eval`: Changed how collection data is returned from modules. This
+ data is now duplicated to prevent accidental or deliberate circumvention of
+ the prohibition on assignment to import data, and brings behavior to parity
+ with binary imports.
+
+## 0.14.4 (February 6, 2020)
+
+IMPROVEMENTS:
+
+- `imports/time`: Changed the `add` timespace function in the `time` import to
+ allow it to take float types as durations. These values will be truncated to
+ the appropriate integer-value duration.
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where out-of-place right-hand braces were being
+ parsed as empty statements, instead of raising syntax errors.
+
+## 0.14.3 (January 22, 2020)
+
+IMPROVEMENTS:
+
+- `cmd/test`: Fail when an assertion is not found in a trace.
+- `cmd/test`: Added a message to notify when no rules were evaluated in a test.
+
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue where import aliases (the `as baz` in `import
+ "foo/bar" as baz`) were being removed when formatting with `sentinel fmt`.
+
+- `imports/http`: Fixed an issue with the http import where the client library's
+ debug logs were being printed to standard error.
+
+## 0.14.2 (January 15, 2020)
+
+NOTES:
+
+With this release, we are discontinuing support for MacOS 32-bit. 64-bit builds
+are now the only builds available for MacOS.
+
+IMPROVEMENTS:
+
+- `lang/printer`: A newline is now added to files formatted via `sentinel fmt`.
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue in printing where multi-line expressions would
+ indent infinitely. They will now only indent once at the most. This is mostly
+ seen when using `sentinel fmt`.
+- `runtime/encoding`: Fixed an issue where if a plugin returned a deeply
+ embedded undefined value, the value was instead decoded into a map.
+
+## 0.14.1 (January 6, 2020)
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where reserved words could not be used as
+ selectors when the selector expression was the last lexeme on a line.
+
+## 0.14.0 (December 18, 2019)
+
+LANGUAGE CHANGES:
+
+- **Filter Expression** `filter`. A new quantifier expression, `filter` has been added.
+ `filter` returns a subset of the provided collection based on a boolean condition that
+ is asserted for each element.
+
+NOTES:
+
+- **Apple Notarization**. Our darwin releases for this version and up will be signed
+ and notarized according to Apple's requirements.
+
+## 0.13.1 (November 25, 2019)
+
+BUG FIXES:
+
+- `runtime/eval`: Parameters are no longer allowed in mock files. Adding one to
+ a mock will result in a runtime error.
+- `runtime/eval`: Fixed an issue where import calls from within mocks were
+ failing under certain circumstances.
+- `imports/decimal`: `int` should now correctly provide the truncated integer
+ representation of a decimal number, not the rounded one.
+
+## 0.13.0 (November 15, 2019)
+
+LANGUAGE CHANGES:
+
+- **Policy Parameters**. The new [policy
+ parameters](https://docs.hashicorp.com/sentinel/language/parameters) feature
+ allows authors to use a `param` declaration at the top of a policy to supply
+ values that are expected to come from outside of a policy. See the linked
+ documentation for more details.
+
+FEATURES:
+
+- `imports/http`: The [`http`
+ import](https://docs.hashicorp.com/sentinel/imports/http) is a new addition
+ to the standard library enabling the use of HTTP-accessible data from outside
+ the runtime in policy rules.
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where loading other imports before a mocked
+ import would cause those imports to no longer be visible from within the
+ policy.
+
+## 0.12.0 (October 7, 2019)
+
+LANGUAGE CHANGES:
+
+- **New `case` statement**. This statement is a selection control mechanism to
+ conditionally execute a branch based on expression equality, allowing
+ simplification of complex conditional chains that may otherwise need to be
+ written with `else if`. See the [Case
+ Statements](https://docs.hashicorp.com/sentinel/language/spec#case-statements)
+ section of the Sentinel language specification for more details.
+
+IMPROVEMENTS:
+
+- `lang/semantic`: Added a semantic check to ensure usage of append is not using
+ a return value.
+
+BUG FIXES:
+
+- `runtime/eval`: Corrected an issue where calling a method on an import object
+ value that was the result of a method call on another import object value
+ would have erroneously tried to call an import of the name of the "parent"
+ import object value. Example: in `a = subject.new(); b = a.call(); c =
+ b.call()`, `b.call()` would attempt to call a method named `call` on the root
+ namespace for an import named `a`. This has now been corrected so that
+ `b.call()` will now correctly call the `call` method for the respective object
+ namespace residing in the import named `subject`.
+- `imports`: Some standard imports may have been returning null for some unknown
+ keys in objects when they should have been returning undefined. This was due
+ to an SDK issue which was corrected in SDK version 0.3.2, which has now been
+ corrected.
+- `cmd/test`: `sentinel test` will now correctly fail a policy if it encounters
+ an error.
+- `cmd/test`: `sentinel test` will now correctly display errors and other output
+ that were missing due to a formatting issue.
+- `runtime/eval`: The `append` builtin now correctly returns undefined for all
+ calls, as called for by the Specification. Note that in most cases, the
+ semantic check outlined above will trigger an error if the return value is
+ used.
+
+## 0.11.0 (September 5, 2019)
+
+LANGUAGE CHANGES:
+
+- **New builtin function:** `range()`. This function existed in the spec in
+ earlier versions but was removed as it lacked an implementation. This has now
+ been implemented and re-added to the spec. See the
+ [Range](https://docs.hashicorp.com/sentinel/language/spec#range) section of
+ the Sentinel Specification for more details.
+- Lists are now comparable. Lists are equal if their corresponding elements are
+ comparable and equal.
+- Method calls on values returned by imports are now supported. See the
+ [Imports](https://docs.hashicorp.com/sentinel/language/spec#imports) section
+ of the Sentinel Specification for more details.
+
+FEATURES:
+
+- `imports/decimal`: This is a new import designed to do exact precision
+ mathematical calculations.
+- `runtime/eval`: Compound call expressions that refer to imports (example:
+ foo.bar().baz() when `foo` is a loaded import) will now function as expected.
+ Previously, this was only supported up to the first call (example:
+ `foo.bar()`).
+- `imports/time`: Timespaces returned by calls such as `time.now` are now
+ callable. Example: `t = time.now; t.after(some_previous_time)` will now
+ function.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: The implementation of comparison of non-comparable types has
+ now changed. Rather than triggering a runtime error, non-comparable types will
+ now return false when attempting to compare.
+
+## 0.10.4 (August 15, 2019)
+
+BUG FIXES:
+
+- `runtime/encoding`: Fixed an issue with conversion of null values that could
+ lead to crashes in imports.
+
+## 0.10.3 (July 15, 2019)
+
+BUG FIXES:
+
+- `command/config`: Parsing a configuration file where malformed data is
+ encountered after apparently properly-formed data will now report an error. An
+ example would be a situation where misplaced braces would cause a JSON object
+ to only parse part of the configuration file.
+
+## 0.10.2 (June 25, 2019)
+
+BUG FIXES:
+
+- `lang/parser`: Corrected an issue where parsing certain compound binary
+ expressions where the negation predicate (`not`) was in use would cause the
+ negation to have no effect. Example: `foo else "bar" not in "baz"` would have
+ been parsed and evaluated as `foo else "bar" in "baz"`, effectively producing
+ the opposite result.
+
+## 0.10.1 (May 9, 2019)
+
+BUG FIXES:
+
+- `command/test`: `sentinel test` now displays policies without
+tests as an unknown result with [no test files], instead of the somewhat
+erroneous behavior of displaying it as a PASS. A full test run with a mixture
+of passing tests and no tests still results in an overall successful result.
+
+## 0.10.0 (April 18, 2019)
+
+LANGUAGE CHANGES:
+
+- Mixed-number arithmetic operations are now allowed. Addition (`+`),
+ subtraction (`-`), multiplication (`*`), division (`/`), and remainder
+ operations (`%`) are no longer restricted to number values of the same
+ type, and can mix integer and floating point. The result of these operations
+ is always a floating-point number.
+- Remainder (modulo, `%`) operations are now allowed on floating-point numbers.
+
+
+BUG FIXES:
+
+- `lang/parser`: Fixed a bug that affected the use of `not` with `contains`,
+ `matches`, or `in` in a compound expression.
+- `runtime/eval`: Fixed error messages on evaluation errors with `contains` and
+ `in` to indicate that strings are an allowed type along with lists and maps.
+
+
+## 0.9.2 (March 15, 2019)
+
+This is a dependency update related to the changes mentioned in 0.9.1. No other
+changes have been made.
+
+## 0.9.1 (March 14, 2019)
+
+This is a patch release that is required to integrate with the latest versions
+of the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). No other
+changes have been made.
+
+## 0.9.0 (January 28, 2019)
+
+FEATURES:
+
+- `imports/strings`: Added a `join` function to the `strings` import. This can
+ be used to join a list into a string with a specific separator.
+ Multi-dimensional lists and all Sentinel primitives are supported.
+
+## 0.8.1 (January 17, 2019)
+
+BUG FIXES:
+
+- `lang/eval`: Fixed a bug that prevents the effective use of `return`
+ statements in `for` loops.
+
+## 0.8.0 (January 14, 2019)
+
+FEATURES:
+
+- Mocks can now be represented by Sentinel code. This allows for the mocking of
+ functions and other complex data structures that cannot be represented in
+ JSON. For more information on using this feature via mocks, click
+ [here](https://docs.hashicorp.com/sentinel/commands/config#mocking-with-sentinel-code).
+
+
+IMPROVEMENTS:
+
+- Import validation has now been moved to the semantic checking phase. This
+ should result in better reporting of validation errors. In addition, import
+ validation will now enforce the use of an `as` identifier when an import path
+ is not a valid identifier on its own (example: `import "foo/bar"`).
+
+## 0.7.0 (December 12, 2018)
+
+BUG FIXES:
+
+- There have been changes to the runtime in how scope is handled over multiple
+ policy executions. Scope is now correctly unique per single policy execution,
+ and values set or builtins that are overridden in one policy will no longer
+ affect those values within another.
+
+## 0.6.0 (November 30, 2018)
+
+FEATURES:
+
+- `imports/runtime`: This new import allows for one to check various aspects of
+ the Sentinel runtime as it may be embedded in the simulator or a specific
+ implementation. For now, it allows the version to be checked.
+
+## 0.5.1 (November 28, 2018)
+
+IMPROVEMENTS:
+
+- `imports/time`: Added the `zone` and `zone_string` attributes to assist with
+ validation of a timespace's zone.
+- `command/fmt`: Added a new -check flag. This option does not commit changes,
+ but instead checks to see what files need formatting and outputs them on
+ stdout.
+
+BUG FIXES:
+
+- `command/test`: Ensure that passing test results are correctly output one per
+ line. Tests are also now run in a deterministic fashion based on
+ lexicographical (alphabetical) order.
+- `imports/time`: `month_name` and `weekday_name` will now show up correctly in
+ a returned timespace result.
+
+
+## 0.5.0 (November 5, 2018)
+
+IMPROVEMENTS:
+
+- `spec`: Selectors can now contain any reserved word (example: `rule`) or
+ keyword operator (example: `any`, `all`, `is`, `not`). This only works for the
+ _selector_ part of the expression (after the first period) - the first primary
+ expression (before the first period) still needs to be an identifier that does
+ not conflict with reserved words.
+
+BUG FIXES:
+
+- The simulator should now display import function call names correctly in
+ import errors.
+
+## 0.4.0 (October 1, 2018)
+
+FEATURES:
+
+- `builtin`: Added the `bool` built-in type conversion function. Booleans will
+ also now accepted as conversion into other values as well, with the full list
+ of behaviors available in the spec.
+
+## 0.3.2 (September 27, 2018)
+
+FEATURES:
+
+- `command/apply`: `sentinel apply` now prints out messages output by the
+ `print()` function when a trace is output on policy failure, or when a trace
+ is forced with `-trace`.
+- `imports/time`: Added the `month_name` and `weekday_name` keys to the
+ timespace, which return full-English names for the month and day of the week.
+
+
+BUG FIXES:
+
+- `command/fmt`: `sentinel fmt -` Will no longer print out the filter status
+ message on the output stream when `-write=false` Is not explicitly stated.
+ This brings the behavior of the command in line with the help text.
+- `runtime`: Index operations on the right-hand-side that have negative indexes
+ that go out of range (example: `length(list) * -1 - 1`) now correctly return
+ `undefined`. left-hand-side index assignments with a out-of-range negative
+ index still return runtime errors.
+
+## 0.3.1 (August 3, 2018)
+
+BUG FIXES:
+
+- `runtime`: Basic index assignment has been implemented as per the spec.
+- `runtime`: Index expressions for lists with negative indexes will no longer panic if the
+ list index is less than `length(list) * -1`.
+
+## 0.3.0 (July 20, 2018)
+
+FEATURES:
+
+- **New standard import: `types`.** This can be used to dynamicaly detect the
+ type of some value.
+
+## 0.2.0 (April 11, 2018)
+
+FEATURES:
+
+- **New standard import: `json`.** Marshal and unmarshal JSON documents and
+ access their contents as native Sentinel values.
+- **`break` and `continue`.** These are now both specified and implemented.
+ `break` allows loop exiting and `continue` allows immediate execution of the
+ next iteration.
+
+IMPROVEMENTS:
+
+- runtime: `print()` map values are now ordered alphabetically by keys.
+
+BUG FIXES:
+
+- command/test: If no `test` block exists, test behaves like it is asserting
+ `main: true`.
+- runtime: default maximum stack depth to 500
+- runtime: `print()` map values now appear like more typical maps.
+- runtime: division by zero is an error, not a crash
+- runtime: plugins that send map values with `null` values now decode properly
+ into native Sentinel values.
+
+## 0.1.0 (September 19, 2017)
+
+Initial release.
+
+
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/commands/apply.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/commands/apply.mdx
new file mode 100644
index 0000000000..c5e81d6d65
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/commands/apply.mdx
@@ -0,0 +1,63 @@
+---
+page_title: 'Command: apply'
+sidebar_current: docs-commands-apply
+description: >-
+ The `sentinel apply` command is used to execute a policy locally for
+ development purposes.
+layout: docs
+---
+
+# Command: `apply`
+
+The `sentinel apply` command is used to execute a policy locally for development
+purposes.
+
+## Usage
+
+Usage: `sentinel apply [options] POLICY`
+
+This command executes the policy file at the path specified by POLICY. In
+addition to a path to a policy, POLICY can reference a label of a
+[policy](/sentinel/configuration#policies) entry in the configuration.
+
+Use the exit code of this command to determine the exact status of the policy
+evaluation. `0` is pass, `1` is fail, `2` is undefined (fail, but because the
+result was undefined), and `3` is a runtime error. Errors unrelated to the
+policy status itself are returned with an exit status of `9`.
+
+To control the behavior of the `apply` command, create a [configuration
+file](/sentinel/configuration). With this, you can define available
+[import plugins](/sentinel/concepts/imports), mock data, and global values.
+This can help you simulate a policy embedded within an application.
+
+As of the 0.16 release, POLICY is optional. When it is not supplied,
+`sentinel apply` will run **all** policies in the supplied configuration.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-config=path` - Path to a configuration file specifying available imports,
+ mock data, globals, etc. The default is `sentinel.[hcl|json]`.
+
+- `-json` Enable JSON output. Using this mode, all other output will be
+ suppressed and the full detail of the apply will be output in a
+ machine-readable format. Enabling this implies `-trace`. See the section on
+ [tracing JSON output](/sentinel/writing/tracing#json-output) for more details.
+
+- `-json-rule=RULE` Enable JSON output, outputting only the value of the
+ specified rule. When running against a policy set, the policy must be supplied
+ to enable per-rule filtering. Implies `-json`.
+
+- `-global key=value` - Set global values. This is the same as setting `global`
+ in the configuration file, and will override any of these respective values
+ set in the configuration. The value is either a string, or a JSON number,
+ array, or object. To force strings, use quotes.
+
+- `-param key=value` - Set parameters, the same as setting `param` in the
+ configuration file. Values are handled in the same way they are with the
+ `-global` flag.
+
+- `-trace` - Always show the execution trace. This shows intermediate
+ boolean expression values. This always shows for failed policies.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/commands/fmt.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/commands/fmt.mdx
new file mode 100644
index 0000000000..8297316d8b
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/commands/fmt.mdx
@@ -0,0 +1,33 @@
+---
+page_title: 'Command: fmt'
+sidebar_current: docs-commands-fmt
+description: The `sentinel fmt` command formats a policy source to a canonical format.
+layout: docs
+---
+
+# Command: `fmt`
+
+The `sentinel fmt` command formats a policy source to a canonical format.
+
+## Usage
+
+Usage: `sentinel fmt [options] FILE ...`
+
+This command formats all the specified policy files to a canonical format.
+
+By default, policy files are overwritten in place. This behavior can be
+changed with the `-write` flag. If a specified FILE is `-` then stdin is
+read and the output is always written to stdout.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-write=true` - Write formatted policy to the named source file. If false,
+ output will go to stdout. If multiple files are specified, the output will
+ be concatenated directly.
+
+- `-check=false` - Don't format, only check if formatting is necessary. Files
+ that require formatting are printed, and a non-zero exit code is returned if
+ changes are required.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/commands/index.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/commands/index.mdx
new file mode 100644
index 0000000000..7177093cec
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/commands/index.mdx
@@ -0,0 +1,23 @@
+---
+page_title: Commands (CLI)
+sidebar_current: docs-commands
+description: >-
+ Sentinel provides a `sentinel` command-line interface (CLI) for developing and
+ testing policies.
+layout: docs
+---
+
+# Sentinel CLI Commands
+
+The Sentinel command-line interface (CLI) allows for the developing and testing
+of policies outside of a particular Sentinel implementation. Having a standard
+workflow to develop policies is critical for our mission of [policy as
+code](/sentinel/concepts/policy-as-code).
+
+The CLI takes a subcommand to execute. The complete list of subcommands is in
+the navigation to the left.
+
+The Sentinel CLI is a well-behaved command line application. In erroneous cases,
+a non-zero exit status will be returned. It also responds to -h and --help as
+you'd expect. To view a list of the available commands at any time, just run
+`sentinel` with no arguments.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/commands/test.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/commands/test.mdx
new file mode 100644
index 0000000000..ed50aacc22
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/commands/test.mdx
@@ -0,0 +1,53 @@
+---
+page_title: 'Command: test'
+sidebar_current: docs-commands-test
+description: The `sentinel test` command runs policies against the Sentinel test framework.
+layout: docs
+---
+
+# Command: `test`
+
+The `sentinel test` command runs policies against the Sentinel test framework.
+
+To learn more about testing, see the [testing
+policies](/sentinel/writing/testing) page.
+
+## Usage
+
+Usage: `sentinel test [options] [PATH] ...`
+
+This command runs the defined test cases against a set of policies.
+
+The test cases are expected to live at the path `test//*.[hcl|json]` relative
+to the policy being tested. `` should be the name of the policy
+excluding the file extension. The files should be in the [configuration
+file structure](/sentinel/configuration). The `test` key is used for
+assertions. Test cases ignore the root level configuration file and must have
+all required configuration provided in each test case.
+
+The PATH arguments specified may be a list of zero or more files or directories.
+When no arguments are given, it defaults to the current directory. When a
+directory is given, all policies ending with the extension `.sentinel` are
+tested.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-json` - Suppress normal output and output test results as a JSON document.
+ See the [testing JSON output](/sentinel/writing/testing#test-json-output)
+ section for more details.
+
+- `-run=regexp` - Run only tests matching the regular expression pattern.
+ A `/` splits policy name and test case name.
+
+- `-verbose` - Show tracing and log output for tests that succeed in addition
+ to tests that fail. By default, traces and logs are only shown for tests that
+ fail.
+
+- `-maxConcurrency` - Allows users to specify the number of tests to be run concurrently.
+ Defaults to the number of logical CPUs if not provided. To run tests sequentially, use `-maxConcurrency=1`
+
+- `-timeout` - Allows users to specify a timeout after which the test command will stop running.
+Defaults to 5 minutes if not provided. The timeout needs to be specified as a Duration type, eg `100ms, 5s etc`
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/concepts/enforcement-levels.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/concepts/enforcement-levels.mdx
new file mode 100644
index 0000000000..fc4b8062d0
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/concepts/enforcement-levels.mdx
@@ -0,0 +1,43 @@
+---
+page_title: Enforcement Levels
+sidebar_current: docs-concepts-levels
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Enforcement Levels
+
+Enforcement levels are a first class concept in Sentinel allowing pass/fail
+behavior to be associated separately from the policy logic. This enables
+any policy to be a warning, allow overrides, or be absolutely mandatory.
+Because this level is not part of the policy body itself, different uses of
+the same policy can have different enforcement levels.
+
+Sentinel has three enforcement levels:
+
+- **Advisory:** The policy is allowed to fail. However, a warning should be
+ shown to the user or logged. Advisory is the default enforcement level.
+
+- **Soft Mandatory:** The policy must pass unless an override is specified.
+ The semantics of "override" are specific to each Sentinel-enabled application.
+ The purpose of this level is to provide a level of privilege separation
+ for a behavior. Additionally, the override provides non-repudiation since
+ at least the primary actor was explicitly overriding a failed policy.
+
+- **Hard Mandatory:** The policy must pass no matter what. The only way to
+ override a hard mandatory policy is to explicitly remove the policy.
+ It should be used in situations where an override is not possible.
+
+## Configuring Enforcement Levels
+
+Enforcement levels are configured when a policy is deployed to a
+Sentinel-enabled application. The exact mechanism that the level is specified
+is determined by each application. Please reference the documentation for
+your Sentinel-enabled application for more information.
+
+Enforcement levels are not configured and are not known by the policy body
+itself. All policies should be written to describe exactly the behavior
+they're attempting to control. For example, a policy that restricts deploys
+to business hours should be written exactly like so. When that policy is
+configured on an application, the operator may specify that it is advisory,
+soft, or hard mandatory.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/concepts/imports.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/concepts/imports.mdx
new file mode 100644
index 0000000000..1306e1512e
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/concepts/imports.mdx
@@ -0,0 +1,32 @@
+---
+page_title: Imports
+sidebar_current: docs-concepts-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom imports](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only
+local context for making policy decisions.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that the
+import calendar allows access to engineer calendars. This import only exists
+for the purpose of this example and at the time of writing is not a real
+available import.
+
+```json sentinel
+{
+ "policy": "import \"calendar\"\n// Get the calendar for Bob for today\nbob_calendar = calendar.of(\"bob\").today\n\n// Allow this policy to pass if Bob is not on vacation.\nmain = rule { not bob_calendar.has_event(\"vacation\") }",
+ "mocks": {
+ "calendar": "has_event = func(event) {\n\treturn true\n}\nof = func(name) {\n\treturn { \"today\": { \"has_event\": has_event } }\n}"
+ }
+}
+```
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/concepts/index.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/concepts/index.mdx
new file mode 100644
index 0000000000..ce7ac51454
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/concepts/index.mdx
@@ -0,0 +1,15 @@
+---
+page_title: Basic Concepts
+sidebar_current: docs-concepts
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Basic Concepts
+
+This section covers some high level basic concepts that are important
+to understand for day to day Sentinel usage. Every page in
+this section is recommended reading for anyone consuming
+or extending Sentinel.
+
+Please use the navigation to the left to learn more about a topic.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/concepts/language.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/concepts/language.mdx
new file mode 100644
index 0000000000..ce91f3eb8f
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/concepts/language.mdx
@@ -0,0 +1,91 @@
+---
+page_title: Policy Language
+sidebar_current: docs-concepts-language
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Policy Language
+
+Sentinel defines and uses its own [policy language](/sentinel/language).
+
+The language was designed to be approachable by non-programmers, since
+there are many use cases where the individual defining policy may not
+be a developer. However, the language includes constructs that
+are familiar to developers to enable powerful policies.
+
+To learn more about the language, please see the
+[writing policy section](/sentinel/writing)
+or the [language reference](/sentinel/language).
+
+An example of the policy language is shown below:
+
+```sentinel playground
+import "time"
+
+# Validate time is between 8 AM and 4 PM
+valid_time = rule { time.now.hour >= 8 and time.now.hour < 16 }
+
+# Validate day is M - Th
+valid_day = rule {
+ time.now.weekday_name in ["Monday", "Tuesday", "Wednesday", "Thursday"]
+}
+
+main = rule { valid_time and valid_day }
+```
+
+## Why?
+
+To explain why Sentinel defines and uses its own language, the question
+can be more easily split into roughly two types of languages: _configuration_ languages
+and _programming_ languages.
+
+### Configuration Languages
+
+Configuration languages are formats such as JSON, YAML, XML, etc. Many applications
+use configuration languages as their ACL format. Configuration languages
+are good for static, declarative information. ACL systems are a good fit
+for this. ACL systems are focused, providing the limiting options necessary
+to restrict access to a system.
+
+Configuration languages are not good for dynamic or logical rules. They
+typically only contain limited conditions, loops, or functions. Further
+configuration is often declarative, which can introduce complexities to
+logical statements that depend on ordering.
+
+The use cases that Sentinel was built for require the ability to perform
+complex behavior that didn't model well into a configuration format or a
+declarative form.
+
+### Programming Languages
+
+The Sentinel language was designed with the following goals:
+
+- **Non-programmer friendly.** Sentinel was built to be used by non-programmers.
+ For the use cases we discovered, non-programmers needed the ability to
+ enforce certain rules within a system. For example, a person responsible for
+ compliance may need to insert rules into a system.
+
+- **Programmer friendly.** At the same time, the language needed to support
+ programmer-friendly constructs such as conditionals, loops, and functions
+ for complex policies that a programmer may be writing.
+
+- **Embeddable.** Sentinel is embedded in existing software. The language
+ itself needs to be easily embeddedable. Further, Sentinel was designed
+ to be embedded in [HashiCorp](https://www.hashicorp.com) software
+ written in Go, so it needed to be easily embeddable in Go.
+
+- **Safe.** The language is used in highly security-sensitive environments.
+ It must not be able to crash its host system or access system resources
+ without explicit approval.
+
+Prior to building our own language, Sentinel evaluated other languages.
+Some languages worked quite well. As we continued to develop Sentinel, we
+found that the flexibility and control over designing our own language
+outweighed the costs.
+
+Because the language itself doesn't need to be general purpose, building
+a language focused on policy proved to be the best route for Sentinel.
+It allows us to build powerful tracing capabilities for debugging, make
+interesting performance choices, and extend the language as needed in the
+future.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/concepts/policy-as-code.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/concepts/policy-as-code.mdx
new file mode 100644
index 0000000000..6dac593e18
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/concepts/policy-as-code.mdx
@@ -0,0 +1,72 @@
+---
+page_title: Policy as Code
+sidebar_current: docs-concepts-pac
+description: >-
+ Policy as code is the idea of writing code in a high-level language to manage
+ and automate policies. By representing policies as code in text files, proven
+ software development best practices can be adopted such as version control,
+ automated testing, and automated deployment.
+layout: docs
+---
+
+# Policy as Code
+
+Policy as code is the idea of writing code in a high-level language to
+manage and automate policies. By representing policies as code in text files,
+proven software development best practices can be adopted such as version
+control, automated testing, and automated deployment.
+
+Many existing policy or ACL systems do not practice policy as code. Many
+policies are set by clicking in a GUI, which isn't easily repeatable nor
+versionable. They usually don't provide any system for testing policies
+other than testing an action that would violate the policy. This makes it
+difficult for automated testing. And the policy language itself varies by
+product.
+
+Sentinel is built around the idea and provides all the benefits of policy as code.
+
+## Benefits
+
+Policy as code provides a number of benefits:
+
+- **Sandboxing.** Policies provide the guardrails for other automated systems.
+ As the number of automated systems grow, there is also a growing need to
+ protect those automated systems from performing dangerous actions. Manual
+ verification is too slow; policies need to be represented as code to
+ keep up with other automated systems.
+
+- **Codification.** By representing policy logic as code, the information
+ and logic about a policy is directly represented in code and can be augmented
+ with comments rather than relying on oral tradition to learn about the
+ reason for policies.
+
+- **Version Control.** Policies are encouraged to be stored as simple text
+ files managed by a version control system. This lets you gain all the
+ benefits of a modern VCS such as history, diffs, pull requests, and more.
+
+- **Testing.** Policies are just code. Their syntax and behavior can be
+ [easily validated with Sentinel](/sentinel/commands/test). This also encourages
+ automated testing such as through a CI. Paired with a VCS system, this
+ allows a pull request workflow to verify that a policy keeps the system
+ behavior as expected before merging.
+
+- **Automation.** With all policies as code in simple text files, various
+ automation tools can be used. For example, it is trivial to create tools
+ to automatically deploy the policies into a system.
+
+## Sentinel and Policy as Code
+
+Sentinel fully embraces policy as code in a number of ways:
+
+- **Language.** All Sentinel policies are written using the
+ [Sentinel language](/sentinel/concepts/language). This language is
+ made to be inputted directly to text files. As an additional benefit,
+ all Sentinel-enabled applications share the same policy language.
+
+- **Development.** Sentinel provides a [CLI](/sentinel/commands)
+ for development and testing. This local CLI can be used to verify policies
+ before deploying them to a system.
+
+- **Testing.** Sentinel provides a [test framework](/sentinel/commands/test)
+ designed specifically for automation. This allows developers and CI systems
+ to further verify policies.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/configuration/index.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/configuration/index.mdx
new file mode 100644
index 0000000000..9c4b0c942e
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/configuration/index.mdx
@@ -0,0 +1,422 @@
+---
+page_title: Sentinel CLI Configuration File Syntax
+sidebar_current: docs-configuration
+description: >-
+ The Sentinel CLI's configuration file can be used to control the
+ behavior of the simulator during apply and test operations.
+layout: docs
+---
+
+# Sentinel CLI Configuration File Syntax
+
+The Sentinel CLI's configuration file can be used to control the behavior
+of the simulator during [`apply`](/sentinel/commands/apply) and
+[`test`](/sentinel/commands/test) operations.
+
+## Usage
+
+The configuration file is used in different ways depending on what operation you
+are trying to execute.
+
+### Apply
+
+When using `sentinel apply`, configuration files that have the `.hcl`
+extension are loaded into a single configuration. This allows for configuration
+to be split across multiple files, which is useful for large policy sets. To
+supply a single configuration file, the `-config=FILE` flag can be used, where
+`FILE` is the path to the configuration file. This argument also allows for a
+`.json` configuration file to be supplied. The default is `sentinel.[hcl|json]`.
+
+See the [apply command](/sentinel/commands/apply) reference for more details.
+
+### Test
+
+When using `sentinel test`, each file matching `test//*.[hcl|json]` is a
+configuration file representing a single test case, where `` is the name
+of the policy being tested. Configure assertions for each [test
+case](#test-cases) using the test section.
+
+See the [test command](/sentinel/commands/test) reference for more details.
+
+#### Remote sources
+
+When declaring a `policy` or `module` block within the configuration, a
+`source` attribute must be supplied. This `source` should declare the location
+of the resource, which can be either a local file or a URL pointing to a remote
+file. Examples of local `source` attributes are detailed both in the
+[Policies](#policies) and [Modules](#modules) sections of this page. Below
+are a few examples of remote `source` attributes.
+
+Example:
+
+```hcl
+policy "foo" {
+ source = "git::https://github.com/hashicorp/sentinel-example.git//main.sentinel"
+ enforcement_level = "hard-mandatory"
+}
+```
+
+The above example will fetch the policy from the provided URL and place it into
+the cache ready for use. Be sure to read the
+[Remote Sources](/sentinel/configuration/remote-sources) page for an
+in-depth overview.
+
+## Configuration File Reference
+
+The format of the configuration file is either JSON or HCL. The available
+blocks are:
+
+- [`mock`](#mock-imports) - A mock import specification.
+- [`policy`](#policies) - Configuration for a [policy](/sentinel/writing).
+- [`import`](#imports) - Configuration for a [module](/sentinel/extending/modules), [standard import](/sentinel/imports), [plugin](/sentinel/extending/plugins) or
+ [static import](/sentinel/extending/static-imports).
+- [`global`](#globals) - Data that is inserted into the global scope.
+- [`param`](#parameters) - Values for [parameters](/sentinel/language/parameters) defined in the policy.
+- [`test`](#test-cases) - Test cases for the [test command](/sentinel/commands/test).
+- [`sentinel`](#sentinel) - Configuration to manage the Sentinel runtime
+
+### Mock Imports
+
+Mock imports allow running a policy with an
+[import](/sentinel/concepts/imports) that you may not have access to or is
+too difficult to run. For example, it can be easier to mock data for [Terraform
+Enterprise](https://www.terraform.io/docs/enterprise/sentinel/index.html)
+locally instead of having to run a workspace through a plan/policy check cycle.
+
+Mock imports are specified using the `mock` block, labelled by the import
+name that you want to mock. For example, if you wanted to mock the `time`
+import, you could create an entry with the `time` label pointing to the data
+you want to mock.
+
+The data can take one of two forms:
+
+#### Mocking static data
+
+Static data can be mocked directly via HCl using the `data` attribute on the
+`mock` block.
+
+Example:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ hour = 9
+ minute = 42
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"time\"\n\nmain = rule { time.now.hour is 9 }",
+ "mocks": {
+ "time": "now = { \"hour\": 9, \"minute\": 42 }"
+ }
+}
+```
+
+#### Mocking with Sentinel code
+
+There are some Sentinel types that raw data cannot mock, such as functions,
+and maps that don't have string keys. To mock this data, you can use
+a Sentinel file itself. In this case, you can set the `module` attribute to
+a file with the Sentinel code in it, relative to the current working directory in
+the event of `apply`, or relative to the configuration file location in the
+event of `test`.
+
+Note that `main` is not required in a mock data file.
+
+Example:
+
+```hcl
+mock "foo" {
+ module {
+ source = "mock-foo.sentinel"
+ }
+}
+```
+
+`mock-foo.sentinel` would contain:
+
+```sentinel
+bar = func() {
+ return "baz"
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"foo\"\n\nmain = rule { foo.bar() is \"baz\" }",
+ "mocks": {
+ "foo": "bar = func() { return \"baz\" }"
+ }
+}
+```
+
+### Policies
+
+The `policy` block allows for the the configuration of policies to assist
+with integrating into other commands.
+
+Each `policy` block has a label to identify the policy, with the block
+providing configuration of the policy. The available configuration options for
+policy are `source`, `enforcement_level` and an optional `params` attribute. The
+`source` key provides the location of the policy, while `enforcement_level` is
+currently used by integrations such as [Terraform Cloud](https://www.terraform.io/docs/cloud/sentinel/manage-policies.html#enforcement-levels).
+For more information on the `source` value, see [Policy and Module Sources](#policy-and-module-sources).
+
+The optional `params` attribute is used to provide values to [parameters](/sentinel/language/parameters)
+defined within the policy file.
+
+```hcl
+policy "foo" {
+ source = "foo.sentinel"
+ enforcement_level = "hard-mandatory"
+ params = {
+ "name" = "Sample"
+ }
+}
+```
+
+If `enforcement_level` is not supplied, it will fallback to the `advisory` level.
+
+### Imports
+
+Import configuration allows you to configure [modules](/sentinel/extending/modules),
+[standard imports](/sentinel/imports) and [import plugins](/sentinel/extending/plugins).
+
+The `import` is a multi-label block, with the first label determining the kind of import
+being configured. Currently the supported values for this label are:
+
+* [`"module"`](#import-modules) for configuring import modules
+* [`"plugin"`](#import-plugins) for configuring standard imports and import plugins
+* [`"static"`](#static-imports) for configuring static data files as imports
+
+#### Import Modules
+
+The `import "module"` block allows you to map a Sentinel import to a
+[module](/sentinel/extending/modules). The Sentinel CLI will parse the module
+source and make it available for evaluation with the policy.
+
+Each block has a label aligning to the name of the import, with
+the block set to the configuration object for the module. Currently, the only
+value within the configuration object is `source`, which points to the
+location of the module. For more information on the `source` value, see
+[Policy and Module Sources](#policy-and-module-sources).
+
+```hcl
+import "module" "foo" {
+ source = "modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "modules/bar.sentinel"
+}
+```
+
+Note when using [`sentinel test`](/sentinel/commands/test), module paths must be
+specified relative to the location of the configuration file.
+
+```hcl
+import "module" "foo" {
+ source = "../../modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "../../modules/bar/sentinel"
+}
+```
+
+See [Writing and Using a
+Module](/sentinel/extending/modules#writing-and-using-a-module) for more details
+on using a module with this configuration.
+
+### Import Plugins
+
+Import `"plugin"` configuration allows you to configure both [standard imports](/sentinel/imports)
+as well as [import plugins](/sentinel/extending/plugins) that can be used within
+a policy. If defining a custom import plugin, the Sentinel CLI will launch the
+plugin, connect to it, configure it, and execute it as needed by the policy.
+
+The available configuration attributes for an `import "plugin"` are:
+
+- `source` (string, required) - Path to the import plugin executable.
+- `args` (list of string, optional) - A list of arguments to pass to the
+ executable when starting it.
+- `env` (map of string to string, optional) - A set of environmental variables
+ to set when launching the plugin.
+- `config` (map, optional) - Configuration for the plugin itself. This is
+ specific to each individual plugin. Please reference the documentation for
+ the plugin for more information.
+
+Standard import example:
+
+```hcl
+import "plugin" "time" {
+ config = { "timezone": "Australia/Brisbane" }
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"time\"\n\nmain = time.now.zone_string is \"+10:00\"",
+ "mocks": {
+ "time": "now = { \"zone_string\": \"+10:00\" }"
+ }
+}
+```
+
+Custom plugin example:
+
+```hcl
+# flipper receives a boolean and returns the opposite
+import "plugin" "flipper" {
+ source = "/path/to/flipper"
+}
+```
+
+```json sentinel
+{
+ "policy": "import \"flipper\"\n\nmain = flipper.flip(false)",
+ "mocks": {
+ "flipper": "flip = func(input) { return not input }"
+ }
+}
+```
+
+#### Static Imports
+
+Static import configuration allows you to supply a data file and make
+it available to a policy via an import statement.
+
+The available configuration attributes for an `import "static"` are:
+
+- `source` (string, required) - Path to the static data file.
+- `format` (string, required) - The format of the static data. Currently, only
+ the "json" value is supported.
+
+Static import example:
+
+```hcl
+import "static" "people" {
+ source = "./data/people.json"
+ format = "json"
+}
+```
+
+```json sentinel
+{
+ "policy": "import \"people\"\n\nmain = length(people.names) > 0",
+ "mocks": {
+ "people": "names = [\"John Smith\", \"Jane Smith\"]"
+ }
+}
+```
+
+### Globals
+
+Global data is injected directly into the global scope of the policy.
+This can be used to simulate global data that may be injected by a
+Sentinel-enabled application.
+
+Global data is specified by the `global` key. The value is a map of
+variables to inject directly into the running policy.
+
+Example:
+
+```hcl
+global "time" {
+ value = {
+ now = {
+ day = 31
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass. Notice
+that we don't have to do any `import`. The values of `global` are
+injected directly into the global scope.
+
+```json sentinel
+{
+ "policy": "main = time.now.day == 31",
+ "globals": {
+ "time": {
+ "now": {
+ "day": 31
+ }
+ }
+ }
+}
+```
+
+### Parameters
+
+The `param` section allows you to supply values for the
+[parameters](/sentinel/language/parameters) found in a policy. Values
+entered here will satisfy required parameters in a policy, in addition to
+override any defaults. Note that any of the manual parameter value methods
+supported by `sentinel apply` will override the values set here.
+
+```hcl
+param "foo" {
+ value = "bar"
+}
+```
+
+### Test Cases
+
+Test cases specify the test cases for the [test command](/sentinel/commands/test).
+
+Tests are specified by the `test` key. The value of this is a map of
+string to boolean. The key is the name of a rule and the value is the
+expected value of that rule. If a rule is not specified, than any value is
+allowed.
+
+Example:
+
+```hcl
+test {
+ rules = {
+ main = true
+ days = []
+ }
+}
+```
+
+For the policy:
+
+```json sentinel
+{
+ "policy": "valid_days = rule {\n\tfilter days as d {\n\td is \"monday\"\n\t}\n}\n\nmain = rule { valid_days is empty }",
+ "globals": {
+ "days": []
+ }
+}
+```
+
+For more information, read about the [test command](/sentinel/commands/test).
+
+### Sentinel
+
+The `sentinel` block provides configuration specific to the Sentinel runtime.
+
+An example of how the `sentinel` block can be used to enable the [terraform](/sentinel/features/terraform)
+feature is as follows:
+
+```hcl
+sentinel {
+ features = {
+ terraform = true
+ }
+}
+```
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/configuration/overrides.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/configuration/overrides.mdx
new file mode 100644
index 0000000000..ca1a6842b1
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/configuration/overrides.mdx
@@ -0,0 +1,80 @@
+---
+page_title: Override Files
+sidebar_current: docs-configuration-overrides
+description: >-
+ The Sentinel CLI's configuration can be overriden using override files.
+ Override files merge additional settings into existing configuration.
+layout: docs
+---
+
+# Override Files
+
+As outlined in the configuration [overview](/sentinel/configuration#Apply),
+the normal behavior of Sentinel is to load all `.hcl` files into a single
+configuration object.
+
+In addition to appending multiple configuration files, Sentinel allows for the
+rare case of overriding configuration through override files. Override files
+are any files that match either `_override.{hcl,json}` or `override.{hcl,json}`.
+
+Sentinel will parse override files after it has loaded the primary
+configuration, and then process each override file in turn (in lexicographical
+order). All top level blocks other than [test](/sentinel/configuration#test-cases)
+require the block to already be defined in the primary configuration. It will
+then attempt to merge the override block into the existing configuration.
+
+## Example
+
+If you had a Sentinel configuration `sentinel.hcl` that contained the following:
+
+```hcl
+policy "main" {
+ source = "./main.sentinel"
+ enforcement_level = "advisory"
+}
+```
+
+And you created a file `override.hcl` that contained the following:
+
+```hcl
+policy "main" {
+ enforcement_level = "soft-mandatory"
+}
+```
+
+Sentinel will merge the contents of the override into the former, providing the
+following configuration:
+
+```hcl
+policy "main" {
+ source = "./main.sentinel"
+ enforcement_level = "soft-mandatory"
+}
+```
+
+## Merging Behavior
+
+The general rule, which applies in most cases, is:
+
+- A top-level block in an override file merges with a block in a normal
+ configuration file that has the same block header. The block header is the
+ block type and any quoted labels that follow it.
+- Within a top-level block, an attribute argument within an override block
+ replaces any argument of the same name in the original block.
+- Within a top-level block, any nested blocks within an override block replace
+ all blocks of the same type in the original block. Any block types that do not
+ appear in the override block remain from the original block.
+- The contents of nested configuration blocks are not merged.
+- The resulting merged block must still comply with any validation rules that
+ apply to the given block type.
+
+If more than one override file defines the same top-level block, the overriding
+effect is compounded, with later blocks taking precedence over earlier blocks.
+Overrides are processed in order first by filename (in lexicographical order)
+and then by position in each file.
+
+## Required Attributes
+
+It is important to note that all attributes inside an override file are
+considered to be optional, meaning that an override file can itself fail
+validation rules for block types.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/configuration/remote-sources.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/configuration/remote-sources.mdx
new file mode 100644
index 0000000000..b64e4d3abc
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/configuration/remote-sources.mdx
@@ -0,0 +1,165 @@
+---
+page_title: Remote Sources
+sidebar_current: docs-configuration-remote-sources
+description: >-
+ There are a number of options for formatting the URL provided to the
+ source attribute on policy and module blocks.
+layout: docs
+---
+
+# Remote Sources
+
+There are a number of options for formatting the URL provided to the
+`source` attribute on `policy` and `import "module"` blocks. This flexibility
+should solve most use cases and allow for reusable policies and modules.
+
+### Supported Protocols
+
+-> **NOTE:** All protocols are supported on the CLI, however each production
+Sentinel integration may not. Be sure to read the appropriate integration
+documentation to check the supported protocols.
+
+- Local filesystem
+- Git
+- Mercurial
+- HTTP
+- Amazon S3
+- Google GCP
+
+### Forced Protocols
+
+Due to the ambiguous nature of URLs, it is possible to force a particular
+protocol to be used during the fetch. To achieve this, simply prefix the url
+with the appropriate protocol as listed below:
+
+- `file` - Local filesystem
+- `git` - Git
+- `hg` - Mercurial
+- `http` or `https` - HTTP
+- `s3` - Amazon S3
+- `gcp` - Google GCP
+
+Example:
+
+```
+git::http://github.com/hashicorp/sentinel.git
+```
+
+The above would download the provided HTTP URL using the Git protocol.
+
+### Protocol Options
+
+In addition to some global options, there are options available to specific
+protocols to perform features such as authentication.
+
+#### Subdirectories / File Pathing
+
+When supplying URLs that point to a directory (eg. `git`), a subdirectory and
+file can be provided by suffixing the URL with `//` and the path. For example,
+if we have a git repository that has a policy, `main.sentinel` in the root, we
+could directly fetch the file by using the following:
+
+```
+git::https://github.com/hashicorp/sentinel-docs-example.git//main.sentinel
+```
+
+Or, if the file was nested in the `policies` folder:
+
+```
+git::https://github.com/hashicorp/sentinel-docs-example.git//policies/main.sentinel
+```
+
+#### Checksumming
+
+For downloads of any protocol, you can automatically verify a checksum. To
+checksum a file, append a `checksum` query parameter to the URL. The parameter
+value can be in the format of type:value or just value, where type is "md5",
+"sha1", "sha256", "sha512" or "file" . The "value" should be the actual
+checksum value or download URL for "file". When type part is omitted, type will
+be guessed based on the length of the checksum string.
+
+```
+./foo.sentinel?checksum=md5:b7d96c89d09d9e204f5fedc4d5d55b21
+
+./foo.sentinel?checksum=b7d96c89d09d9e204f5fedc4d5d55b21
+
+./foo.sentinel?checksum=file:./foo.txt.sha256sum
+```
+
+#### Unarchiving
+
+An `archive` query parameter can be supplied to explicitly state what format
+to attempt to extract, if required. The supported formats are:
+
+- `tar.gz` and `tgz`
+- `tar.bz2` and `tbz2`
+- `tar.xz` and `txz`
+- `zip`
+- `gz`
+- `bz2`
+- `xz`
+
+If a URL has an extension that matches one of the supported formats, it will
+use that format to unarchive.
+
+```
+./file.zip
+```
+
+The above would use the `zip` format to unarchive.
+
+```
+./path/to/file?archive=zip
+```
+
+Would force the `zip` format. If for some reason you required archiving to be
+disabled, this can also be achieved by using the `false` value.
+
+```
+./path/to/file?archive=false
+```
+
+#### Git (`git`)
+
+- `ref` - The Git ref to checkout. This is a ref, so it can point to a commit
+ SHA, a branch name, etc. If it is a named ref such as a branch name, it will
+ be updated to the latest on each get.
+- `sshkey` - An SSH private key to use during clones. The provided key must be
+ a base64-encoded string. For example, to generate a suitable sshkey from a
+ private key file on disk, you would run base64 -w0 \.
+ **Note:** Git 2.3+ is required to use this feature.
+- `depth` - The Git clone depth. The provided number specifies the last `n`
+ revisions to clone from the repository.
+
+The git getter accepts both URL-style SSH addresses like
+`git::ssh://git@example.com/foo/bar`, and "scp-style" addresses like
+`git::git@example.com/foo/bar`. In the latter case, omitting the `git::` forced
+prefix is allowed if the username prefix is exactly git@.
+
+The "scp-style" addresses cannot be used in conjunction with the `ssh://`
+scheme prefix, because in that case the colon is used to mark an optional port
+number to connect on, rather than to delimit the path from the host.
+
+#### Mercurial (`hg`)
+
+- `rev` - The Mercurial revision to checkout.
+
+#### HTTP (`http` or `https`)
+
+Basic Authentication
+
+To use HTTP basic authentication, simply prepend `username:password@` to the
+hostname in the URL such as `https://Aladdin:OpenSesame@www.example.com/index.html`.
+All special characters, including the username and password, must be URL
+encoded.
+
+#### S3 (`s3`)
+
+The following query parameters are available and will take priority when
+authenticating against an S3 bucket.
+
+- `aws_access_key_id` - AWS access key.
+- `aws_access_key_secret` - AWS access key secret.
+- `aws_access_token` - AWS access token if this is being used.
+- `aws_profile` - Use this profile from local ~/.aws/ config. Takes priority
+ over the other three.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/consul.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/consul.mdx
new file mode 100644
index 0000000000..7a3aaaa697
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/consul.mdx
@@ -0,0 +1,47 @@
+---
+page_title: Consul and Sentinel
+sidebar_current: docs-app-consul
+description: >-
+ Consul Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies are applied during writes to
+ the KV Store.
+layout: docs
+---
+
+# Consul
+
+[Consul Enterprise](https://www.hashicorp.com/products/consul/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies are applied during writes to the KV Store.
+
+Sentinel policies have access to the key/value being written. They can be used to
+allow or deny the modification. The information that Sentinel policies have access
+to will expand over time.
+
+The Consul integration with Sentinel is documented in depth in the
+[Consul Enterprise documentation](https://www.consul.io/docs/guides/sentinel.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Input validation depending on the name of the key.**
+
+```sentinel
+main = rule { valid_key() }
+
+required = [
+ ["port", "\\d+"], # ports must be integers
+ ["name", "\\w+"], # name must be a word
+]
+
+valid_key = func() {
+ for required as v {
+ if key is v[0] {
+ return value matches v[1]
+ }
+ }
+
+ return false
+}
+```
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/extending/index.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/extending/index.mdx
new file mode 100644
index 0000000000..a39cb0384d
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/extending/index.mdx
@@ -0,0 +1,52 @@
+---
+page_title: Extending Sentinel
+sidebar_current: docs-extending
+description: Learn how to create your own Sentinel imports to extend Sentinel's functionality.
+layout: docs
+---
+
+# Extending Sentinel
+
+-> **NOTE:** Sentinel's extensibility is currently undergoing large
+improvements - watch this space for future updates!
+
+Sentinel can be extended by writing additional
+[imports](/sentinel/language/imports). These imports can bring new functionality
+to Sentinel by allowing access to new data or by the addition of new functions.
+This section is designed to show you how to accomplish this extensibility and
+describe how it works.
+
+Currently, the only usable way to add additional imports is via
+[modules](#modules). While [plugins](#plugins) currently work under the Sentinel
+CLI, no Sentinel integration currently supports their use.
+
+-> You may also be interested in advanced details on [import
+internals](/sentinel/extending/internals).
+
+## Modules
+
+Modules allow you to re-use Sentinel code as an import. Modules are loaded
+external of policies and mapped to imports. This is usually configured via a
+file such as the [CLI configuration file](/sentinel/configuration).
+
+See the [modules](/sentinel/extending/modules) section for more details.
+
+## Plugins
+
+-> **NOTE:** Plugins are currently only supported on the CLI and not in any
+production Sentinel integration, with the exception of existing configuration
+in [Nomad](https://www.nomadproject.io/docs/configuration/sentinel).
+
+Imports can also be implemented as plugins when Sentinel code itself is too
+restrictive to represent the import, or if you need access to features not
+normally available to the Sentinel runtime.
+
+See the [plugins](/sentinel/extending/plugins) section for more details.
+
+## Static Imports
+
+Static imports provide support for loading data files into the Sentinel runtime,
+making them available as an import.
+
+See the [static imports](/sentinel/extending/static-imports) section for more
+details.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/extending/internals.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/extending/internals.mdx
new file mode 100644
index 0000000000..11be9d3d67
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/extending/internals.mdx
@@ -0,0 +1,252 @@
+---
+page_title: >-
+ Extending Sentinel: Internals
+sidebar_current: docs-extending-internals
+description: This Section covers some details about Sentinel's import internals.
+layout: docs
+---
+
+# Extending Sentinel: Internals
+
+-> **Advanced Topic!** This page covers low-level technical details of Sentinel.
+You don't need to understand these details to effectively use Sentinel. The
+details are documented here for those who wish to learn about them.
+
+This section covers some of the internal details of Sentinel's import system.
+The goal of this section is to help remove notion of "magic" from Sentinel, to
+allow you to trust and understand what Sentinel is doing with imports, and also
+to help practitioners and developers alike understand the differences between
+the ways that imports can be loaded into Sentinel.
+
+## Language and Runtime
+
+Understanding the import system in Sentinel generally requires understanding of
+two key concepts within Sentinel: the _language_, and the _runtime
+implementation_ of the language.
+
+The Sentinel language and the rules governing it are detailed in the [Sentinel
+Language Specification](/sentinel/language/spec). A runtime implementation must
+conform to the rules laid out in the specification at a minimum. However, to
+meet the design goals of a particular implementation, the runtime may implement
+features on top of the language. The subtle implementation details within the
+runtime may also vary while still being compliant with the specification.
+
+The core runtime included within the [Sentinel CLI](/sentinel/commands) is
+sometimes referred to as the _reference implementation_ of the Sentinel
+language. This runtime is also the main runtime embedded within each
+Sentinel-enabled HashiCorp product such as Terraform Cloud and Enterprise, Vault
+Enterprise, Nomad Enterprise, and Consul Enterprise. The runtime includes an API
+to allow these integrations implement Sentinel in as robust or as restrictive of
+a way as possible, so depending on the implementation, your experience may vary.
+
+## Sentinel's Import Model
+
+The concept of Imports within Sentinel is a _language_ feature. You can see the
+exact rules governing imports within the
+[Imports](/sentinel/language/spec#imports) section of the specification.
+
+Within the runtime, there are currently two major classifications of imports:
+
+- **Modules:** The mapping of Sentinel code to an import, essentially mapping a
+ scope of values to a particular package.
+- **Binary Imports:** A grouping of embedded and external plugins written using
+ the Sentinel SDK. These are comprised of internal or embedded imports (usually
+ the [standard imports](/sentinel/imports) and imports included by an
+ integration) and import plugins.
+
+Below is a diagram explaining in brief the relationship between the langauge
+and runtime features.
+
+
+
+This kind of topology ultimately minimizes the visibility of implementation
+internals to the actual policy language. This allows us to keep the Sentinel
+language itself simple and keep concerns such as module or plugin management,
+validation, and configuration out of the language. These kinds of things would
+impact the readability of a policy, possibly present security challenges, and
+would ultimately be of no use to more minimal embedded applications.
+
+### Implementation Differences Between Import Types
+
+As one would imagine, both modules and binary imports are loaded in much
+different ways, and the methods that they expose data to Sentinel differ as
+well. The effect is generally the same however across both, and we take care to
+ensure that both modules and binary imports behave as close as possible to each
+other, however there are some subtle differences:
+
+- Modules currently support the exporting of rules, but do not support function
+ calls with object receiver data (as seen in some standard imports such as
+ [`decimal`](/sentinel/imports/decimal), [`http`](/sentinel/imports/http), and
+ [`time`](/sentinel/imports/time)). This will be coming in a later release.
+- Binary imports cannot export rules. However, they do support object receiver
+ data (see [`framework.New`](/sentinel/extending/plugins#framework-new) in
+ [Extending Sentinel: Plugins](/sentinel/extending/plugins)).
+
+## Modules
+
+_Modules_ are essentially Sentinel code that is intended to be re-used in multiple
+policies as an import.
+
+The mechanism of module loading is fairly straightforward: during initialization
+of the Sentinel runtime, modules are parsed along with policies. The parsed
+_abstract syntax tree_ (AST) for each module is loaded into the runtime under
+the specified _import path_. These are then passed to the lower-level
+interpreter during the evaluation of each policy.
+
+As with policy code, during evaluation, a module's AST is walked to execute the
+code within that specific module. The module data is then stored within an
+specific scope mapped to the import name. The module data can then be accessed
+through the import via selector expressions and function calls (e.g.,
+`foo.some_map` or `foo.some_func()` for a module loaded via `import "foo"`).
+
+The AST for a module is only walked if a policy imports it, and it is _only
+walked once_. That means if a policy and a module import the same module, or a
+policy imports the same module twice (using [import
+aliases](/sentinel/language/imports#aliases)), those instances will share state.
+If you call a function that alters a singleton variable within the module, that
+singleton will be modified for all instances of the import.
+
+-> **Fun fact!** When you [mock an import with Sentinel
+code](/sentinel/configuration#mocking-with-sentinel-code), you are actually
+using a module. The module is loaded with a flag that allows it to override an
+existing import, which would normally give a runtime error.
+
+### Map and List Cloning for Module Calls
+
+It's also worthwhile noting that map and list values are cloned for modules.
+
+Consider the case where you have a module with a map singleton.
+
+```sentinel
+// modules/foo.sentinel
+a_map = {"a": "b"}
+```
+
+Normally, assignment to the singleton, even when using an index expression, is
+blocked, so `foo.a_map["c"] = "d"` would not pass semantic checking.
+
+However, even when you try to do assignment via an intermediary, you will still
+be only modifying the copy, and the original will not be affected:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+a_map_copy = foo.a_map
+a_map_copy["c"] = "d" // Only modifies copy, does not modify module singleton
+print(foo.a_map) // Still only prints {"a": "b"}
+```
+
+This is to ensure that modules are consistent with binary imports in how return
+data is handled, and the expectation that most non-object data within an import
+is read-only, with the exception of modification of singleton data via
+functions. As return of complex object and collection data in a binary import is
+always via copy, it's not possible to modify any value elements within the
+import in a similar fashion.
+
+There are also some other implications of this cloning that are of note:
+
+- Rules are not cloned, either within a list or map, or by themselves. This is
+ to ensure that [memoization](/sentinel/language/rules#lazy-and-memoized)
+ functions correctly. As only a module can export rules at this time, there is no
+ parity for this in binary imports.
+- Functions are _not_ cloned. This mainly has implications for scope - for
+ example, if you assign a function to another value, or assign an object to a
+ value that has a method that utilizes a global module variable, those calls will
+ reference and/or modify those values.
+
+Functions, while represented differently in binary imports, generally follow the
+same logic here - as they would be invoked in the same package within the binary
+import, any singleton values they accessed there would be affected in a similar
+fashion. As rules are pseudo-functions, this generally makes sense here too.
+
+## Binary Imports
+
+_Binary imports_ is a grouping referring to all imports that are designed with
+the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). These can be
+either internally embedded, or executed via a plugin.
+
+All imports found in the [standard library](/sentinel/imports) are binary
+imports, embedded internally.
+
+The main difference between internally embedded imports and external plugins is
+whether or not the runtime needs to execute a plugin binary as part of
+[lifecycle management](#lifecycle), and whether or not it needs to
+[communicate](#communication) over RPC. Internal binary imports do not require
+these steps, so their execution model is somewhat simpler; however, technically,
+they still are the same as external plugins in terms of design model and value
+conversion. Most standard imports are even tested using the [SDK test
+suite](/sentinel/extending/plugins#testing), which builds the import as an
+external plugin.
+
+The remainder of this section discusses topics mostly relevant to external
+plugins. A large amount of technical detail regarding binary import development
+(also applicable to embedded binary imports) can be seen on the
+[Plugins](/sentinel/extending/plugins) page.
+
+-> **NOTE:** At this time, plugins can only be written for the Sentinel CLI, and
+cannot be used with any of Sentinel's integrations.
+
+### Plugin Basics
+
+Sentinel plugins are built on top of the HashiCorp [go-plugin
+system](https://github.com/hashicorp/go-plugin). This is the same plugin system
+powering all pluggable HashiCorp tools such as
+[Terraform](https://www.terraform.io), [Vault](https://www.vaultproject.io), and
+more. go-plugin is a system that has been used in production for millions of
+users for over 5 years.
+
+Plugins are executable binaries. Sentinel is responsible for launching and
+managing the lifecycle of plugins. Once a plugin is running, Sentinel
+communicates with the plugin via [gRPC](https://grpc.io/). The [protocol is open
+source](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/import.proto)
+to allow anyone to write a Sentinel plugin.
+
+### Lifecycle
+
+Sentinel launches plugins and is responsible for plugin lifecycle.
+
+The runtime optimizes for latency by launching the plugin as soon as it is
+configured, rather than when a policy requires it. This ensures that the plugin
+is ready to be used immediately. A plugin is only closed when Sentinel is
+reconfigured to no longer allow that plugin or if the Sentinel-enabled
+application is closing.
+
+When a plugin is removed from the configuration, Sentinel may wait to shut down
+the plugin until all currently executing policies that are using that plugin
+complete. New policy executions will not be allowed to use the old plugins.
+
+Plugins are automatically restarted if they shut down unexpectedly. Policies
+that were executing while this happens may fail. The Sentinel system is
+improving to more gracefully understand locations where it is safe to retry an
+execution.
+
+### Communication
+
+An [initial
+handshake](https://github.com/hashicorp/go-plugin/blob/master/docs/internals.md)
+is done via stdout to determine the main communication location. Following the
+handshake, communication will either occur on a local Unix domain socket or via
+a local-only TCP socket. This communication is done mostly over the low-level
+[`Get`](https://github.com/hashicorp/sentinel-sdk/blob/2b4db07dd12b55142f0c016f301af80aa4422520/proto/import.proto#L12)
+RPC call.
+
+#### Value Conversion
+
+As can generally be seen in [Developing an Import
+Plugin](/sentinel/extending/plugins#developing-an-import-plugin), the Sentinel
+type system is not exposed to binary imports. While explicit values for null and
+undefined exist, values are mostly converted over the wire from their Go values
+to their Sentinel equivalents.
+
+This has a few implications:
+
+- As discussed in [Map and List Cloning for Module
+ Calls](#map-and-list-cloning-for-module-calls), Map and list data returned from
+ binary import value lookups or function calls is always cloned. These can be
+ freely modified without affecting anything within the binary import.
+- It's currently impossible to represent certain Sentinel types such as
+ [rules](/sentinel/language/rules) within a binary import. This is not a major
+ issue at this point in time as practitioners generally do not look to binary
+ imports for rules; we may examine this as a later time if this situation
+ changes.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/extending/modules.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/extending/modules.mdx
new file mode 100644
index 0000000000..9846d263e5
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/extending/modules.mdx
@@ -0,0 +1,135 @@
+---
+page_title: >-
+ Extending Sentinel: Modules
+sidebar_current: docs-extending-modules
+description: >-
+ Modules allow you to re-use Sentinel code as an import.
+layout: docs
+---
+
+# Extending Sentinel: Modules
+
+Modules allow you to re-use Sentinel code as an import. This allows for the
+export of any general construct that Sentinel supports, including values,
+functions, and even rules. As such, it's a great way to package code that is
+useful across multiple policies, reducing boilerplate and making final policy
+code simpler.
+
+You may want to use modules for:
+
+- **Writing a simple re-usable helper library.** If you mostly know Sentinel or
+ have helpers that you have written in Sentinel, moving these helpers to a module
+ will offer a low-friction way of facilitating their re-use.
+- **Writing re-usable rules.** If you have a set of
+ [rules](/sentinel/language/rules) you know you want to use across multiple
+ policies, bundling them into a module is a great way of abstracting the logic
+ away.
+- **Abstracting existing Sentinel imports.** Using a module is a great way to
+ extend existing Sentinel imports by abstracting the common functionality that
+ you use within an import to your own workflow.
+
+This page describes how you can use modules within the context of the Sentinel
+CLI. For instructions for a particular integration, see the documentation for
+that product.
+
+-> **Not all Sentinel-enabled applications support modules.** Please refer
+to the documentation of the specific Sentinel-enabled application. Some
+applications disable modules for security reasons.
+
+## Configuring a Module
+
+A module is configured within the Sentinel CLI by adding an entry for it in
+within the `imports` section of the [CLI configuration
+file](/sentinel/configuration). The key for each entry specifies the path that
+the module is imported as.
+
+```hcl
+import "module" "foo" {
+ source = "modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "modules/bar.sentinel"
+}
+```
+
+In this example, we have two modules:
+
+- Import path `foo`, being loaded from the code within `modules/foo.sentinel`.
+- Import path `bar`, being loaded from the code within `modules/bar.sentinel`.
+
+See the [Import Modules](/sentinel/configuration#import-modules) section within
+the CLI configuration file syntax page for more details.
+
+### Remote Modules
+
+In addition to loading from a local source, modules can be loaded from a remote
+location. This can increase reusability and allow for sharing modules across
+multiple configurations.
+
+### Testing Using Modules
+
+As with [mocks](/sentinel/configuration#mocking-with-sentinel-code), modules
+are loaded relative to the configuration file location when using `sentinel test`. As such, you need to account for this in your test configuration files:
+
+```hcl
+import "module" "foo" {
+ source = "../../modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "../../modules/bar.sentinel"
+}
+```
+
+This could be a test file for a policy (example: `policy.sentinel`), residing
+under the correct test file directory for this policy (example:
+`test/policy/pass.json`).
+
+## Writing and Using a Module
+
+Writing a module is nearly the same as writing a Sentinel policy, except for the
+following two exceptions:
+
+- Modules do not require [`main`](/sentinel/writing/basic#the-simplest-policy)
+ to be present. It can be, but it will not carry any extra significance as it
+ does within a policy.
+- [`param`](/sentinel/language/parameters) declarations cannot be present in a module. If they are encountered,
+ a runtime error is given.
+
+Once you have a complete module ready for use and configured, using the module
+is as simple as importing it.
+
+Building on the above [configuration example](#configuring-a-module), we can
+export a simple test function in the module `foo` by adding this code to
+`modules/foo.sentinel`:
+
+```sentinel
+// modules/foo.sentinel
+hello = func() {
+ print("hello world!")
+ return undefined
+}
+```
+
+We can then import it within our policy and use it:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+// prints "hello world" in the trace
+foo.hello()
+
+main = true
+```
+
+Note that modules are considered [imports](/sentinel/language/spec#imports) by
+the Sentinel runtime and as such conform to the specification. This means that
+you cannot directly assign values to anything behind a module scope. You can,
+however, use functions to change state.
+
+-> **NOTE:** At this time, modules do not support object receiver data in the
+way that some imports do, like [`time`](/sentinel/imports/time),
+[`decimal`](/sentinel/imports/decimal), and [`http`](/sentinel/imports/http).
+Support for this will be coming in later versions of Sentinel.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/extending/plugins.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/extending/plugins.mdx
new file mode 100644
index 0000000000..e80de5d4b1
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/extending/plugins.mdx
@@ -0,0 +1,521 @@
+---
+page_title: >-
+ Extending Sentinel: Plugins
+sidebar_current: docs-extending-plugins
+description: >-
+ Plugins allow you to write imports as standalone executables, allowing you to
+ extend Sentinel in ways not normally possible with policy code alone.
+layout: docs
+---
+
+# Extending Sentinel: Plugins
+
+-> **NOTE:** Plugins are currently only supported on the CLI and not in any
+production Sentinel integration, with the exception of existing configuration
+in [Nomad](https://www.nomadproject.io/docs/configuration/sentinel).
+
+Plugins allow you to write imports as standalone executables, allowing you to
+extend Sentinel in ways not normally possible with policy code alone.
+
+You might want to write or use a plugin when you need to:
+
+- **Use a provider or API integration for Sentinel.** In this case, you will
+ probably be working with a provider SDK, or other libraries that will be making
+ complex network requests to external services. Sentinel only provides limited
+ capabilities for these kinds of operations in the standard library, so writing
+ this kind of import as a module is not optimal.
+- **Introduce novel functionality not currently supported by Sentinel.**
+ Sentinel's policy language is limited by design to encourage simple policies,
+ reduce its runtime footprint, and to keep it efficient and safe. This will
+ mean that some functionality may be difficult or impossible to express as
+ Sentinel code, and would require a plugin to write.
+- **Extend a complex module.** Additionally, modules are restricted to a single
+ file of Sentinel code, so these will naturally become more and more unwieldy as
+ you continue to add to them. Eventually, there might be a time where a plugin is
+ better suited for the functionality, especially if it's a general-purpose
+ library.
+
+This section details how import plugins are currently configured in the Sentinel
+CLI, and some detail on how they are written. As we continue to build on the
+ability to use import plugins, we will extend this section with more details.
+
+-> **Not all Sentinel-enabled applications will support plugins.** Some
+applications will disable plugins for security reasons. For those that do enable
+plugins, the method to configure plugins will vary.
+
+## Installing Plugins
+
+Sentinel plugins are standalone executables. Sentinel-enabled applications such
+as the CLI launch these plugins and communicate with them via
+[RPC](https://en.wikipedia.org/wiki/Remote_procedure_call). To install a plugin,
+the first step is to download the plugin executable.
+
+After downloading the plugin, you must add it in the CLI configuration file,
+setting its name, path, and any arguments, environment variables, or
+configuration. An example is below:
+
+```hcl
+import "plugin" "custom_time" {
+ source = "/path/to/sentinel-import-time"
+ config = { "fixed_time": 1504155600 }
+}
+```
+
+-> **NOTE:** We are using "custom_time" as standard import paths cannot be
+overriden. See [Configuration File Syntax](/sentinel/configuration#imports) for
+more details.
+
+After configuring the plugin, it will be available on the next `sentinel apply`
+or `sentinel test`.
+
+For more details on configuration, see the
+[plugins](/sentinel/configuration#plugins) section of the [CLI configuration
+file syntax](/sentinel/configuration).
+
+## Debugging Plugins
+
+The Sentinel CLI logs when it launches, configures, and closes a plugin. The
+plugin may also log additional information when it is running.
+
+To debug issues with a plugin, you can usually refer to these logs. Depending on
+the plugin configuration, the logs you see may vary - refer to the plugin
+documentation on how to enable logs for a particular plugin.
+
+## Developing an Import Plugin
+
+Anyone can develop a Sentinel import plugin using the [Sentinel
+SDK](https://github.com/hashicorp/sentinel-sdk). The SDK contains a high-level
+framework for writing plugins in [Go](https://golang.org) including a test
+framework. Additionally, the SDK contains the low-level [gRPC protocol buffers
+definition](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/plugin.proto)
+for writing plugins in other languages.
+
+The rest of this page will document how to write a new Sentinel plugin in Go
+using the Sentinel SDK and the high-level framework provided. You are expected
+to already know the basics of Go and have a properly configured Go installation.
+
+Throughout this page, we'll be developing a simple plugin to access time values.
+
+-> **NOTE:** The example here is not reflective of the actual implementation of
+the [`time`](/sentinel/imports/time) standard import, so make sure to not
+get the two confused.
+
+### Plugin Framework
+
+The Sentinel SDK provides a high-level
+[framework](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework)
+for writing plugins. We recommend using this framework.
+
+#### Basic Concepts
+
+This framework works by implementing the correct Go interfaces.
+
+As a general overview:
+[`framework.Plugin`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Plugin)
+implements the
+[`sdk.Plugin`](https://godoc.org/github.com/hashicorp/sentinel-sdk#Plugin)
+interface and therefore is a valid import plugin. You must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface to configure the plugin. The root may then delegate nested access to
+various
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+implementations. Other interfaces are implemented to provide functionality past
+what is provided by the basic `framework.Namespace` implementation - these are
+documented below.
+
+This all may sound like a lot of interfaces, but each interface typically only
+requires a single function implementation and you're only required to implement
+a single namespace (`framework.Namespace`).
+
+As we move on, we'll use real examples to make this clear.
+
+#### Implementing framework.Root
+
+To begin, you must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface. This is the interface representing the root of your plugin. The root
+itself must be implement
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace),
+which allows values to be accessed. Note that the root can optionally implement
+other interfaces, but they're more advanced and omitted here for simplicity.
+
+For our custom time example, our root may look like this:
+
+```sentinel
+type root struct {
+ time time.Time
+}
+
+// framework.Root impl.
+func (m *root) Configure(raw map[string]interface{}) error {
+ if _, ok := raw["timestamp"]; !ok {
+ raw["timestamp"] = time.Now().Unix()
+ }
+
+ v := raw["timestamp"]
+ timestamp, ok := v.(int)
+ if !ok {
+ return fmt.Errorf("invalid timestamp type %T", v)
+ }
+
+ m.time = time.Unix(timestamp, 0).UTC()
+ return nil
+}
+
+// framework.Namespace impl.
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "minute":
+ return m.time.Minute(), nil
+ }
+
+ return nil, nil
+}
+```
+
+This example implements `framework.Root` and `framework.Namespace` and would
+enable the following within a Sentinel policy once the completed plugin is
+installed.
+
+```sentinel
+import "custom_time"
+
+print(custom_time.minute)
+main = true
+```
+
+#### framework.Namespace
+
+The
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+interface implements a namespace of values. You saw above that
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+must itself be a namespace. However, a namespace `Get` implementation may
+further return namespaces to access nested values.
+
+This enables behavior such as `custom_time.month.string` vs. `custom_time.month.index`.
+Notice each of these examples accesses a nested value within `month`. This can
+be modeled as namespaces within the framework.
+
+In the example below, we return a new namespace for `month` to do just this:
+
+```sentinel
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "month":
+ return &namespaceMonth{Month: m.time.Month()}, nil
+ }
+
+ // ...
+}
+
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil nil
+}
+```
+
+#### Primitive Types and Structs
+
+A namespace may also return any primitive Go type as well as structs. The
+framework automatically exposes primitive values as you would expect. For
+structs, exported fields are lowercased and exposed to the policies. The
+`sentinel` struct tag can be used to control how Sentinel can access the field.
+
+In the example below, we expose a struct directly from the root namespace:
+
+```sentinel
+type Location struct {
+ Name string
+ TimeZone string `sentinel:"time_zone"`
+}
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "location":
+ return &Location{Name: "somewhere", TimeZone: "some zone"}, nil
+ }
+
+ // ...
+}
+```
+
+This makes the following values work within a Sentinel policy:
+`time.location.name`, `time.location.time_zone`.
+
+If a field has an empty `sentinel` struct tag (example: `sentinel:""`),
+then that field will not be accessible from a policy.
+
+#### Optional Interfaces
+
+The following are optional interfaces that can be implemented on top of
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace).
+All of these interfaces can be implemented at any level, except for
+[`framework.New`](#framework-new), which only works at the root level.
+
+##### `framework.Call`
+
+The
+[`framework.Call`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Call)
+interface can be implemented to support function calls.
+
+Implementing this interface is very similar to attribute access, except instead
+of returning the attribute value, you return a function. The framework uses Go
+reflection to determine the argument and result types and calls it. If the
+argument types do not match or the signature is otherwise invalid, an error is
+returned.
+
+For example, let's implement a function to add months to the current month:
+
+```sentinel
+func (m *root) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *root) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.time.AddDate(0, n, 0).Month()}
+}
+```
+
+You can now call the function. If today was in the month of September,
+the policy below would pass:
+
+```sentinel
+import "custom_time"
+
+main = custom_time.add_month(4).string == "January"
+```
+
+##### `framework.Map`
+
+The optional
+[`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+interface allows an alternative method to to present a namespace back to a
+Sentinel policy as a map.
+
+`framework.Map` is best paired with
+[`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys),
+which allows you to quickly fetch the necessary data via `Get` calls on the
+namespace:
+
+```
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil, nil
+}
+
+func (m *namespaceMonth) Map() (map[string]interface{}, error) {
+ return framework.MapFromKeys(m, []string{"string", "index"})
+}
+```
+
+##### `framework.New`
+
+The optional
+[`framework.New`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#New)
+interface can be implemented on a root namespace to add _methods_ to your
+namespaces.
+
+Data returned from a namespace is memoized and returned as a map, and is
+normally not callable - to call a function to operate on the data, you would
+need to create a new namespace from the top-level of the plugin, and then make a
+function call on the result within the same expression.
+
+Let's consider the [root example](#implementing-framework-root). Let's say,
+instead of hard-coding the time value at configuration, we wanted to load it
+on-demand using a `time.now` key, and allow `add_month` to be callable on that
+value. Our root and de-coupled time namespace would now look like:
+
+```
+type root struct{}
+
+func (m *root) Configure(raw map[string]interface{}) error { return nil }
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "now":
+ return &namespaceTime{Time: time.Now()}
+ }
+
+ return nil
+}
+
+func (m *root) New(data map[string]interface{}) (framework.Namespace, error) {
+ if v, ok := data["unix"]; ok {
+ if t, ok := v.(int64); !ok {
+ return &namespaceTime{Time: time.Unix(t, 0)}
+ }
+
+ return nil, fmt.Errorf("expected timestamp to be int64, got %T", v)
+ }
+
+ return nil, nil
+}
+
+type namespaceTime struct {
+ Time time.Time
+}
+
+func (m *namespaceTime) Get(key string) interface{} {
+ switch key {
+ case "unix":
+ return m.Time.Unix()
+
+ // case ...
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) Get(key string) (interface{}, error) {
+ return framework.MapFromKeys(m, []string{"unix", "..."})
+}
+
+func (m *namespaceTime) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.Time.AddDate(0, n, 0).Month()}
+}
+```
+
+Via this example, we can now create and assign a `namespaceTime` using `t = custom_time.now`, and then call `t.add_month(months_to_add)` to return a
+`namespaceMonth` for the corresponding month.
+
+Methods on a namespace can also _modfiy_ the receiver data. So you can, for
+example, add a method named `increment_month` that takes no arguments, but
+increments the time stored in `namespaceTime.Time` by a month. Subsequent
+statements using the receiver would see the new value.
+
+Note that there are some restrictions and guidelines that you should take into
+account when using `framework.New`:
+
+- Only data that makes it back to a policy can be used as receiver data to
+ instantiate new namespaces. As such it's recommended to make use of
+ [`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+ and
+ [`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys)
+ to return all of the attribute data you need to construct new objects.
+- `framework.New` is only supported on the root namespace and has no effect on
+ namespaces below the root. Check all possible cases of receiver data within
+ the top-level `New` function and return the appropriate namespace based on the
+ input data.
+- Do not return general errors from your `New` method. When an unknown lookup is
+ made off of memoized data, it will hit your `New` method for possible
+ instantiation and key calls. This will ensure `undefined` is correctly
+ returned for these cases.
+- `framework.New` is designed to add utility to plugins where calling methods on
+ a value is more intuitive than just making a function call, or just returning
+ all of a data set as a memoized map. Use it sparingly and avoid using it on
+ recursively complex data sets.
+
+### Testing
+
+The SDK exposes a testing framework to verify your plugin works as
+expected. This test framework integrates directly into `go test` so it
+is part of a familiar workflow.
+
+The test framework works by dynamically building your plugin and
+running it against the `sentinel` CLI to verify it behaves as expected.
+This ensures that your plugin builds, your plugin communicates via RPC
+correctly, and that the behavior within a policy is also correct.
+
+The example below shows a complete ready table-driven test for our time plugin.
+You can run this with a normal `go test`.
+
+```sentinel
+package main
+
+import (
+ "os"
+ "testing"
+
+ "github.com/hashicorp/sentinel-sdk"
+ plugintesting "github.com/hashicorp/sentinel-sdk/testing"
+)
+
+func TestMain(m *testing.M) {
+ exitCode := m.Run()
+ plugintesting.Clean()
+ os.Exit(exitCode)
+}
+
+func TestPlugin(t *testing.T) {
+ cases := []struct {
+ Name string
+ Data map[string]interface{}
+ Source string
+ }{
+ {
+ "month",
+ map[string]interface{}{"timestamp": 1495483674},
+ `main = subject.month.string == "September"`,
+ },
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.Name, func(t *testing.T) {
+ plugintesting.TestPlugin(t, plugintesting.TestPluginCase{
+ Config: tc.Data,
+ Source: tc.Source,
+ })
+ })
+ }
+}
+```
+
+### Building Your Plugin
+
+To build your plugin, you must first implement the `main` function.
+The main function should just use the `rpc.Serve` method to serve the
+plugin over the Sentinel RPC layer:
+
+```sentinel
+package main
+
+import (
+ "github.com/hashicorp/sentinel-sdk"
+ "github.com/hashicorp/sentinel-sdk/rpc"
+)
+
+func main() {
+ rpc.Serve(&rpc.ServeOpts{
+ PluginFunc: func() sdk.Plugin {
+ return &framework.Plugin{Root: &root{}}
+ },
+ })
+}
+```
+
+You can then build this using `go build`. The output can be named
+anything. Once your plugin is built, [install it](#installing-plugins)
+and try it out!
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/extending/static-imports.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/extending/static-imports.mdx
new file mode 100644
index 0000000000..cb51beb5d8
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/extending/static-imports.mdx
@@ -0,0 +1,48 @@
+---
+page_title: >-
+ Extending Sentinel: Static Imports
+sidebar_current: docs-extending-static-imports
+description: >-
+ Static imports allow you to write imports that use static data files.
+layout: docs
+---
+
+# Extending Sentinel: Static Imports
+
+Static imports allow you to write imports that use static data files. This can
+provide methods of consuming data in ways that are not available via
+[modules](./modules) or [plugins](./plugins).
+
+### Configuring a Static Import
+
+The [configuration page](/sentinel/configuration#static-imports) details how to
+configure static imports.
+
+### Usage
+
+Using a static import is not that different to using a plugin or module. The
+import must first be introduced to the policy via an import statement.
+
+```sentinel
+import "people"
+```
+
+From here, the static data can be used throughout the policy as you would any
+normal Sentinel value.
+
+```sentinel
+admins = filter people as person {
+ person.role is "Admin"
+}
+```
+
+One differentiator for static imports is that you can directly reference the
+import without the use of selectors. For example, to print the value;
+
+```sentinel
+print(people)
+```
+
+This is due to the static data being directly loaded and converted into a
+Sentinel value, ready for use. Static imports, like modules and plugins, cannot
+be assigned a new value.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/features/index.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/features/index.mdx
new file mode 100644
index 0000000000..dd9be64df0
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/features/index.mdx
@@ -0,0 +1,26 @@
+---
+page_title: Features
+sidebar_current: docs-features
+description: Learn how features can enhance the Sentinel runtime by enabling further capabilities.
+layout: docs
+---
+
+# Features
+
+Features are a set of capabilities that enhance the runtime experience. They
+are enabled through the [`sentinel`](/sentinel/configuration#sentinel) block of the
+configuration, using the features attribute. An example of how to enable features
+is as follows:
+
+```hcl
+sentinel {
+ features = {
+ terraform = true
+ }
+}
+```
+
+The current set of features are:
+
+- [terraform](/sentinel/features/terraform) - Allowing the Sentinel runtime
+ to interact with Terraform data.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/features/terraform/index.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/features/terraform/index.mdx
new file mode 100644
index 0000000000..a810dbe478
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/features/terraform/index.mdx
@@ -0,0 +1,24 @@
+---
+page_title: terraform
+sidebar_current: docs-features-terraform
+description: Learn about the terraform feature and its capabilities.
+layout: docs
+---
+
+# terraform feature
+
+The `terraform` feature enhances the Sentinel runtime to work with Terraform
+data. It will add the following imports to the standard library:
+
+- [tfplan/v1](/sentinel/features/terraform/tfplan-v1)
+- [tfplan/v2](/sentinel/features/terraform/tfplan-v2)
+- [tfconfig/v1](/sentinel/features/terraform/tfconfig-v1)
+- [tfconfig/v2](/sentinel/features/terraform/tfconfig-v2)
+- [tfstate/v1](/sentinel/features/terraform/tfstate-v1)
+- [tfstate/v2](/sentinel/features/terraform/tfstate-v2)
+
+-> **NOTE:** The above imports will only work with Terraform 0.12 and above,
+as they rely on the output from the `terraform show -json` command.
+
+It is recommended that the `/v2` suffixed imports are used, as they provide
+the best experience when interacting with the underlying data structures.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/features/terraform/tfconfig-v1.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/features/terraform/tfconfig-v1.mdx
new file mode 100644
index 0000000000..90179d7525
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/features/terraform/tfconfig-v1.mdx
@@ -0,0 +1,945 @@
+---
+page_title: tfconfig/v1 - terraform - Features
+sidebar_current: docs-features-terraform-config-v1
+description: The tfconfig/v1 import provides access to a Terraform configuration.
+layout: docs
+---
+
+# Import: tfconfig/v1
+
+The `tfconfig/v1` import provides access to a Terraform configuration.
+
+The Terraform configuration is the set of `*.tf` files that are used to
+describe the desired infrastructure state. Policies using the `tfconfig/v1`
+import can access all aspects of the configuration: providers, resources,
+data sources, modules, and variables.
+
+Some use cases for `tfconfig/v1` include:
+
+* **Organizational naming conventions**: requiring that configuration elements
+ are named in a way that conforms to some organization-wide standard.
+* **Required inputs and outputs**: organizations may require a particular set
+ of input variable names across all workspaces or may require a particular
+ set of outputs for asset management purposes.
+* **Enforcing particular modules**: organizations may provide a number of
+ "building block" modules and require that each workspace be built only from
+ combinations of these modules.
+* **Enforcing particular providers or resources**: an organization may wish to
+ require or prevent the use of providers and/or resources so that configuration
+ authors cannot use alternative approaches to work around policy
+ restrictions.
+
+Note with these use cases that this import is concerned with object _names_
+in the configuration. Since this is the configuration and not an invocation
+of Terraform, you can't see values for variables, the state, or the diff for
+a pending plan. If you want to write policy around expressions used
+within configuration blocks, you likely want to use the
+[`tfplan/v1`](/sentinel/features/terraform/tfplan-v1) import.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfconfig/v1" {
+ config = {
+ "plan": "./path/to/plan.json"
+ }
+}
+```
+
+## Namespace Overview
+
+The following is a tree view of the import namespace. For more detail on a
+particular part of the namespace, see below.
+
+-> **Note:** The root-level alias keys shown here (`data`, `modules`,
+`providers`, `resources`, and `variables`) are shortcuts to a [module
+namespace](#namespace-module) scoped to the root module. For more details, see
+the section on [root namespace aliases](#root-namespace-aliases).
+
+```
+tfconfig/v1
+├── module() (function)
+│ └── (module namespace)
+│ ├── data
+│ │ └── TYPE.NAME
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── provisioners
+│ │ └── NUMBER
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── type (string)
+│ ├── modules
+│ │ └── NAME
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ ├── source (string)
+│ │ └── version (string)
+│ ├──outputs
+│ │ └── NAME
+│ │ ├── depends_on (list of strings)
+│ │ ├── description (string)
+│ │ ├── sensitive (boolean)
+│ │ ├── references (list of strings)
+│ │ └── value (value)
+│ ├── providers
+│ │ └── TYPE
+│ │ ├── alias
+│ │ │ └── ALIAS
+│ │ │ ├── config (map of keys)
+│ │ | ├── references (map of keys)
+│ │ │ └── version (string)
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── version (string)
+│ ├── resources
+│ │ └── TYPE.NAME
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── provisioners
+│ │ └── NUMBER
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── type (string)
+│ └── variables
+│ └── NAME
+│ ├── default (value)
+│ └── description (string)
+├── module_paths ([][]string)
+│
+├── data (root module alias)
+├── modules (root module alias)
+├── outputs (root module alias)
+├── providers (root module alias)
+├── resources (root module alias)
+└── variables (root module alias)
+```
+
+### `references` Overview
+
+As an example, consider the following resource block:
+
+```hcl
+resource "local_file" "accounts" {
+ content = "some text"
+ filename = "${var.subdomain}.${var.domain}/accounts.txt"
+}
+```
+
+In this example, one might want to ensure `domain` and `subdomain` input
+variables are used within `filename` in this configuration.
+
+-> Any non-static values (such as interpolated strings) are not present within the
+configuration value and `references` should be used instead:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+# filename_references is a list of string values containing the references used in the expression
+filename_references = tfconfig.resources.local_file.accounts.references.filename
+
+main = rule {
+ filename_references contains "var.domain" and
+ filename_references contains "var.subdomain"
+}
+```
+
+The `references` value is present in any namespace where non-constant
+configuration values can be expressed. This is essentially every namespace
+which has a `config` value as well as the `outputs` namespace.
+
+-> **Note:** Remember, this import enforces policy around the literal Terraform
+configuration and not the final values as a result of invoking Terraform. If
+you want to write policy around the _result_ of expressions used within
+configuration blocks (for example, if you wanted to ensure the final value of
+`filename` above includes `accounts.txt`), you likely want to use the
+[`tfplan/v1`](/sentinel/features/terraform/tfplan-v1) import.
+
+## Namespace: Root
+
+The root-level namespace consists of the values and functions documented below.
+
+In addition to this, the root-level `data`, `modules`, `providers`, `resources`,
+and `variables` keys all alias to their corresponding namespaces within the
+[module namespace](#namespace-module).
+
+
+
+### Function: `module()`
+
+```
+module = func(ADDR)
+```
+
+* **Return Type:** A [module namespace](#namespace-module).
+
+The `module()` function in the [root namespace](#namespace-root) returns the
+[module namespace](#namespace-module) for a particular module address.
+
+The address must be a list and is the module address, split on the period (`.`),
+excluding the root module.
+
+Hence, a module with an address of simply `foo` (or `root.foo`) would be
+`["foo"]`, and a module within that (so address `foo.bar`) would be read as
+`["foo", "bar"]`.
+
+[`null`][ref-null] is returned if a module address is invalid, or if the module
+is not present in the configuration.
+
+[ref-null]: /sentinel/language/spec#null
+
+As an example, given the following module block:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+If the module contained the following content:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { subject.module(["foo"]).resources.null_resource.foo.config.triggers[0].foo is "bar" }
+```
+
+
+
+### Value: `module_paths`
+
+* **Value Type:** List of a list of strings.
+
+The `module_paths` value within the [root namespace](#namespace-root) is a list
+of all of the modules within the Terraform configuration.
+
+Modules not present in the configuration will not be present here, even if they
+are present in the diff or state.
+
+This data is represented as a list of a list of strings, with the inner list
+being the module address, split on the period (`.`).
+
+The root module is included in this list, represented as an empty inner list.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The value of `module_paths` would be:
+
+```
+[
+ [],
+ ["foo"],
+]
+```
+
+And the following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.module_paths contains ["foo"] }
+```
+
+#### Iterating Through Modules
+
+Iterating through all modules to find particular resources can be useful. This
+[example][iterate-over-modules] shows how to use `module_paths` with the
+[`module()` function](#function-module-) to find all resources of a
+particular type from all modules using the `tfplan` import. By changing `tfplan`
+in this function to `tfconfig`, you could make a similar function find all
+resources of a specific type in the Terraform configuration.
+
+[iterate-over-modules]: https://developer.hashicorp.com/terraform/cloud-docs/policy-enforcement/sentinel#sentinel-imports
+
+## Namespace: Module
+
+The **module namespace** can be loaded by calling [`module()`](#root-function-module)
+for a particular module.
+
+It can be used to load the following child namespaces:
+
+* `data` - Loads the [resource namespace](#namespace-resources-data-sources),
+ filtered against data sources.
+* `modules` - Loads the [module configuration
+ namespace](#namespace-module-configuration).
+* `outputs` - Loads the [output namespace](#namespace-outputs).
+* `providers` - Loads the [provider namespace](#namespace-providers).
+* `resources` - Loads the [resource
+ namespace](#namespace-resources-data-sources), filtered against resources.
+* `variables` - Loads the [variable namespace](#namespace-variables).
+
+### Root Namespace Aliases
+
+The root-level `data`, `modules`, `providers`, `resources`, and `variables` keys
+all alias to their corresponding namespaces within the module namespace, loaded
+for the root module. They are the equivalent of running `module([]).KEY`.
+
+
+
+## Namespace: Resources/Data Sources
+
+The **resource namespace** is a namespace _type_ that applies to both resources
+(accessed by using the `resources` namespace key) and data sources (accessed
+using the `data` namespace key).
+
+Accessing an individual resource or data source within each respective namespace
+can be accomplished by specifying the type and name, in the syntax
+`[resources|data].TYPE.NAME`.
+
+In addition, each of these namespace levels is a map, allowing you to filter
+based on type and name. Some examples of multi-level access are below:
+
+* To fetch all `aws_instance` resources within the root module, you can specify
+ `tfconfig.resources.aws_instance`. This would give you a map of resource
+ namespaces indexed from the names of each resource (`foo`, `bar`, and so
+ on).
+* To fetch all resources within the root module, irrespective of type, use
+ `tfconfig.resources`. This is indexed by type, as shown above with
+ `tfconfig.resources.aws_instance`, with names being the next level down.
+
+As an example, perhaps you wish to deny use of the `local_file` resource
+in your configuration. Consider the following resource block:
+
+```hcl
+resource "local_file" "foo" {
+ content = "foo!"
+ filename = "${path.module}/foo.bar"
+}
+```
+
+The following policy would fail:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.resources not contains "local_file" }
+```
+
+Further explanation of the namespace will be in the context of resources. As
+mentioned, when operating on data sources, use the same syntax, except with
+`data` in place of `resources`.
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+The `config` value within the [resource
+namespace](#namespace-resources-data-sources) is a map of key-value pairs that
+directly map to Terraform config keys and values.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#resources-value-references) should be used instead.
+
+As an example, consider the following resource block:
+
+```hcl
+resource "local_file" "accounts" {
+ content = "some text"
+ filename = "accounts.txt"
+}
+```
+
+In this example, one might want to access `filename` to validate that the correct
+file name is used. Given the above example, the following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule {
+ tfconfig.resources.local_file.accounts.config.filename is "accounts.txt"
+}
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [resource namespace](#namespace-resources-data-sources)
+contains the identifiers within non-constant expressions found in [`config`](#resources-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+
+
+### Value: `provisioners`
+
+* **Value Type:** List of [provisioner namespaces](#namespace-provisioners).
+
+The `provisioners` value within the [resource namespace](#namespace-resources)
+represents the [provisioners][ref-tf-provisioners] within a specific resource.
+
+Provisioners are listed in the order they were provided in the configuration
+file.
+
+While the `provisioners` value will be present within data sources, it will
+always be an empty map `null` (in Terraform 0.12) since data sources cannot
+actually have provisioners.
+
+The data within a provisioner can be inspected via the returned [provisioner
+namespace](#namespace-provisioners).
+
+[ref-tf-provisioners]: https://developer.hashicorp.com/terraform/language/resources/provisioners/syntax
+
+## Namespace: Provisioners
+
+The **provisioner namespace** represents the configuration for a particular
+[provisioner][ref-tf-provisioners] within a specific resource.
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+The `config` value within the [provisioner namespace](#namespace-provisioners)
+represents the values of the keys within the provisioner.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#provisioners-value-references) should be used instead.
+
+As an example, given the following resource block:
+
+```hcl
+resource "null_resource" "foo" {
+ # ...
+
+ provisioner "local-exec" {
+ command = "echo ${self.private_ip} > file.txt"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule {
+ tfconfig.resources.null_resource.foo.provisioners[0].config.command is "echo ${self.private_ip} > file.txt"
+}
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [provisioner namespace](#namespace-provisioners)
+contains the identifiers within non-constant expressions found in [`config`](#provisioners-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+
+
+### Value: `type`
+
+* **Value Type:** String.
+
+The `type` value within the [provisioner namespace](#namespace-provisioners)
+represents the type of the specific provisioner.
+
+As an example, in the following resource block:
+
+```hcl
+resource "null_resource" "foo" {
+ # ...
+
+ provisioner "local-exec" {
+ command = "echo ${self.private_ip} > file.txt"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.resources.null_resource.foo.provisioners[0].type is "local-exec" }
+```
+
+## Namespace: Module Configuration
+
+The **module configuration** namespace displays data on _module configuration_
+as it is given within a `module` block. This means that the namespace concerns
+itself with the contents of the declaration block (example: the `source`
+parameter and variable assignment keys), not the data within the module
+(example: any contained resources or data sources). For the latter, the module
+instance would need to be looked up with the [`module()`
+function](#root-function-module).
+
+
+
+### Value: `source`
+
+* **Value Type:** String.
+
+The `source` value within the [module configuration
+namespace](#namespace-module-configuration) represents the module source path as
+supplied to the module configuration.
+
+As an example, given the module declaration block:
+
+```hcl
+module "foo" {
+ source = "./foo"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.modules.foo.source is "./foo" }
+```
+
+
+
+### Value: `version`
+
+* **Value Type:** String.
+
+The `version` value within the [module configuration
+namespace](#namespace-module-configuration) represents the [version
+constraint][module-version-constraint] for modules that support it, such as
+modules within the [Terraform Module Registry][terraform-module-registry] or the
+[Terraform Cloud private module registry][tfe-private-registry].
+
+[module-version-constraint]: https://developer.hashicorp.com/terraform/language/modules#module-versions
+
+[terraform-module-registry]: https://registry.terraform.io/
+
+[tfe-private-registry]: https://developer.hashicorp.com/terraform/cloud-docs/registry
+
+As an example, given the module declaration block:
+
+```hcl
+module "foo" {
+ source = "foo/bar"
+ version = "~> 1.2"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.modules.foo.version is "~> 1.2" }
+```
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#modules-value-references) should be used instead.
+
+The `config` value within the [module configuration
+namespace](#namespace-module-configuration) represents the values of the keys
+within the module configuration. This is every key within a module declaration
+block except [`source`](#modules-value-source) and [`version`](#modules-value-version), which
+have their own values.
+
+As an example, given the module declaration block:
+
+```hcl
+module "foo" {
+ source = "./foo"
+
+ bar = "baz"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.modules.foo.config.bar is "baz" }
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [module configuration namespace](#namespace-module-configuration)
+contains the identifiers within non-constant expressions found in [`config`](#modules-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+## Namespace: Outputs
+
+The **output namespace** represents _declared_ output data within a
+configuration. As such, configuration for the [`value`](#outputs-value-value) attribute
+will be in its raw form, and not yet interpolated. For fully interpolated output
+values, see the [`tfstate` import][ref-tfe-sentinel-tfstate].
+
+[ref-tfe-sentinel-tfstate]: /sentinel/features/terraform/tfstate-v1
+
+This namespace is indexed by output name.
+
+
+
+### Value: `depends_on`
+
+* **Value Type:** A list of strings.
+
+The `depends_on` value within the [output namespace](#namespace-outputs)
+represents any _explicit_ dependencies for this output. For more information,
+see the [depends_on output setting][ref-depends_on] within the general Terraform
+documentation.
+
+[ref-depends_on]: https://developer.hashicorp.com/terraform/language/values/outputs#depends_on
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ depends_on = ["null_resource.bar"]
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.depends_on[0] is "null_resource.bar" }
+```
+
+
+
+### Value: `description`
+
+* **Value Type:** String.
+
+The `description` value within the [output namespace](#namespace-outputs)
+represents the defined description for this output.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ description = "foobar"
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.description is "foobar" }
+```
+
+
+
+### Value: `sensitive`
+
+* **Value Type:** Boolean.
+
+The `sensitive` value within the [output namespace](#namespace-outputs)
+represents if this value has been marked as sensitive or not.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ sensitive = true
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { subject.outputs.id.sensitive }
+```
+
+
+
+### Value: `value`
+
+* **Value Type:** Any primitive type, list or map.
+
+The `value` value within the [output namespace](#namespace-outputs) represents
+the defined value for the output as declared in the configuration. Primitives
+will bear the implicit type of their declaration (string, int, float, or bool),
+and maps and lists will be represented as such.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#outputs-value-references) should be used instead.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ value = "foo"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.value is "foo" }
+```
+
+
+
+### Value: `references`
+
+* **Value Type:**. List of strings.
+
+The `references` value within the [output namespace](#namespace-outputs)
+contains the names of any referenced identifiers when [`value`](#outputs-value-value)
+is a non-constant expression.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.references contains "null_resource.foo.id" }
+```
+
+## Namespace: Providers
+
+The **provider namespace** represents data on the declared providers within a
+namespace.
+
+This namespace is indexed by provider type and _only_ contains data about
+providers when actually declared. If you are using a completely implicit
+provider configuration, this namespace will be empty.
+
+This namespace is populated based on the following criteria:
+
+* The top-level namespace [`config`](#providers-value-config) and
+ [`version`](#providers-value-version) values are populated with the configuration and
+ version information from the default provider (the provider declaration that
+ lacks an alias).
+* Any aliased providers are added as namespaces within the
+ [`alias`](#providers-value-alias) value.
+* If a module lacks a default provider configuration, the top-level `config` and
+ `version` values will be empty.
+
+
+
+### Value: `alias`
+
+* **Value Type:** A map of [provider namespaces](#namespace-providers), indexed
+ by alias.
+
+The `alias` value within the [provider namespace](#namespace-providers)
+represents all declared [non-default provider
+instances][ref-tf-provider-instances] for a specific provider type, indexed by
+their specific alias.
+
+[ref-tf-provider-instances]: https://developer.hashicorp.com/terraform/language/providers/configuration#alias-multiple-provider-configurations
+
+The return type is a provider namespace with the data for the instance in
+question loaded. The `alias` key will not be available within this namespace.
+
+As an example, given the following provider declaration block:
+
+```hcl
+provider "aws" {
+ alias = "east"
+ region = "us-east-1"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.providers.aws.alias.east.config.region is "us-east-1" }
+```
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#providers-value-references) should be used instead.
+
+The `config` value within the [provider namespace](#namespace-providers)
+represents the values of the keys within the provider's configuration, with the
+exception of the provider version, which is represented by the
+[`version`](#providers-value-version) value. [`alias`](#providers-value-alias) is also not included
+when the provider is aliased.
+
+As an example, given the following provider declaration block:
+
+```hcl
+provider "aws" {
+ region = "us-east-1"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.providers.aws.config.region is "us-east-1" }
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [provider namespace](#namespace-providers)
+contains the identifiers within non-constant expressions found in [`config`](#providers-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+
+
+### Value: `version`
+
+* **Value Type:** String.
+
+The `version` value within the [provider namespace](#namespace-providers)
+represents the explicit expected version of the supplied provider. This includes
+the pessimistic operator.
+
+As an example, given the following provider declaration block:
+
+```hcl
+provider "aws" {
+ version = "~> 1.34"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.providers.aws.version is "~> 1.34" }
+```
+
+## Namespace: Variables
+
+The **variable namespace** represents _declared_ variable data within a
+configuration. As such, static data can be extracted, such as defaults, but not
+dynamic data, such as the current value of a variable within a plan (although
+this can be extracted within the [`tfplan` import][ref-tfe-sentinel-tfplan]).
+
+[ref-tfe-sentinel-tfplan]: /sentinel/features/terraform/tfplan-v1
+
+This namespace is indexed by variable name.
+
+
+
+### Value: `default`
+
+* **Value Type:** Any primitive type, list, map, or `null`.
+
+The `default` value within the [variable namespace](#namespace-variables)
+represents the default for the variable as declared in the configuration.
+
+The actual value will be as configured. Primitives will bear the implicit type
+of their declaration (string, int, float, or bool), and maps and lists will be
+represented as such.
+
+If no default is present, the value will be [`null`][ref-sentinel-null] (not to
+be confused with [`undefined`][ref-sentinel-undefined]).
+
+[ref-sentinel-null]: /sentinel/language/spec#null
+
+[ref-sentinel-undefined]: /sentinel/language/undefined
+
+As an example, given the following variable blocks:
+
+```hcl
+variable "foo" {
+ default = "bar"
+}
+
+variable "number" {
+ default = 42
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+default_foo = rule { tfconfig.variables.foo.default is "bar" }
+default_number = rule { tfconfig.variables.number.default is 42 }
+
+main = rule { default_foo and default_number }
+```
+
+
+
+### Value: `description`
+
+* **Value Type:** String.
+
+The `description` value within the [variable namespace](#namespace-variables)
+represents the description of the variable, as provided in configuration.
+
+As an example, given the following variable block:
+
+```hcl
+variable "foo" {
+ description = "foobar"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.variables.foo.description is "foobar" }
+```
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/features/terraform/tfconfig-v2.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/features/terraform/tfconfig-v2.mdx
new file mode 100644
index 0000000000..ddc282316b
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/features/terraform/tfconfig-v2.mdx
@@ -0,0 +1,441 @@
+---
+page_title: tfconfig/v2 - terraform - Features
+sidebar_current: docs-features-terraform-tfconfig-v2
+description: The tfconfig/v2 import provides access to a Terraform configuration.
+layout: docs
+---
+
+# Import: tfconfig/v2
+
+The `tfconfig/v2` import provides access to a Terraform configuration.
+
+The Terraform configuration is the set of `*.tf` files that are used to
+describe the desired infrastructure state. Policies using the `tfconfig`
+import can access all aspects of the configuration: providers, resources,
+data sources, modules, and variables.
+
+Some use cases for `tfconfig` include:
+
+* **Organizational naming conventions**: requiring that configuration elements
+ are named in a way that conforms to some organization-wide standard.
+* **Required inputs and outputs**: organizations may require a particular set
+ of input variable names across all workspaces or may require a particular
+ set of outputs for asset management purposes.
+* **Enforcing particular modules**: organizations may provide a number of
+ "building block" modules and require that each workspace be built only from
+ combinations of these modules.
+* **Enforcing particular providers or resources**: an organization may wish to
+ require or prevent the use of providers and/or resources so that configuration
+ authors cannot use alternative approaches to work around policy
+ restrictions.
+
+The data in the `tfconfig/v2` import is sourced from the JSON configuration file
+that is generated by the [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) command. For more information on
+the file format, see the [JSON Output Format](https://developer.hashicorp.com/terraform/internals/json-format)
+page.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfconfig/v2" {
+ config = {
+ "plan": "./path/to/plan.json"
+ }
+}
+```
+
+## Import Overview
+
+The `tfconfig/v2` import is structured as a series of _collections_, keyed as a
+specific format, such as resource address, module address, or a
+specifically-formatted provider key.
+
+```
+tfconfig/v2
+├── strip_index() (function)
+├── providers
+│ └── (indexed by [module_address:]provider[.alias])
+│ ├── provider_config_key (string)
+│ ├── name (string)
+│ ├── full_name (string)
+│ ├── alias (string)
+│ ├── module_address (string)
+│ ├── config (block expression representation)
+│ └── version_constraint (string)
+├── resources
+│ └── (indexed by address)
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── provider_config_key (string)
+│ ├── provisioners (list)
+│ │ └── (ordered provisioners for this resource only)
+│ ├── config (block expression representation)
+│ ├── count (expression representation)
+│ ├── for_each (expression representation)
+│ └── depends_on (list of strings)
+├── provisioners
+│ └── (indexed by resource_address:index)
+│ ├── resource_address (string)
+│ ├── type (string)
+│ ├── index (string)
+│ └── config (block expression representation)
+├── variables
+│ └── (indexed by module_address:name)
+│ ├── module_address (string)
+│ ├── name (string)
+│ ├── default (value)
+│ └── description (string)
+├── outputs
+│ └── (indexed by module_address:name)
+│ ├── module_address (string)
+│ ├── name (string)
+│ ├── sensitive (boolean)
+│ ├── value (expression representation)
+│ ├── description (string)
+│ └── depends_on (list of strings)
+└── module_calls
+ └── (indexed by module_address:name)
+ ├── module_address (string)
+ ├── name (string)
+ ├── source (string)
+ ├── config (block expression representation)
+ ├── count (expression representation)
+ ├── depends_on (expression representation)
+ ├── for_each (expression representation)
+ └── version_constraint (string)
+```
+
+The collections are:
+
+* [`providers`](#the-providers-collection) - The configuration for all provider
+ instances across all modules in the configuration.
+* [`resources`](#the-resources-collection) - The configuration of all resources
+ across all modules in the configuration.
+* [`variables`](#the-variables-collection) - The configuration of all variable
+ definitions across all modules in the configuration.
+* [`outputs`](#the-outputs-collection) - The configuration of all output
+ definitions across all modules in the configuration.
+* [`module_calls`](#the-module_calls-collection) - The configuration of all module
+ calls (individual [`module`](/terraform/language/modules) blocks) across
+ all modules in the configuration.
+
+These collections are specifically designed to be used with the
+[`filter`](/sentinel/language/collection-operations#filter-expression)
+quantifier expression in Sentinel, so that one can collect a list of resources
+to perform policy checks on without having to write complex module or
+configuration traversal. As an example, the following code will return all
+`aws_instance` resource types within the configuration, regardless of what
+module they are in:
+
+```
+all_aws_instances = filter tfconfig.resources as _, r {
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+You can add specific attributes to the filter to narrow the search, such as the
+module address. The following code would return resources in a module named
+`foo` only:
+
+```
+all_aws_instances = filter tfconfig.resources as _, r {
+ r.module_address is "module.foo" and
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+### Address Differences Between `tfconfig`, `tfplan`, and `tfstate`
+
+This import deals with configuration before it is expanded into a
+resource graph by Terraform. As such, it is not possible to compute an index as
+the import is building its collections and computing addresses for resources and
+modules.
+
+As such, addresses found here may not always match the expanded addresses found
+in the [`tfplan/v2`](/sentinel/features/terraform/tfplan-v2) and [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2)
+imports, specifically when
+[`count`](https://developer.hashicorp.com/terraform/language/resources#count-multiple-resource-instances-by-count)
+and
+[`for_each`](https://developer.hashicorp.com/terraform/language/resources#for_each-multiple-resource-instances-defined-by-a-map-or-set-of-strings),
+are used.
+
+As an example, consider a resource named `null_resource.foo` with a count of `2`
+located in a module named `bar`. While there will possibly be entries in the
+other imports for `module.bar.null_resource.foo[0]` and
+`module.bar.null_resource.foo[1]`, in `tfconfig/v2`, there will only be a
+`module.bar.null_resource.foo`. As mentioned in the start of this section, this
+is because configuration actually _defines_ this scaling, whereas _expansion_
+actually happens when the resource graph is built, which happens as a natural
+part of the refresh and planning process.
+
+The `strip_index` helper function, found in this import, can assist in
+removing the indexes from addresses found in the `tfplan/v2` and `tfstate/v2`
+imports so that data from those imports can be used to reference data in this
+one.
+
+## The `strip_index` Function
+
+The `strip_index` helper function can be used to remove indexes from addresses
+found in [`tfplan/v2`](/sentinel/features/terraform/tfplan-v2) and [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2),
+by removing the indexes from each resource.
+
+This can be used to help facilitate cross-import lookups for data between plan,
+state, and config.
+
+```
+import "tfconfig/v2" as tfconfig
+import "tfplan/v2" as tfplan
+
+main = rule {
+ all filter tfplan.resource_changes as _, rc {
+ rc.mode is "managed" and
+ rc.type is "aws_instance"
+ } as _, rc {
+ tfconfig.resources[tfconfig.strip_index(rc.address)].config.ami.constant_value is "ami-abcdefgh012345"
+ }
+}
+```
+
+## Expression Representations
+
+Most collections in this import will have one of two kinds of _expression
+representations_. This is a verbose format for expressing a (parsed)
+configuration value independent of the configuration source code, which is not
+100% available to a policy check in Terraform Cloud.
+
+```
+(expression representation)
+├── constant_value (value)
+└── references (list of strings)
+```
+
+There are two major parts to an expression representation:
+
+* Any _strictly constant value_ is expressed as an expression with a
+ `constant_value` field.
+* Any expression that requires some degree of evaluation to generate the final
+ value - even if that value is known at plan time - is not expressed in
+ configuration. Instead, any particular references that are made are added to
+ the `references` field. More details on this field can be found in the
+ [expression
+ representation](https://developer.hashicorp.com/terraform/internals/json-format#expression-representation)
+ section of the JSON output format documentation.
+
+For example, to determine if an output is based on a particular
+resource value, one could do:
+
+```
+import "tfconfig/v2" as tfconfig
+
+main = rule {
+ tfconfig.outputs["instance_id"].value.references is ["aws_instance.foo"]
+}
+```
+
+-> **Note:** The representation does not account for
+complex interpolations or other expressions that combine constants with other
+expression data. For example, the partially constant data in `"foo${var.bar}"` would be lost.
+
+### Block Expression Representation
+
+Expanding on the above, a multi-value expression representation (such as the
+kind found in a [`resources`](#the-resources-collection) collection element) is
+similar, but the root value is a keyed map of expression representations. This
+is repeated until a "scalar" expression value is encountered, ie: a field that
+is not a block in the resource's schema.
+
+```
+(block expression representation)
+└── (attribute key)
+ ├── (child block expression representation)
+ │ └── (...)
+ ├── constant_value (value)
+ └── references (list of strings)
+```
+
+As an example, one can validate expressions in an
+[`aws_instance`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance) resource using the
+following:
+
+```
+import "tfconfig/v2" as tfconfig
+
+main = rule {
+ tfconfig.resources["aws_instance.foo"].config.ami.constant_value is "ami-abcdefgh012345"
+}
+```
+
+Note that _nested blocks_, sometimes known as _sub-resources_, will be nested in
+configuration as as list of blocks (reflecting their ultimate nature as a list
+of objects). An example would be the `aws_instance` resource's
+[`ebs_block_device`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#ebs-ephemeral-and-root-block-devices) block:
+
+```
+import "tfconfig/v2" as tfconfig
+
+main = rule {
+ tfconfig.resources["aws_instance.foo"].config.ebs_block_device[0].volume_size < 10
+}
+```
+
+## The `providers` Collection
+
+The `providers` collection is a collection representing the configurations of
+all provider instances across all modules in the configuration.
+
+This collection is indexed by an opaque key. This is currently
+`module_address:provider.alias`, the same value as found in the
+`provider_config_key` field. `module_address` and the colon delimiter are
+omitted for the root module.
+
+The `provider_config_key` field is also found in the `resources` collection and
+can be used to locate a provider that belongs to a configured resource.
+
+The fields in this collection are as follows:
+
+* `provider_config_key` - The opaque configuration key, used as the index key.
+* `name` - The name of the provider, ie: `aws`.
+* `full_name` - The fully-qualified name of the provider, e.g. `registry.terraform.io/hashicorp/aws`.
+* `alias` - The alias of the provider, ie: `east`. Empty for a default provider.
+* `module_address` - The address of the module this provider appears in.
+* `config` - A [block expression
+ representation](#block-expression-representation) with provider configuration
+ values.
+* `version_constraint` - The defined version constraint for this provider.
+
+## The `resources` Collection
+
+The `resources` collection is a collection representing all of the resources
+found in all modules in the configuration.
+
+This collection is indexed by the resource address.
+
+The fields in this collection are as follows:
+
+* `address` - The resource address. This is the index of the collection.
+* `module_address` - The module address that this resource was found in.
+* `mode` - The resource mode, either `managed` (resources) or `data` (data
+ sources).
+* `type` - The type of resource, ie: `null_resource` in `null_resource.foo`.
+* `name` - The name of the resource, ie: `foo` in `null_resource.foo`.
+* `provider_config_key` - The opaque configuration key that serves as the index
+ of the [`providers`](#the-providers-collection) collection.
+* `provisioners` - The ordered list of provisioners for this resource. The
+ syntax of the provisioners matches those found in the
+ [`provisioners`](#the-provisioners-collection) collection, but is a list
+ indexed by the order the provisioners show up in the resource.
+* `config` - The [block expression
+ representation](#block-expression-representation) of the configuration values
+ found in the resource.
+* `count` - The [expression data](#expression-representations) for the `count`
+ value in the resource.
+* `for_each` - The [expression data](#expression-representations) for the
+ `for_each` value in the resource.
+* `depends_on` - The contents of the `depends_on` config directive, which
+ declares explicit dependencies for this resource.
+
+## The `provisioners` Collection
+
+The `provisioners` collection is a collection of all of the provisioners found
+across all resources in the configuration.
+
+While normally bound to a resource in an ordered fashion, this collection allows
+for the filtering of provisioners within a single expression.
+
+This collection is indexed with a key following the format
+`resource_address:index`, with each field matching their respective field in the
+particular element below:
+
+* `resource_address`: The address of the resource that the provisioner was found
+ in. This can be found in the [`resources`](#the-resources-collection)
+ collection.
+* `type`: The provisioner type, ie: `local_exec`.
+* `index`: The provisioner index as it shows up in the resource provisioner
+ order.
+* `config`: The [block expression
+ representation](#block-expression-representation) of the configuration values
+ in the provisioner.
+
+## The `variables` Collection
+
+The `variables` collection is a collection of all variables across all modules
+in the configuration.
+
+Note that this tracks variable definitions, not values. See the [`tfplan/v2`
+`variables` collection](/sentinel/features/terraform/tfplan-v2#the-variables-collection) for variable
+values set within a plan.
+
+This collection is indexed by the key format `module_address:name`, with each
+field matching their respective name below. `module_address` and the colon
+delimiter are omitted for the root module.
+
+* `module_address` - The address of the module the variable was found in.
+* `name` - The name of the variable.
+* `default` - The defined default value of the variable.
+* `description` - The description of the variable.
+
+## The `outputs` Collection
+
+The `outputs` collection is a collection of all outputs across all modules in
+the configuration.
+
+Note that this tracks variable definitions, not values. See the [`tfstate/v2`
+`outputs` collection](/sentinel/features/terraform/tfstate-v2#the-outputs-collection) for the final
+values of outputs set within a state. The [`tfplan/v2` `output_changes`
+collection](/sentinel/features/terraform/tfplan-v2#the-output_changes-collection) also contains a more
+complex collection of planned output changes.
+
+This collection is indexed by the key format `module_address:name`, with each
+field matching their respective name below. `module_address` and the colon
+delimiter are omitted for the root module.
+
+* `module_address` - The address of the module the output was found in.
+* `name` - The name of the output.
+* `sensitive` - Indicates whether or not the output was marked as
+ [`sensitive`](https://developer.hashicorp.com/terraform/language/values/outputs#sensitive-suppressing-values-in-cli-output).
+* `value` - An [expression representation](#expression-representations) for the output.
+* `description` - The description of the output.
+* `depends_on` - A list of resource names that the output depends on. These are
+ the hard-defined output dependencies as defined in the
+ [`depends_on`](https://developer.hashicorp.com/terraform/language/values/outputs#depends_on-explicit-output-dependencies)
+ field in an output declaration, not the dependencies that get derived from
+ natural evaluation of the output expression (these can be found in the
+ `references` field of the expression representation).
+
+## The `module_calls` Collection
+
+The `module_calls` collection is a collection of all module declarations at all
+levels within the configuration.
+
+Note that this is the
+[`module`](https://developer.hashicorp.com/terraform/language/modules#calling-a-child-module) stanza in
+any particular configuration, and not the module itself. Hence, a declaration
+for `module.foo` would actually be declared in the root module, which would be
+represented by a blank field in `module_address`.
+
+This collection is indexed by the key format `module_address:name`, with each
+field matching their respective name below. `module_address` and the colon
+delimiter are omitted for the root module.
+
+* `module_address` - The address of the module the declaration was found in.
+* `name` - The name of the module.
+* `source` - The contents of the `source` field.
+* `config` - A [block expression
+ representation](#block-expression-representation) for all parameter values
+ sent to the module.
+* `count` - An [expression representation](#expression-representations) for the
+ `count` field.
+* `depends_on`: An [expression representation](#expression-representations) for the
+ `depends_on` field.
+* `for_each` - An [expression representation](#expression-representations) for
+ the `for_each` field.
+* `version_constraint` - The string value found in the `version` field of the
+ module declaration.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/features/terraform/tfplan-v1.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/features/terraform/tfplan-v1.mdx
new file mode 100644
index 0000000000..0e0f9436a5
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/features/terraform/tfplan-v1.mdx
@@ -0,0 +1,610 @@
+---
+page_title: tfplan/v1 - terraform - Features
+sidebar_current: docs-features-terraform-tfplan-v1
+description: >-
+ The tfplan/v1 import provides access to a Terraform plan. A Terraform plan is the
+ file created as a result of `terraform plan` and is the input to `terraform
+ apply`. The plan represents the changes that Terraform needs to make to
+ infrastructure to reach the desired state represented by the configuration.
+layout: docs
+---
+
+# Import: tfplan/v1
+
+The `tfplan/v1` import provides access to a Terraform plan. A Terraform plan is the
+file created as a result of `terraform plan` and is the input to `terraform
+apply`. The plan represents the changes that Terraform needs to make to
+infrastructure to reach the desired state represented by the configuration.
+
+In addition to the diff data available in the plan, there is an
+[`applied`](#value-applied) state available that merges the plan with the state
+to create the planned state after apply.
+
+Finally, this import also allows you to access the configuration files and the
+Terraform state at the time the plan was run. See the section on [accessing a
+plan's state and configuration
+data](#accessing-a-plan-39-s-state-and-configuration-data) for more information.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan and schemas file.
+
+```hcl
+import "plugin" "tfplan/v1" {
+ config = {
+ "plan_path": "./path/to/plan.json",
+ "schemas_path": "./path/to/schemas.json"
+ }
+}
+```
+
+## Namespace Overview
+
+The following is a tree view of the import namespace. For more detail on a
+particular part of the namespace, see below.
+
+-> Note that the root-level alias keys shown here (`data`, `path`, and
+`resources`) are shortcuts to a [module namespace](#namespace-module) scoped to
+the root module. For more details, see the section on [root namespace
+aliases](#root-namespace-aliases).
+
+```
+tfplan/v1
+├── module() (function)
+│ └── (module namespace)
+│ ├── path ([]string)
+│ ├── data
+│ │ └── TYPE.NAME[NUMBER]
+│ │ ├── applied (map of keys)
+│ │ └── diff
+│ │ └── KEY
+│ │ ├── computed (bool)
+│ │ ├── new (string)
+│ │ └── old (string)
+│ └── resources
+│ └── TYPE.NAME[NUMBER]
+│ ├── applied (map of keys)
+│ ├── destroy (bool)
+│ ├── requires_new (bool)
+│ └── diff
+│ └── KEY
+│ ├── computed (bool)
+│ ├── new (string)
+│ └── old (string)
+├── module_paths ([][]string)
+├── terraform_version (string)
+├── variables (map of keys)
+│
+├── data (root module alias)
+├── path (root module alias)
+├── resources (root module alias)
+│
+├── config (tfconfig namespace alias)
+└── state (tfstate import alias)
+```
+
+## Namespace: Root
+
+The root-level namespace consists of the values and functions documented below.
+
+In addition to this, the root-level `data`, `path`, and `resources` keys alias
+to their corresponding namespaces or values within the [module
+namespace](#namespace-module).
+
+### Accessing a Plan's State and Configuration Data
+
+The `config` and `state` keys alias to the [`tfconfig`](/sentinel/features/terraform/tfconfig-v1) and
+[`tfstate`](/sentinel/features/terraform/tfstate-v1) namespaces, respectively, with the data sourced from
+the Terraform _plan_ (as opposed to actual configuration and state).
+
+-> Note that these aliases are not represented as maps. While they will appear
+empty when viewed as maps, the specific import namespace keys will still be
+accessible.
+
+### Function: `module()`
+
+```
+module = func(ADDR)
+```
+
+* **Return Type:** A [module namespace](#namespace-module).
+
+The `module()` function in the [root namespace](#namespace-root) returns the
+[module namespace](#namespace-module) for a particular module address.
+
+The address must be a list and is the module address, split on the period (`.`),
+excluding the root module.
+
+Hence, a module with an address of simply `foo` (or `root.foo`) would be
+`["foo"]`, and a module within that (so address `foo.bar`) would be read as
+`["foo", "bar"]`.
+
+[`null`](/sentinel/language/spec#null) is returned if a module address is
+invalid, or if the module is not present in the diff.
+
+As an example, given the following module block:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+If the module contained the following content:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.module(["foo"]).resources.null_resource.foo[0].applied.triggers.foo is "bar" }
+```
+
+### Value: `module_paths`
+
+* **Value Type:** List of a list of strings.
+
+The `module_paths` value within the [root namespace](#namespace-root) is a list
+of all of the modules within the Terraform diff for the current plan.
+
+Modules not present in the diff will not be present here, even if they are
+present in the configuration or state.
+
+This data is represented as a list of a list of strings, with the inner list
+being the module address, split on the period (`.`).
+
+The root module is included in this list, represented as an empty inner list, as
+long as there are changes.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The value of `module_paths` would be:
+
+```
+[
+ [],
+ ["foo"],
+]
+```
+
+And the following policy would evaluate to `true`:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.module_paths contains ["foo"] }
+```
+
+-> Note the above example only applies if the module is present in the diff.
+
+#### Iterating Through Modules
+
+Iterating through all modules to find particular resources can be useful. This
+[example][iterate-over-modules] shows how to use `module_paths` with the
+[`module()` function](#function-module-) to find all resources of a
+particular type from all modules that have pending changes using the `tfplan/v1`
+import.
+
+[iterate-over-modules]: https://developer.hashicorp.com/terraform/cloud-docs/policy-enforcement/sentinel#iterate-over-modules-and-find-resources
+
+### Value: `terraform_version`
+
+* **Value Type:** String.
+
+The `terraform_version` value within the [root namespace](#namespace-root)
+represents the version of Terraform used to create the plan. This can be used to
+enforce a specific version of Terraform in a policy check.
+
+As an example, the following policy would evaluate to `true`, as long as the
+plan was made with a version of Terraform in the 0.11.x series, excluding any
+pre-release versions (example: `-beta1` or `-rc1`):
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.terraform_version matches "^0\\.11\\.\\d+$" }
+```
+
+### Value: `variables`
+
+* **Value Type:** A string-keyed map of values.
+
+The `variables` value within the [root namespace](#namespace-root) represents
+all of the variables that were set when creating the plan. This will only
+contain variables set for the root module.
+
+Note that unlike the [`default`][import-tfconfig-variables-default] value in the
+[`tfconfig` variables namespace][import-tfconfig-variables], primitive values
+here are stringified, and type conversion will need to be performed to perform
+comparison for int, float, or boolean values. This only applies to variables
+that are primitives themselves and not primitives within maps and lists, which
+will be their original types.
+
+[import-tfconfig-variables-default]: /sentinel/features/terraform/tfconfig-v1#value-default
+
+[import-tfconfig-variables]: /sentinel/features/terraform/tfconfig-v1#namespace-variables
+
+If a default was accepted for the particular variable, the default value will be
+populated here.
+
+As an example, given the following variable blocks:
+
+```hcl
+variable "foo" {
+ default = "bar"
+}
+
+variable "number" {
+ default = 42
+}
+
+variable "map" {
+ default = {
+ foo = "bar"
+ number = 42
+ }
+}
+```
+
+The following policy would evaluate to `true`, if no values were entered to
+change these variables:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+default_foo = rule { tfplan.variables.foo is "bar" }
+default_number = rule { tfplan.variables.number is "42" }
+default_map_string = rule { tfplan.variables.map["foo"] is "bar" }
+default_map_int = rule { tfplan.variables.map["number"] is 42 }
+
+main = rule { default_foo and default_number and default_map_string and default_map_int }
+```
+
+## Namespace: Module
+
+The **module namespace** can be loaded by calling
+[`module()`](#function-module-) for a particular module.
+
+It can be used to load the following child namespaces, in addition to the values
+documented below:
+
+* `data` - Loads the [resource namespace](#namespace-resources-data-sources),
+ filtered against data sources.
+* `resources` - Loads the [resource
+ namespace](#namespace-resources-data-sources), filtered against resources.
+
+### Root Namespace Aliases
+
+The root-level `data` and `resources` keys both alias to their corresponding
+namespaces within the module namespace, loaded for the root module. They are the
+equivalent of running `module([]).KEY`.
+
+### Value: `path`
+
+* **Value Type:** List of strings.
+
+The `path` value within the [module namespace](#namespace-module) contains the
+path of the module that the namespace represents. This is represented as a list
+of strings.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The following policy would evaluate to `true` _only_ if the diff had changes for
+that module:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.module(["foo"]).path contains "foo" }
+```
+
+## Namespace: Resources/Data Sources
+
+The **resource namespace** is a namespace _type_ that applies to both resources
+(accessed by using the `resources` namespace key) and data sources (accessed
+using the `data` namespace key).
+
+Accessing an individual resource or data source within each respective namespace
+can be accomplished by specifying the type, name, and resource number (as if the
+resource or data source had a `count` value in it) in the syntax
+`[resources|data].TYPE.NAME[NUMBER]`. Note that NUMBER is always needed, even if
+you did not use `count` in the resource.
+
+In addition, each of these namespace levels is a map, allowing you to filter
+based on type and name.
+
+-> The (somewhat strange) notation here of `TYPE.NAME[NUMBER]` may imply that
+the inner resource index map is actually a list, but it's not - using the square
+bracket notation over the dotted notation (`TYPE.NAME.NUMBER`) is required here
+as an identifier cannot start with a number.
+
+Some examples of multi-level access are below:
+
+* To fetch all `aws_instance.foo` resource instances within the root module, you
+ can specify `tfplan.resources.aws_instance.foo`. This would then be indexed by
+ resource count index (`0`, `1`, `2`, and so on). Note that as mentioned above,
+ these elements must be accessed using square-bracket map notation (so `[0]`,
+ `[1]`, `[2]`, and so on) instead of dotted notation.
+* To fetch all `aws_instance` resources within the root module, you can specify
+ `tfplan.resources.aws_instance`. This would be indexed from the names of
+ each resource (`foo`, `bar`, and so on), with each of those maps containing
+ instances indexed by resource count index as per above.
+* To fetch all resources within the root module, irrespective of type, use
+ `tfplan.resources`. This is indexed by type, as shown above with
+ `tfplan.resources.aws_instance`, with names being the next level down, and so
+ on.
+
+~> When [resource targeting](https://developer.hashicorp.com/terraform/cli/commands/plan#resource-targeting) is
+ in effect, `tfplan.resources` will only include the resources specified as
+ targets for the run. This may lead to unexpected outcomes if a policy expects
+ a resource to be present in the plan.
+
+Further explanation of the namespace will be in the context of resources. As
+mentioned, when operating on data sources, use the same syntax, except with
+`data` in place of `resources`.
+
+### Value: `applied`
+
+* **Value Type:** A string-keyed map of values.
+
+The `applied` value within the [resource
+namespace](#namespace-resources-data-sources) contains a "predicted"
+representation of the resource's state post-apply. It's created by merging the
+pending resource's diff on top of the existing data from the resource's state
+(if any). The map is a complex representation of these values with data going
+as far down as needed to represent any state values such as maps, lists, and
+sets.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if the resource was in the diff:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].applied.triggers.foo is "bar" }
+```
+
+-> Note that some values will not be available in the `applied` state because
+they cannot be known until the plan is actually applied. In Terraform 0.11 or
+earlier, these values are represented by a placeholder (the UUID value
+`74D93920-ED26-11E3-AC10-0800200C9A66`) and in Terraform 0.12 or later they
+are `undefined`. **In either case**, you should instead use the
+[`computed`](#value-computed) key within the [diff
+namespace](#namespace-resource-diff) to determine that a computed value will
+exist.
+
+-> If a resource is being destroyed, its `applied` value is omitted from the
+namespace and trying to fetch it will return undefined.
+
+### Value: `diff`
+
+* **Value Type:** A map of [diff namespaces](#namespace-resource-diff).
+
+The `diff` value within the [resource
+namespace](#namespace-resources-data-sources) contains the diff for a particular
+resource. Each key within the map links to a [diff
+namespace](#namespace-resource-diff) for that particular key.
+
+Note that unlike the [`applied`](#value-applied) value, this map is not complex;
+the map is only 1 level deep with each key possibly representing a diff for a
+particular complex value within the resource.
+
+See the below section for more details on the diff namespace, in addition to
+usage examples.
+
+### Value: `destroy`
+
+* **Value Type:** Boolean.
+
+The `destroy` value within the [resource
+namespace](#namespace-resources-data-sources) is `true` if a resource is being
+destroyed for _any_ reason, including cases where it's being deleted as part of
+a resource re-creation, in which case [`requires_new`](#value-requires_new) will
+also be set.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` when `null_resource.foo` is being
+destroyed:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].destroy }
+```
+
+### Value: `requires_new`
+
+* **Value Type:** Boolean.
+
+The `requires_new` value within the [resource
+namespace](#namespace-resources-data-sources) is `true` if the resource is still
+present in the configuration, but must be replaced to satisfy its current diff.
+Whenever `requires_new` is `true`, [`destroy`](#value-destroy) is also `true`.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if one of the `triggers` in
+`null_resource.foo` was being changed:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].requires_new }
+```
+
+## Namespace: Resource Diff
+
+The **diff namespace** is a namespace that represents the diff for a specific
+attribute within a resource. For details on reading a particular attribute,
+see the [`diff`](#value-diff) value in the [resource
+namespace](#namespace-resources-data-sources).
+
+### Value: `computed`
+
+* **Value Type:** Boolean.
+
+The `computed` value within the [diff namespace](#namespace-resource-diff) is
+`true` if the resource key in question depends on another value that isn't yet
+known. Typically, that means the value it depends on belongs to a resource that
+either doesn't exist yet, or is changing state in such a way as to affect the
+dependent value so that it can't be known until the apply is complete.
+
+-> Keep in mind that when using `computed` with complex structures such as maps,
+lists, and sets, it's sometimes necessary to test the count attribute for the
+structure, versus a key within it, depending on whether or not the diff has
+marked the whole structure as computed. This is demonstrated in the example
+below. Count keys are `%` for maps, and `#` for lists and sets. If you are
+having trouble determining the type of specific field within a resource, contact
+the support team.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+
+resource "null_resource" "bar" {
+ triggers = {
+ foo_id = "${null_resource.foo.id}"
+ }
+}
+```
+
+The following policy would evaluate to `true`, if the `id` of
+`null_resource.foo` was currently not known, such as when the resource is
+pending creation, or is being deleted and re-created:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.bar[0].diff["triggers.%"].computed }
+```
+
+### Value: `new`
+
+* **Value Type:** String.
+
+The `new` value within the [diff namespace](#namespace-resource-diff) contains
+the new value of a changing attribute, _if_ the value is known at plan time.
+
+-> `new` will be an empty string if the attribute's value is currently unknown.
+For more details on detecting unknown values, see [`computed`](#value-computed).
+
+Note that this value is _always_ a string, regardless of the actual type of the
+value changing. [Type conversion][ref-sentinel-type-conversion] within policy
+may be necessary to achieve the comparison needed.
+
+[ref-sentinel-type-conversion]: https://docs.hashicorp.com/sentinel/language/values#type-conversion
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`, if the resource was in the diff
+and each of the concerned keys were changing to new values:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].diff["triggers.foo"].new is "bar" }
+```
+
+### Value: `old`
+
+* **Value Type:** String.
+
+The `old` value within the [diff namespace](#namespace-resource-diff) contains
+the old value of a changing attribute.
+
+Note that this value is _always_ a string, regardless of the actual type of the
+value changing. [Type conversion][ref-sentinel-type-conversion] within policy
+may be necessary to achieve the comparison needed.
+
+If the value did not exist in the previous state, `old` will always be an empty
+string.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "baz"
+ }
+}
+```
+
+If that resource was previously in config as:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].diff["triggers.foo"].old is "bar" }
+```
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/features/terraform/tfplan-v2.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/features/terraform/tfplan-v2.mdx
new file mode 100644
index 0000000000..13793424e8
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/features/terraform/tfplan-v2.mdx
@@ -0,0 +1,389 @@
+---
+page_title: tfplan/v2 - terraform - Features
+sidebar_current: docs-features-terraform-tfplan-v2
+description: >-
+ The tfplan import provides access to a Terraform plan. A Terraform plan is the
+ file created as a result of `terraform plan` and is the input to `terraform
+ apply`. The plan represents the changes that Terraform needs to make to
+ infrastructure to reach the desired state represented by the configuration.
+layout: docs
+---
+
+# Import: tfplan/v2
+
+The `tfplan/v2` import provides access to a Terraform plan.
+
+A Terraform plan is the file created as a result of `terraform plan` and is the
+input to `terraform apply`. The plan represents the changes that Terraform needs
+to make to infrastructure to reach the desired state represented by the
+configuration.
+
+In addition to the diff data available in the plan, there is a "planned state"
+that is available through this import, via the
+[`planned_values`](#the-planned_values-collection) collection. This collection
+presents the Terraform state as how it might look after the plan data is
+applied, but is not guaranteed to be the final state.
+
+The data in the `tfplan/v2` import is sourced from the JSON configuration file
+that is generated by the [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) command. For more information on
+the file format, see the [JSON Output Format](https://developer.hashicorp.com/terraform/internals/json-format)
+page.
+
+The entirety of the JSON output file is exposed as a Sentinel map via the
+[`raw`](#the-raw-collection) collection. This allows direct, low-level access to
+the JSON data, but should only be used in complex situations where the
+higher-level collections do not serve the purpose.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfplan/v2" {
+ config = {
+ "plan_path": "./path/to/plan.json"
+ }
+}
+```
+
+## Import Overview
+
+The `tfplan/v2` import is structured as a series of _collections_, keyed as a
+specific format depending on the collection.
+
+```
+tfplan/v2
+├── terraform_version (string)
+├── variables
+│ └── (indexed by name)
+│ ├── name (string)
+│ └── value (value)
+├── planned_values
+│ ├── outputs (tfstate/v2 outputs representation)
+│ └── resources (tfstate/v2 resources representation)
+├── resource_changes
+│ └── (indexed by address[:deposed])
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── index (float (number) or string)
+│ ├── provider_name (string)
+│ ├── deposed (string)
+│ └── change (change representation)
+├── output_changes
+│ └── (indexed by name)
+│ ├── name (string)
+│ └── change (change representation)
+└── raw (map)
+```
+
+The collections are:
+
+* [`variables`](#the-variables-collection) - The values of variables that have
+ been set in the plan itself. This collection only contains variables set in
+ the root module.
+* [`planned_values`](#the-planned_values-collection) - The state representation
+ of _planned values_, or an estimation of what the state will look like after
+ the plan is applied.
+* [`resource_changes`](#the-resource_changes-collection) - The set of change
+ operations for resources and data sources within this plan.
+* [`output_changes`](#the-output_changes-collection) - The changes to outputs
+ within this plan. This collection only contains outputs set in the root
+ module.
+* [`raw`](#the-raw-collection) - Access to the raw plan data.
+
+These collections are specifically designed to be used with the
+[`filter`](/sentinel/language/collection-operations#filter-expression)
+quantifier expression in Sentinel, so that one can collect a list of resources
+to perform policy checks on without having to write complex discovery code. As
+an example, the following code will return all `aws_instance` resource changes,
+across all modules in the plan:
+
+```
+all_aws_instances = filter tfplan.resource_changes as _, rc {
+ rc.mode is "managed" and
+ rc.type is "aws_instance"
+}
+```
+
+You can add specific attributes to the filter to narrow the search, such as the
+module address, or the operation being performed. The following code would
+return resources in a module named `foo` only, and further narrow the search
+down to only resources that were being created:
+
+```
+all_aws_instances = filter tfplan.resource_changes as _, rc {
+ rc.module_address is "module.foo" and
+ rc.mode is "managed" and
+ rc.type is "aws_instance" and
+ rc.change.actions is ["create"]
+}
+```
+
+### Change Representation
+
+Certain collections in this import contain a _change representation_, an object
+with details about changes to a particular entity, such as a resource (within
+the [`resource_changes`](#the-resource_changes-collection) collection), or
+output (within the [`output_changes`](#the-output_changes-collection)
+collection).
+
+```
+(change representation)
+├── actions (list)
+├── before (value, or map)
+├── after (value, or map)
+└── after_unknown (boolean, or map of booleans)
+```
+
+This change representation contains the following fields:
+
+* `actions` - A list of actions being carried out for this change. The order is
+ important, for example a regular replace operation is denoted by `["delete",
+ "create"]`, but a
+ [`create_before_destroy`](https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle#create_before_destroy)
+ resource will have an operation order of `["create", "delete"]`.
+* `before` - The representation of the resource data object value before the
+ action. For create-only actions, this is unset. For no-op actions, this value
+ will be identical with `after`.
+* `after` - The representation of the resource data object value after the
+ action. For delete-only actions, this is unset. For no-op actions, this value
+ will be identical with `before`. Note that unknown values will not show up in
+ this field.
+* `after_unknown` - A deep object of booleans that denotes any values that are
+ unknown in a resource. These values were previously referred to as "computed"
+ values. If the value cannot be found in this map, then its value should be
+ available within `after`, so long as the operation supports it.
+
+#### Actions
+
+As mentioned above, actions show up within the `actions` field of a change
+representation and indicate the type of actions being performed as part of the
+change, and the order that they are being performed in.
+
+The current list of actions are as follows:
+
+* `create` - The action will create the associated entity. Depending on the
+ order this appears in, the entity may be created alongside a copy of the
+ entity before replacing it.
+* `read` - The action will read the associated entity. In practice, seeing this
+ change type should be rare, as reads generally happen before a plan is
+ executed (usually during a refresh).
+* `update` - The action will update the associated entity in a way that alters its state
+ in some way.
+* `delete` - The action will remove the associated entity, deleting any
+ applicable state and associated real resources or infrastructure.
+* `no-op` - No action will be performed on the associated entity.
+
+The `actions` field is a list, as some real-world actions are actually a
+composite of more than one primitive action. At this point in time, this
+is generally only applicable to resource replacement, in which the following
+action orders apply:
+
+* **Normal replacement:** `["delete", "create"]` - Applies to default lifecycle
+ configurations.
+* **Create-before-destroy:** `["create", "delete"]` - Applies when
+ [`create_before_destroy`](https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle#create_before_destroy)
+ is used in a lifecycle configuration.
+
+Note that, in most situations, the plan will list all "changes", including no-op
+changes. This makes filtering on change type crucial to the accurate selection
+of data if you are concerned with the state change of a particular resource.
+
+To filter on a change type, use exact list comparison. For example, the
+following example from the [Import Overview](#import-overview) filters on
+exactly the resources being created _only_:
+
+```
+all_aws_instances = filter tfplan.resource_changes as _, rc {
+ rc.module_address is "module.foo" and
+ rc.mode is "managed" and
+ rc.type is "aws_instance" and
+ rc.change.actions is ["create"]
+}
+```
+
+#### `before`, `after`, and `after_unknown`
+
+The exact attribute changes for a particular operation are outlined in the
+`before` and `after` attributes. Depending on the entity being operated on, this
+will either be a map (as with
+[`resource_changes`](#the-resource_changes-collection)) or a singular value (as
+with [`output_changes`](#the-output_changes-collection)).
+
+What you can expect in these fields varies depending on the operation:
+
+* For fresh create operations, `before` will generally be `null`, and `after`
+ will contain the data you can expect to see after the change.
+* For full delete operations, this will be reversed - `before` will contain
+ data, and `after` will be `null`.
+* Update or replace operations will have data in both fields relevant to their
+ states before and after the operation.
+* No-op operations should have identical data in `before` and `after`.
+
+For resources, if a field cannot be found in `after`, it generally means one of
+two things:
+
+* The attribute does not exist in the resource schema. Generally, known
+ attributes that do not have a value will show up as `null` or otherwise empty
+ in `after`.
+* The attribute is _unknown_, that is, it was unable to be determined at plan
+ time and will only be available after apply-time values have been able to be
+ calculated.
+
+In the latter case, there should be a value for the particular attribute in
+`after_unknown`, which can be checked to assert that the value is indeed
+unknown, versus invalid:
+
+```
+import "tfplan/v2" as tfplan
+
+no_unknown_amis = rule {
+ all filter tfplan.resource_changes as _, rc {
+ rc.module_address is "module.foo" and
+ rc.mode is "managed" and
+ rc.type is "aws_instance" and
+ rc.change.actions is ["create"]
+ } as _, rc {
+ rc.change.after_unknown.ami else false is false
+ }
+}
+```
+
+For output changes, `after_unknown` will simply be `true` if the value won't be
+known until the plan is applied.
+
+## The `terraform_version` Value
+
+The top-level `terraform_version` value in this import gives the Terraform
+version that made the plan. This can be used to do version validation.
+
+```
+import "tfplan/v2" as tfplan
+import "strings"
+
+v = strings.split(tfplan.terraform_version, ".")
+version_major = int(v[1])
+version_minor = int(v[2])
+
+main = rule {
+ version_major is 12 and version_minor >= 19
+}
+```
+
+## The `variables` Collection
+
+The `variables` collection is a collection of the variables set in the root
+module when creating the plan.
+
+This collection is indexed on the name of the variable.
+
+The valid values are:
+
+* `name` - The name of the variable, also used as the collection key.
+* `value` - The value of the variable assigned during the plan.
+
+## The `planned_values` Collection
+
+The `planned_values` collection is a special collection in that it contains two
+fields that alias to state collections with the _planned_ state set. This is the
+best prediction of what the state will look like after the plan is executed.
+
+The two fields are:
+
+* `outputs` - The prediction of what output values will look like after the
+ state is applied. For more details on the structure of this collection, see
+ the [`outputs`](/sentinel/features/terraform/tfstate-v2#the-outputs-collection) collection in the
+ [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2) documentation.
+* `resources` - The prediction of what resource values will look like after the
+ state is applied. For more details on the structure of this collection, see
+ the [`resources`](/sentinel/features/terraform/tfstate-v2#the-resources-collection) collection in the
+ [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2) documentation.
+
+-> **NOTE:** Unknown values are omitted from the `planned_values` state
+representations, regardless of whether or not they existed before. Use
+[`resource_changes`](#the-resource_changes-collection) if awareness of unknown
+data is important.
+
+## The `resource_changes` Collection
+
+The `resource_changes` collection is the set of change operations for resources
+and data sources within this plan.
+
+This includes all resources that have been found in the configuration and state,
+regardless of whether or not they are changing.
+
+~> When [resource targeting](https://developer.hashicorp.com/terraform/cli/commands/plan#resource-targeting)
+ is in effect, the `resource_changes` collection will only include the
+ resources specified as targets for the run. This may lead to unexpected
+ outcomes if a policy expects a resource to be present in the plan.
+
+This collection is indexed on the complete resource address as the key. If
+`deposed` is non-empty, it is appended to the end, and may look something like
+`aws_instance.foo:deposed-abc123`.
+
+An element contains the following fields:
+
+* `address` - The absolute resource address - also the key for the collection's
+ index, if `deposed` is empty.
+
+* `module_address` - The module portion of the absolute resource address.
+
+* `mode` - The resource mode, either `managed` (resources) or `data` (data
+ sources).
+
+* `type` - The resource type, example: `aws_instance` for `aws_instance.foo`.
+
+* `name` - The resource name, example: `foo` for `aws_instance.foo`.
+
+* `index` - The resource index. Can be either a number or a string.
+
+* `provider_name` - The name of the provider this resource belongs to. This
+ allows the provider to be interpreted unambiguously in the unusual situation
+ where a provider offers a resource type whose name does not start with its own
+ name, such as the `googlebeta` provider offering `google_compute_instance`.
+
+ -> **Note:** Starting with Terraform 0.13, the `provider_name` field contains the
+ _full_ source address to the provider in the Terraform Registry. Example:
+ `registry.terraform.io/hashicorp/null` for the null provider.
+
+* `deposed` - An identifier used during replacement operations, and can be used
+ to identify the exact resource being replaced in state.
+
+* `change` - The data describing the change that will be made to this resource.
+ For more details, see [Change Representation](#change-representation).
+
+## The `output_changes` Collection
+
+The `output_changes` collection is a collection of the change operations for
+outputs within this plan.
+
+Only outputs for the root module are included.
+
+This collection is indexed by the name of the output. The fields in a collection
+value are below:
+
+* `name` - The name of the output, also the index key.
+* `change` - The data describing the change that will be made to this output.
+ For more details, see [Change Representation](#change-representation).
+
+## The `raw` Collection
+
+The `raw` collection exposes the raw, unprocessed plan data.
+
+This is the same data that is produced by [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) on the plan file for the run this
+policy check is attached to.
+
+Use of this data is only recommended in expert situations where the data the
+collections present may not exactly serve the needs of the policy. For more
+information on the file format, see the [JSON Output
+Format](https://developer.hashicorp.com/terraform/internals/json-format) page.
+
+-> **NOTE:** Although designed to be relatively stable, the actual makeup for
+the JSON output format is a Terraform CLI concern and as such not managed by
+Sentinel. Use at your own risk, follow the [Terraform CLI
+project](https://github.com/hashicorp/terraform), and watch the file format
+documentation for any changes.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/features/terraform/tfstate-v1.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/features/terraform/tfstate-v1.mdx
new file mode 100644
index 0000000000..9f3487ad08
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/features/terraform/tfstate-v1.mdx
@@ -0,0 +1,552 @@
+---
+page_title: tfstate/v1 - terraform - Features
+sidebar_current: docs-features-terraform-tfstate-v1
+description: The tfstate/v1 import provides access to a Terraform state.
+layout: docs
+---
+
+# Import: tfstate/v1
+
+The `tfstate/v1` import provides access to the Terraform state.
+
+The _state_ is the data that Terraform has recorded about a workspace at a
+particular point in its lifecycle, usually after an apply. You can read more
+general information about how Terraform uses state [here][ref-tf-state].
+
+[ref-tf-state]: https://developer.hashicorp.com/terraform/language/state
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfstate/v1" {
+ config = {
+ "path": "./path/to/plan.json"
+ }
+}
+```
+
+## Namespace Overview
+
+The following is a tree view of the import namespace. For more detail on a
+particular part of the namespace, see below.
+
+-> Note that the root-level alias keys shown here (`data`, `outputs`, `path`,
+and `resources`) are shortcuts to a [module namespace](#namespace-module) scoped
+to the root module. For more details, see the section on [root namespace
+aliases](#root-namespace-aliases).
+
+```
+tfstate/v1
+├── module() (function)
+│ └── (module namespace)
+│ ├── path ([]string)
+│ ├── data
+│ │ └── TYPE.NAME[NUMBER]
+│ │ ├── attr (map of keys)
+│ │ ├── depends_on ([]string)
+│ │ ├── id (string)
+│ │ └── tainted (boolean)
+│ ├── outputs (root module only in TF 0.12 or later)
+│ │ └── NAME
+│ │ ├── sensitive (bool)
+│ │ ├── type (string)
+│ │ └── value (value)
+│ └── resources
+│ └── TYPE.NAME[NUMBER]
+│ ├── attr (map of keys)
+│ ├── depends_on ([]string)
+│ ├── id (string)
+│ └── tainted (boolean)
+│
+├── module_paths ([][]string)
+├── terraform_version (string)
+│
+├── data (root module alias)
+├── outputs (root module alias)
+├── path (root module alias)
+└── resources (root module alias)
+```
+
+## Namespace: Root
+
+The root-level namespace consists of the values and functions documented below.
+
+In addition to this, the root-level `data`, `outputs`, `path`, and `resources`
+keys alias to their corresponding namespaces or values within the [module
+namespace](#namespace-module).
+
+### Function: `module()`
+
+```
+module = func(ADDR)
+```
+
+* **Return Type:** A [module namespace](#namespace-module).
+
+The `module()` function in the [root namespace](#namespace-root) returns the
+[module namespace](#namespace-module) for a particular module address.
+
+The address must be a list and is the module address, split on the period (`.`),
+excluding the root module.
+
+Hence, a module with an address of simply `foo` (or `root.foo`) would be
+`["foo"]`, and a module within that (so address `foo.bar`) would be read as
+`["foo", "bar"]`.
+
+[`null`][ref-null] is returned if a module address is invalid, or if the module
+is not present in the state.
+
+[ref-null]: /sentinel/language/spec#null
+
+As an example, given the following module block:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+If the module contained the following content:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if the resource was present in
+the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.module(["foo"]).resources.null_resource.foo[0].attr.triggers.foo is "bar" }
+```
+
+### Value: `module_paths`
+
+* **Value Type:** List of a list of strings.
+
+The `module_paths` value within the [root namespace](#namespace-root) is a list
+of all of the modules within the Terraform state at plan-time.
+
+Modules not present in the state will not be present here, even if they are
+present in the configuration or the diff.
+
+This data is represented as a list of a list of strings, with the inner list
+being the module address, split on the period (`.`).
+
+The root module is included in this list, represented as an empty inner list, as
+long as it is present in state.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The value of `module_paths` would be:
+
+```
+[
+ [],
+ ["foo"],
+]
+```
+
+And the following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.module_paths contains ["foo"] }
+```
+
+-> Note the above example only applies if the module is present in the state.
+
+#### Iterating Through Modules
+
+Iterating through all modules to find particular resources can be useful. This
+[example][iterate-over-modules] shows how to use `module_paths` with the
+[`module()` function](#function-module-) to find all resources of a
+particular type from all modules using the `tfplan` import. By changing `tfplan`
+in this function to `tfstate`, you could make a similar function find all
+resources of a specific type in the current state.
+
+[iterate-over-modules]: https://developer.hashicorp.com/terraform/cloud-docs/policy-enforcement/sentinel#sentinel-imports
+
+### Value: `terraform_version`
+
+* **Value Type:** String.
+
+The `terraform_version` value within the [root namespace](#namespace-root)
+represents the version of Terraform in use when the state was saved. This can be
+used to enforce a specific version of Terraform in a policy check.
+
+As an example, the following policy would evaluate to `true` as long as the
+state was made with a version of Terraform in the 0.11.x series, excluding any
+pre-release versions (example: `-beta1` or `-rc1`):
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.terraform_version matches "^0\\.11\\.\\d+$" }
+```
+
+-> **NOTE:** This value is also available via the [`tfplan`](/sentinel/features/terraform/tfplan-v1)
+import, which will be more current when a policy check is run against a plan.
+It's recommended you use the value in `tfplan` until Terraform Cloud
+supports policy checks in other stages of the workspace lifecycle. See the
+[`terraform_version`][import-tfplan-terraform-version] reference within the
+`tfplan` import for more details.
+
+[import-tfplan-terraform-version]: /sentinel/features/terraform/tfplan-v1#value-terraform_version
+
+## Namespace: Module
+
+The **module namespace** can be loaded by calling
+[`module()`](#function-module-) for a particular module.
+
+It can be used to load the following child namespaces, in addition to the values
+documented below:
+
+* `data` - Loads the [resource namespace](#namespace-resources-data-sources),
+ filtered against data sources.
+* `outputs` - Loads the [output namespace](#namespace-outputs), which supply the
+ outputs present in this module's state. Note that with Terraform 0.12 or
+ later, this value is only available for the root namespace.
+* `resources` - Loads the [resource
+ namespace](#namespace-resources-data-sources), filtered against resources.
+
+### Root Namespace Aliases
+
+The root-level `data`, `outputs`, and `resources` keys both alias to their
+corresponding namespaces within the module namespace, loaded for the root
+module. They are the equivalent of running `module([]).KEY`.
+
+### Value: `path`
+
+* **Value Type:** List of strings.
+
+The `path` value within the [module namespace](#namespace-module) contains the
+path of the module that the namespace represents. This is represented as a list
+of strings.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The following policy would evaluate to `true`, _only_ if the module was present
+in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.module(["foo"]).path contains "foo" }
+```
+
+## Namespace: Resources/Data Sources
+
+The **resource namespace** is a namespace _type_ that applies to both resources
+(accessed by using the `resources` namespace key) and data sources (accessed
+using the `data` namespace key).
+
+Accessing an individual resource or data source within each respective namespace
+can be accomplished by specifying the type, name, and resource number (as if the
+resource or data source had a `count` value in it) in the syntax
+`[resources|data].TYPE.NAME[NUMBER]`. Note that NUMBER is always needed, even if
+you did not use `count` in the resource.
+
+In addition, each of these namespace levels is a map, allowing you to filter
+based on type and name.
+
+-> The (somewhat strange) notation here of `TYPE.NAME[NUMBER]` may imply that
+the inner resource index map is actually a list, but it's not - using the square
+bracket notation over the dotted notation (`TYPE.NAME.NUMBER`) is required here
+as an identifier cannot start with number.
+
+Some examples of multi-level access are below:
+
+* To fetch all `aws_instance.foo` resource instances within the root module, you
+ can specify `tfstate.resources.aws_instance.foo`. This would then be indexed
+ by resource count index (`0`, `1`, `2`, and so on). Note that as mentioned
+ above, these elements must be accessed using square-bracket map notation (so
+ `[0]`, `[1]`, `[2]`, and so on) instead of dotted notation.
+* To fetch all `aws_instance` resources within the root module, you can specify
+ `tfstate.resources.aws_instance`. This would be indexed from the names of
+ each resource (`foo`, `bar`, and so on), with each of those maps containing
+ instances indexed by resource count index as per above.
+* To fetch all resources within the root module, irrespective of type, use
+ `tfstate.resources`. This is indexed by type, as shown above with
+ `tfstate.resources.aws_instance`, with names being the next level down, and so
+ on.
+
+Further explanation of the namespace will be in the context of resources. As
+mentioned, when operating on data sources, use the same syntax, except with
+`data` in place of `resources`.
+
+### Value: `attr`
+
+* **Value Type:** A string-keyed map of values.
+
+The `attr` value within the [resource
+namespace](#namespace-resources-data-sources) is a direct mapping to the state
+of the resource.
+
+The map is a complex representation of these values with data going as far down
+as needed to represent any state values such as maps, lists, and sets.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if the resource was in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.resources.null_resource.foo[0].attr.triggers.foo is "bar" }
+```
+
+### Value: `depends_on`
+
+* **Value Type:** A list of strings.
+
+The `depends_on` value within the [resource
+namespace](#namespace-resources-data-sources) contains the dependencies for the
+resource.
+
+This is a list of full resource addresses, relative to the module (example:
+`null_resource.foo`).
+
+As an example, given the following resources:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+
+resource "null_resource" "bar" {
+ # ...
+
+ depends_on = [
+ "null_resource.foo",
+ ]
+}
+```
+
+The following policy would evaluate to `true` if the resource was in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.resources.null_resource.bar[0].depends_on contains "null_resource.foo" }
+```
+
+### Value: `id`
+
+* **Value Type:** String.
+
+The `id` value within the [resource
+namespace](#namespace-resources-data-sources) contains the id of the resource.
+
+-> **NOTE:** The example below uses a _data source_ here because the
+[`null_data_source`][ref-tf-null-data-source] data source gives a static ID,
+which makes documenting the example easier. As previously mentioned, data
+sources share the same namespace as resources, but need to be loaded with the
+`data` key. For more information, see the
+[synopsis](#namespace-resources-data-sources) for the namespace itself.
+
+[ref-tf-null-data-source]: https://registry.terraform.io/providers/hashicorp/null/latest/docs/data-sources/data_source
+
+As an example, given the following data source:
+
+```hcl
+data "null_data_source" "foo" {
+ # ...
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.data.null_data_source.foo[0].id is "static" }
+```
+
+### Value: `tainted`
+
+* **Value Type:** Boolean.
+
+The `tainted` value within the [resource
+namespace](#namespace-resources-data-sources) is `true` if the resource is
+marked as tainted in Terraform state.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`, if the resource was marked as
+tainted in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.resources.null_resource.foo[0].tainted }
+```
+
+## Namespace: Outputs
+
+The **output namespace** represents all of the outputs present within a
+[module](#namespace-module). Outputs are present in a state if they were saved
+during a previous apply, or if they were updated with known values during the
+pre-plan refresh.
+
+**With Terraform 0.11 or earlier** this can be used to fetch both the outputs
+of the root module, and the outputs of any module in the state below the root.
+This makes it possible to see outputs that have not been threaded to the root
+module.
+
+**With Terraform 0.12 or later** outputs are available in the top-level (root
+module) namespace only and not accessible within submodules.
+
+This namespace is indexed by output name.
+
+### Value: `sensitive`
+
+* **Value Type:** Boolean.
+
+The `sensitive` value within the [output namespace](#namespace-outputs) is
+`true` when the output has been [marked as sensitive][ref-tf-sensitive-outputs].
+
+[ref-tf-sensitive-outputs]: https://developer.hashicorp.com/terraform/language/values/outputs#sensitive-suppressing-values-in-cli-output
+
+As an example, given the following output:
+
+```hcl
+output "foo" {
+ sensitive = true
+ value = "bar"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.outputs.foo.sensitive }
+```
+
+### Value: `type`
+
+* **Value Type:** String.
+
+The `type` value within the [output namespace](#namespace-outputs) gives the
+output's type. This will be one of `string`, `list`, or `map`. These are
+currently the only types available for outputs in Terraform.
+
+As an example, given the following output:
+
+```hcl
+output "string" {
+ value = "foo"
+}
+
+output "list" {
+ value = [
+ "foo",
+ "bar",
+ ]
+}
+
+output "map" {
+ value = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+type_string = rule { tfstate.outputs.string.type is "string" }
+type_list = rule { tfstate.outputs.list.type is "list" }
+type_map = rule { tfstate.outputs.map.type is "map" }
+
+main = rule { type_string and type_list and type_map }
+```
+
+### Value: `value`
+
+* **Value Type:** String, list, or map.
+
+The `value` value within the [output namespace](#namespace-outputs) is the value
+of the output in question.
+
+Note that the only valid primitive output type in Terraform is currently a
+string, which means that any int, float, or boolean value will need to be
+converted before it can be used in comparison. This does not apply to primitives
+within maps and lists, which will be their original types.
+
+As an example, given the following output blocks:
+
+```hcl
+output "foo" {
+ value = "bar"
+}
+
+output "number" {
+ value = "42"
+}
+
+output "map" {
+ value = {
+ foo = "bar"
+ number = 42
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+value_foo = rule { tfstate.outputs.foo.value is "bar" }
+value_number = rule { int(tfstate.outputs.number.value) is 42 }
+value_map_string = rule { tfstate.outputs.map.value["foo"] is "bar" }
+value_map_int = rule { tfstate.outputs.map.value["number"] is 42 }
+
+main = rule { value_foo and value_number and value_map_string and value_map_int }
+```
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/features/terraform/tfstate-v2.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/features/terraform/tfstate-v2.mdx
new file mode 100644
index 0000000000..9b29aa2c51
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/features/terraform/tfstate-v2.mdx
@@ -0,0 +1,176 @@
+---
+page_title: tfstate/v2 - terraform - Features
+sidebar_current: docs-features-terraform-tfstate-v2
+description: The tfstate/v2 import provides access to a Terraform state.
+layout: docs
+---
+
+# Import: tfstate/v2
+
+The `tfstate/v2` import provides access to a Terraform state.
+
+The _state_ is the data that Terraform has recorded about a workspace at a
+particular point in its lifecycle, usually after an apply. You can read more
+general information about how Terraform uses state
+[here](https://developer.hashicorp.com/terraform/language/state).
+
+The data in the `tfstate/v2` import is sourced from the JSON configuration file
+that is generated by the [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) command. For more information on
+the file format, see the [JSON Output Format](https://developer.hashicorp.com/terraform/internals/json-format)
+page.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfstate/v2" {
+ config = {
+ "path": "./path/to/plan.json"
+ }
+}
+```
+
+## Import Overview
+
+The `tfstate/v2` import is structured as currently two _collections_, keyed in
+resource address and output name, respectively.
+
+```
+(tfstate/v2)
+├── terraform_version (string)
+├── resources
+│ └── (indexed by address)
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── index (float (number) or string)
+│ ├── provider_name (string)
+│ ├── values (map)
+│ ├── depends_on (list of strings)
+│ ├── tainted (boolean)
+│ └── deposed_key (string)
+└── outputs
+ └── (indexed by name)
+ ├── name (string)
+ ├── sensitive (boolean)
+ └── value (value)
+```
+
+The collections are:
+
+* [`resources`](#the-resources-collection) - The state of all resources across
+ all modules in the state.
+* [`outputs`](#the-outputs-collection) - The state of all outputs from the root module in the state.
+
+These collections are specifically designed to be used with the
+[`filter`](/sentinel/language/collection-operations#filter-expression)
+quantifier expression in Sentinel, so that one can collect a list of resources
+to perform policy checks on without having to write complex module traversal. As
+an example, the following code will return all `aws_instance` resource types
+within the state, regardless of what module they are in:
+
+```
+all_aws_instances = filter tfstate.resources as _, r {
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+You can add specific attributes to the filter to narrow the search, such as the
+module address. The following code would return resources in a module named
+`foo` only:
+
+```
+all_aws_instances = filter tfstate.resources as _, r {
+ r.module_address is "module.foo" and
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+## The `terraform_version` Value
+
+The top-level `terraform_version` value in this import gives the Terraform
+version that recorded the state. This can be used to do version validation.
+
+```
+import "tfstate/v2" as tfstate
+import "strings"
+
+v = strings.split(tfstate.terraform_version, ".")
+version_major = int(v[1])
+version_minor = int(v[2])
+
+main = rule {
+ version_major is 12 and version_minor >= 19
+}
+```
+
+## The `resources` Collection
+
+The `resources` collection is a collection representing all of the resources in
+the state, across all modules.
+
+This collection is indexed on the complete resource address as the key.
+
+An element in the collection has the following values:
+
+* `address` - The absolute resource address - also the key for the collection's
+ index.
+
+* `module_address` - The address portion of the absolute resource address.
+
+* `mode` - The resource mode, either `managed` (resources) or `data` (data
+ sources).
+
+* `type` - The resource type, example: `aws_instance` for `aws_instance.foo`.
+
+* `name` - The resource name, example: `foo` for `aws_instance.foo`.
+
+* `index` - The resource index. Can be either a number or a string.
+
+* `provider_name` - The name of the provider this resource belongs to. This
+ allows the provider to be interpreted unambiguously in the unusual situation
+ where a provider offers a resource type whose name does not start with its own
+ name, such as the `googlebeta` provider offering `google_compute_instance`.
+
+ -> **Note:** Starting with Terraform 0.13, the `provider_name` field contains the
+ _full_ source address to the provider in the Terraform Registry. Example:
+ `registry.terraform.io/hashicorp/null` for the null provider.
+
+* `values` - An object (map) representation of the attribute values of the
+ resource, whose structure depends on the resource type schema. When accessing
+ proposed state through the [`planned_values`](/sentinel/features/terraform/tfplan-v2#the-planned_values-collection)
+ collection of the tfplan/v2 import, unknown values will be omitted.
+
+* `depends_on` - The addresses of the resources that this resource depends on.
+
+* `tainted` - `true` if the resource has been explicitly marked as
+ [tainted](https://developer.hashicorp.com/terraform/cli/commands/taint) in the state.
+
+* `deposed_key` - Set if the resource has been marked deposed and will be
+ destroyed on the next apply. This matches the deposed field in the
+ [`resource_changes`](/sentinel/features/terraform/tfplan-v2#the-resource_changes-collection)
+ collection in the [`tfplan/v2`](/sentinel/features/terraform/tfplan-v2) import.
+
+## The `outputs` Collection
+
+The `outputs` collection is a collection of outputs from the root module of the
+state.
+
+Note that no child modules are included in this output set, and there is no way
+to fetch child module output values. This is to encourage the correct flow of
+outputs to the recommended root consumption level.
+
+The collection is indexed on the output name, with the following fields:
+
+* `name`: The name of the output, also the collection key.
+* `sensitive`: Whether or not the value was marked as
+ [sensitive](https://developer.hashicorp.com/terraform/language/values/outputs#sensitive-suppressing-values-in-cli-output)
+ in
+ configuration.
+* `value`: The value of the output.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/functions/append.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/functions/append.mdx
new file mode 100644
index 0000000000..81395154f6
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/functions/append.mdx
@@ -0,0 +1,46 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Append'
+sidebar_current: docs-funcs-append
+description: The built-in function `append` appends a value to the end of a list.
+layout: docs
+---
+
+# Builtin Function: append
+
+The built-in function `append` appends a value to the end of a list. The list
+is modified in-place. The return value will always be [undefined](/sentinel/language/undefined).
+
+`append` called on any value other than a list or undefined will result
+in an immediate error.
+
+Examples:
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+### Why does this function return undefined?
+
+This function returns `undefined` to avoid unintended side effects of the function in the context
+of a [rule](/sentinel/language/rules).
+
+For example, if `append` returned the list value as a result, the following policy would depend
+on the order in which rules are referenced:
+
+```sentinel
+list = []
+a = rule { append(list, 1) contains 1 }
+b = rule { append(list, 2) contains 2 }
+
+// Scenario 1: list becomes [1, 2]
+main = rule { a and b }
+
+// Scenario 2: list becomes [2, 1]
+main = rule { b and a }
+```
+
+This sort of side effect behavior introduces complex and difficult to track logical errors in programs.
+As a result, functions like this return `undefined` and modify values in-place.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/functions/delete.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/functions/delete.mdx
new file mode 100644
index 0000000000..3b60803997
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/functions/delete.mdx
@@ -0,0 +1,25 @@
+---
+page_title: 'Sentinel Language - Builtin Function: delete'
+sidebar_current: docs-funcs-delete
+description: The built-in function `delete` deletes an element from a map.
+layout: docs
+---
+
+# Builtin Function: delete
+
+The built-in function `delete` deletes an element from a map.
+
+If the element to delete does not exist, the map is left unchanged.
+Calling `delete` on a value that is not a map or
+[undefined](/sentinel/language/undefined)
+is an immediate error. The result of `delete` is always `undefined`.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/functions/error.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/functions/error.mdx
new file mode 100644
index 0000000000..96d253b662
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/functions/error.mdx
@@ -0,0 +1,15 @@
+---
+page_title: 'Sentinel Language - Builtin Function: error'
+sidebar_current: docs-funcs-error
+description: >-
+ The built-in function `error` is used to exit immediately with an error
+ message.
+layout: docs
+---
+
+# Builtin Function: error
+
+The built-in function `error` is used to exit immediately with an error message.
+
+Please see the [page on errors](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/functions/index.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/functions/index.mdx
new file mode 100644
index 0000000000..4e12143ad0
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/functions/index.mdx
@@ -0,0 +1,13 @@
+---
+page_title: Sentinel Language - Builtin Functions
+sidebar_current: docs-funcs
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Language: Builtin Functions
+
+Builtin functions are
+[functions](/sentinel/language/functions)
+that are available in all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in function.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/functions/keys.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/functions/keys.mdx
new file mode 100644
index 0000000000..a96cafe897
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/functions/keys.mdx
@@ -0,0 +1,22 @@
+---
+page_title: 'Sentinel Language - Builtin Function: keys'
+sidebar_current: docs-funcs-keys
+description: The built-in function `keys` returns the keys of a map.
+layout: docs
+---
+
+# Builtin Function: keys
+
+The built-in function `keys` returns the keys of a map.
+
+`keys` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned keys are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+```
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/functions/length.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/functions/length.mdx
new file mode 100644
index 0000000000..8b874b14c4
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/functions/length.mdx
@@ -0,0 +1,23 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Length'
+sidebar_current: docs-funcs-length
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Builtin Function: length
+
+The built-in function `length` returns the length of a collection or string.
+
+The length of a string is the number of bytes in the string. It is not
+necessarilly the number of characters. The length of a collection is the
+number of elements in that collection. The length of
+[undefined](/sentinel/language/undefined) is `undefined`.
+
+Examples:
+
+```sentinel
+length("foo") // 3
+length([1, 2, 3, 4]) // 4
+length({ "key": "value" }) // 1
+```
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/functions/print.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/functions/print.mdx
new file mode 100644
index 0000000000..bcf441d09d
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/functions/print.mdx
@@ -0,0 +1,13 @@
+---
+page_title: 'Sentinel Language - Builtin Function: print'
+sidebar_current: docs-funcs-print
+description: The built-in function `print` is used for logging and debugging.
+layout: docs
+---
+
+# Builtin Function: print
+
+The built-in function `print` is used for logging and debugging.
+
+Please see the [page on logging](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/functions/range.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/functions/range.mdx
new file mode 100644
index 0000000000..0f86bfc402
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/functions/range.mdx
@@ -0,0 +1,49 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Range'
+sidebar_current: docs-funcs-range
+description: The built-in function `range` returns a list of numbers in a range.
+layout: docs
+---
+
+# Builtin Function: range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it
+defaults to `1`.
+
+Negative steps are permitted and count down from `start` towards `end`, instead
+of up.
+
+The range ends when the next value given by the sum of the last value and `step`
+would exceed `end`, regardless of if it has been reached.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4] - starts at 1 instead of 0
+range(1, 5, 2) // [1,3] - 3+2 does not satisfy r[-1] < end
+range(0, -3, -1) // [0,-1,-2] - example of negative range
+```
+
+Impossible ranges, or ranges where `start` will never reach `end` given `step`,
+yield an empty list.
+
+```
+range(1, 1) // [] - already starting at 1
+range(0, 1, -1) // [] - negative step will never reach 1
+```
+
+Supplying an undefined value to any argument of `range` yields an undefined
+value back.
+
+A range with a zero step is a runtime error.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/functions/values.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/functions/values.mdx
new file mode 100644
index 0000000000..8f98d3ab56
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/functions/values.mdx
@@ -0,0 +1,22 @@
+---
+page_title: 'Sentinel Language - Builtin Function: values'
+sidebar_current: docs-funcs-values
+description: The built-in function `values` returns the values of a map.
+layout: docs
+---
+
+# Builtin Function: values
+
+The built-in function `values` returns the values of a map.
+
+`values` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned values are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+values(data) // [3, 2]
+```
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/imports/base64.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/imports/base64.mdx
new file mode 100644
index 0000000000..4e58429268
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/imports/base64.mdx
@@ -0,0 +1,46 @@
+---
+page_title: 'Import: base64'
+sidebar_current: docs-imports-base64
+description: The base64 import enables a Sentinel policy to encode and decode Base64 values.
+layout: docs
+---
+
+# Import: base64
+
+The base64 import enables a Sentinel policy to encode and decode Base64 values.
+
+### base64.encode(str)
+
+Encodes the string `str` into a Base64 encoded string, using the standard
+encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-4).
+
+```sentinel
+enc = base64.encode("foo") // "Zm9v"
+```
+
+### base64.decode(str)
+
+Decodes the Base64 encoded string `str`, returning the string result,
+using the standard encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-4).
+
+```sentinel
+dec = base64.decode("Zm9v") // "foo"
+```
+
+### base64.urlencode(str)
+
+Encodes the string `str` into a Base64 encoded string, using the alternate
+encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-5).
+
+```sentinel
+enc = base64.urlencode("foo") // "Zm9v"
+```
+
+### base64.urldecode(str)
+
+Decodes the Base64 encoded string `str`, returning the string result, using the
+alternate encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-5).
+
+```sentinel
+dec = base64.urldecode("Zm9v") // "foo"
+```
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/imports/decimal.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/imports/decimal.mdx
new file mode 100644
index 0000000000..a6633a38e0
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/imports/decimal.mdx
@@ -0,0 +1,318 @@
+---
+page_title: 'Import: decimal'
+sidebar_current: docs-imports-decimal
+description: |-
+ The decimal import provides functions for operating on numbers represented
+ as decimals.
+layout: docs
+---
+
+# Import: decimal
+
+The decimal import provides functions for operating on numbers represented
+as decimals.
+
+Binary floating-point numbers provided by the `float` type are unable to
+fully represent numbers like `1.1` and `2.2`. The decimal import can
+represent these numbers exactly, and so should be used when exactness is
+required.
+
+Decimals are created through the `new` function, which takes any type that
+can represent a number. Arguments to the decimal operators can also take
+a value of any of these types. The full list is:
+
+- float
+- int
+- string
+- existing decimal value
+
+### decimal.infinity(sign)
+
+Creates an infinite-value decimal. Infinite-value decimals are always larger
+or smaller, depending on sign, than any non-infinite decimals.
+
+```sentinel
+decimal.infinity(1) // +Infinity
+decimal.infinity(-1) // -Infinity
+```
+
+Also available as `decimal.inf`.
+
+### decimal.nan
+
+A decimal representing a "not-a-number" value. NaNs are not equal to any
+other number, even themselves.
+
+```sentinel
+decimal.new(-1).sqrt() // NaN
+decimal.new(0).mul(decimal.inf(1)) // NaN
+decimal.nan.is(decimal.nan) // false
+```
+
+### decimal.is_infinite(d)
+
+Evaluates to true if the decimal is infinite.
+
+```sentinel
+decimal.is_infinite(100) // false
+decimal.is_infinite("Infinity") // true
+```
+
+Also available as `decimal.is_inf`, `decimal.isinf` and `decimal.isinfinite`.
+
+### decimal.is_nan(d)
+
+Evaluates to true if the decimal is not-a-number.
+
+```sentinel
+decimal.is_nan("NaN") // true
+```
+
+Also available as `decimal.isnan`.
+
+### decimal.new(v)
+
+Constructs a decimal from another value.
+
+```sentinel
+decimal.new("1.1") // Constructs a decimal from a string.
+decimal.new(2.2) // Constructs a decimal from a float.
+decimal.new(3) // Constructs a decimal from an integer.
+decimal.new("1.1234E+400") // Constructs a decimal from a string using E-notation.
+```
+
+## Type: number
+
+Numbers are represented internally by a sign, coefficient, and exponent.
+The value is calculated with the formula `sign * coefficient * 10^exponent`.
+Exponent is limited to 32 bits, and the coefficient is limited by the total
+precision.
+
+The maximum precision a number can represent is 100 digits. This includes
+both before and after the decimal place.
+
+### number.string
+
+A string representation of the decimal.
+
+```sentinel
+decimal.new(1.5).string // 1.5
+```
+
+### number.sign
+
+A number between `-1` and `+1` representing the sign of the decimal.
+
+```sentinel
+decimal.new(1.5).sign // 1
+```
+
+### number.coefficient
+
+The coefficient component of the decimal.
+
+```sentinel
+decimal.new(1.5).coefficient // 15
+```
+
+### number.exponent
+
+The exponent component of the decimal.
+
+```sentinel
+decimal.new(1.5).exponent // -1
+```
+
+### number.float
+
+A floating-point representation of the decimal.
+
+```sentinel
+decimal.new(1.5).float // 1.500000
+```
+
+### number.int
+
+An integer representation of the decimal. This always rounds down to the nearest integer.
+
+```sentinel
+decimal.new(1.5).int // 1
+```
+
+### number.is(v)
+
+Test for equality with another value.
+
+```sentinel
+decimal.new(1.5).is(1) // false
+```
+
+### number.is_not(v)
+
+Test for inequality with another value.
+
+```sentinel
+decimal.new(1.5).is_not(1) // true
+```
+
+### number.less_than(v)
+
+Test that the decimal is less than another value.
+Can also be called as `number.lt(v)`
+
+```sentinel
+decimal.new(1.5).less_than(1) // false
+```
+
+### number.less_than_or_equals(v)
+
+Test that the decimal is less than or equals to another value.
+Can also be called as `number.lte(v)`
+
+```sentinel
+decimal.new(1.5).less_than_or_equals(1) // false
+```
+
+### number.greater_than(v)
+
+Test that the decimal is greater than another value.
+Can also be called as `number.gt(v)`
+
+```sentinel
+decimal.new(1.5).greater_than(1) // true
+```
+
+### number.greater_than_or_equals(v)
+
+Test that the decimal is greater than or equals to another value.
+Can also be called as `number.gte(v)`
+
+```sentinel
+decimal.new(1.5).greater_than_or_equals(1) // true
+```
+
+### number.add(v)
+
+Add another number to the decimal.
+
+```sentinel
+decimal.new(1.5).add(1) // 2.5
+```
+
+### number.subtract(v)
+
+Subtract another number from the decimal.
+Can also be called as `number.sub(v)`
+
+```sentinel
+decimal.new(1.5).subtract(1) // 0.5
+```
+
+### number.multiply(v)
+
+Multiply the decimal by a number.
+Can also be called as `number.mul(v)`
+
+```sentinel
+decimal.new(1.5).multiply(2) // 3.0
+```
+
+### number.divide(v)
+
+Divide the decimal by a number.
+Can also be called as `number.div(v)`
+
+```sentinel
+decimal.new(3.0).divide(1.5) // 2
+```
+
+### number.modulo(v)
+
+Find the remainder after dividing the decimal by a number.
+Can also be called as `mod`, `remainder`, or `rem`.
+
+```sentinel
+decimal.new(1.5).modulo(1) // 0.5
+```
+
+### number.power(v)
+
+Raise the decimal to a power.
+Can also be called as `number.pow(v)`
+
+```sentinel
+decimal.new(1.5).power(2) // 2.25
+```
+
+### number.exp()
+
+The natural exponent of the decimal. This calculates `e^number`.
+
+```sentinel
+decimal.new(1.5).exp() // 4.4816...
+```
+
+### number.loge()
+
+The natural logarithm of the decimal.
+Can also be called as `number.ln()`
+
+```sentinel
+decimal.new(1.5).loge() // 0.4054...
+```
+
+### number.log10()
+
+The base-10 logarithm of the decimal.
+Can also be called as `number.log()`
+
+```sentinel
+decimal.new(1.5).log10() // 0.1760...
+```
+
+### number.square_root()
+
+The square root of the decimal.
+Can also be called as `number.sqrt()`
+
+```sentinel
+decimal.new(1.5).square_root() // 1.2247...
+```
+
+### number.ceiling()
+
+Round the decimal to the smallest integer greater or equal to itself.
+Can also be called as `number.ceil()`
+
+```sentinel
+decimal.new(1.5).ceiling() // 2
+```
+
+### number.floor()
+
+Round the decimal to the largest integer less than or equal to itself.
+
+```sentinel
+decimal.new(1.5).floor() // 1
+```
+
+### number.absolute()
+
+The absolute value of the decimal.
+Can also be called as `number.abs()`
+
+```sentinel
+decimal.new(1.5).absolute() // 1.5
+decimal.new(-1.5).absolute() // 1.5
+```
+
+### number.negate()
+
+The negated value of the decimal. A positive decimal will become negative,
+and a negative decimal will become positive.
+Can also be called as `number.neg()`
+
+```sentinel
+decimal.new(1.5).negate() // -1.5
+decimal.new(-1.5).negate() // 1.5
+```
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/imports/http.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/imports/http.mdx
new file mode 100644
index 0000000000..f94ae98c27
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/imports/http.mdx
@@ -0,0 +1,370 @@
+---
+page_title: 'Import: http'
+sidebar_current: docs-imports-http
+description: The http import enables the use of HTTP-accessible data from outside the runtime in Sentinel policy rules.
+layout: docs
+---
+
+# Import: http
+
+The http import enables the use of HTTP-accessible data from outside
+the runtime in Sentinel policy rules.
+
+The simplest example of the import in use would be:
+
+```sentinel
+import "http"
+
+resp = http.get("https://example.hashicorp.com")
+main = rule { resp.body contains "something" }
+```
+
+By default, any request response that is not a successful "200 OK" HTTP
+response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information and
+how to customize this behavior.
+
+For more advanced requests which require setting request headers, use a
+[`request` type](#type-request):
+
+```sentinel
+import "http"
+import "json"
+
+param token
+
+req = http.request("https://example.hashicorp.com").with_header("Authorization", "token "+token)
+resp = json.unmarshal(http.get(req).body)
+main = rule { resp["some_key"] is true }
+```
+
+The `with_header` function above returns the `request` object with the
+`Authorization` HTTP header set to value of the variable `some_value`. Many
+of the methods within the http import API are designed around this concept
+of method chaining, allowing for complex building of HTTP requests
+encapsulated in functions. The following is an idiomatic example further
+demonstrating this pattern:
+
+```sentinel
+import "http"
+import "json"
+
+param github_user
+param github_token
+
+client = func(req) {
+ return http.with_timeout(5).with_retries(3)
+}
+
+github_request = func(path) {
+ full_url = "https://api.github.com" + path
+ return http.request(full_url).
+ with_basic_auth(github_user, github_token).
+ with_header("Accept", "application/vnd.github.v3+json").
+ with_header("Custom", "foo"),
+}
+
+default_response = client.get(github_request("/example"))
+
+# Override the Custom header for this single request
+custom_header_response = json.unmarshal(
+ client.get(
+ github_request("/example").with_header("Custom", "bar"),
+ ),
+)
+
+# Override the timeout value for a known slower request
+slow_response = json.unmarshal(
+ client.with_timeout(15).get(github_request("/slow-endpoint")),
+)
+
+some_key_is_true = rule { json.unmarshal(default_response.body)["some_key"] is true }
+body_contains_text = rule { custom_header_response.body contains "something" }
+response_is_ok = rule { slow_response.status_code is 200 }
+
+main = rule { some_key_is_true and body_contains_text and response_is_ok }
+```
+
+### http.client
+
+Retrieve the base HTTP client with default settings. The returned client may be
+configured by calling configuration functions on it; all of these configuration
+functions on are also aliased as top-level functions in this namespace. See
+[`client`](#type-client) below.
+
+### http.request(url)
+
+Create a new [`request`](#type-request) to the specified `url` (string). A
+`request` type enables customization of headers and other concerns before then
+providing the constructed `request` to [`get()`](#client-get-url_or_request),
+[`post()`](#client-post-url_or_request) or [`send()`](#client-send-request).
+
+```sentinel
+req = http.request("example.hashicorp.com/foo").with_header("Foo", "bar")
+resp = http.get(req)
+```
+
+### http.methodPost
+
+Returns a string containing the accepted verb for POST requests, `"POST"`.
+
+### http.methodGet
+
+Returns a string containing the accepted verb for GET requests, `"GET"`.
+
+## Type: client
+
+All of the following configuration functions on the default client are also
+aliased as top-level functions in the import. This allows a short-hand (and
+idiomatic) way of configuring a new client without having to directly access
+`http.client` each time:
+
+```sentinel
+# The following two lines are semantically the same:
+resp = http.client.with_timeout(5).get(url)
+resp = http.with_timeout(5).get(url)
+```
+
+### url_or_request
+
+The [`get()`](#client-get-url_or_request) and [`post()`](#client-post-url_or_request)
+functions accept either a URL string or a request object, noted as
+`url_or_request` throughout the documentation.
+
+When `url_or_request` is a string, a request is made with the URL
+specified by the string. To customize HTTP headers and other settings, pass
+a request. By default, a request has a timeout value of 10 seconds and 1
+retry. These settings can be adjusted with [`with_timeout()`](#clientwith_timeoutinteger) and
+[`with_retries()`](#clientwith_retriesinteger), respectively.
+
+### Redirects
+
+If the response is one of the following redirect codes, the client follows the
+redirect, up to a maximum of 10 redirects:
+
+- 301 (Moved Permanently)
+- 302 (Found)
+- 303 (See Other)
+- 307 (Temporary Redirect)
+- 308 (Permanent Redirect)
+
+If the redirect limit is exceeded, the result is a policy error.
+
+By default, after any redirects are followed (up to the maximum), any request
+response that is not a successful "200 OK" response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information
+and how to customize this behavior.
+
+### client.get(url_or_request)
+
+Issue a GET request with a URL string or a `request` type. Returns a `response`
+type.
+
+```sentinel
+import "http"
+
+resp = http.get("https://example.hashicorp.com")
+main = rule { resp.body contains "something" }
+```
+
+### client.post(url_or_request)
+
+Issue a POST request with a URL string or a `request` type. Returns a `response`
+type.
+
+```sentinel
+import "http"
+
+req = http.request("https://example.hashicorp.com")
+ .with_body(json.marshal({"foo": "bar"}))
+resp = http.post(req)
+main = rule { resp.body contains "something" }
+```
+
+### client.send(request)
+
+Make a request using the supplied `request` type. Returns a `response`.
+
+```sentinel
+import "http"
+
+req = http.request("https://example.hashicorp.com")
+resp = http.send(req)
+main = rule { resp.body contains "something" }
+```
+
+### client.accept_status_codes(list)
+
+Configures a client to not automatically cause a policy failure if
+the resulting response's status code is in `list`. Any status code received
+that is not present in this list will trigger a runtime error.
+
+By default, any request response that is not a successful "200 OK" response
+causes a policy error. This is for convenience, as the most common scenario
+by far is to receive a response and immediately signal a policy error if the
+status code is not 200. Use this scoping to specify a list of non-redirect
+HTTP codes that you wish to accept without error and evaluate the response
+yourself.
+
+Redirects are not considered final status codes in this context. That is,
+redirects are always followed up to the maximum allowed value (see [`Redirects`](#redirects))
+and the final response code in the redirect chain is validated against
+`list`.
+
+```sentinel
+resp = http.accept_status_codes([200, 404]).get(url)
+main = rule { resp.status_code is 404 }
+```
+
+### client.accepted_status_codes
+
+Returns a list of the client's accepted status codes.
+
+```sentinel
+client = http.accept_status_codes([200, 404])
+main = rule { client.accepted_status_codes contains 404 }
+```
+
+### client.with_retries(integer)
+
+Sets the maximum number of retries, specified by `integer`, that should be
+attempted on an unsuccessful request. An unsuccessful request is considered
+to be any 5xx response except `501 (Not Implemented)` or a request timeout.
+By default, one retry is attempted.
+
+The maximum number of retries is 10, for a total of 11 request attempts.
+When a request exceeds the maximum number of retries without a response, the
+result is a policy error. Specifying a number larger than this will result
+in a runtime error.
+
+Between each retry, an exponentially increasing delay is observed, allowing
+time for the server to recover. This delay starts at 1 second for the first
+retry and increases each attempt thereafter. The maximum delay for a single
+attempt is 30 seconds.
+
+Retries can be disabled by specifying 0.
+
+### client.retries
+
+Returns the client's configured number of retries as an integer.
+
+### client.with_timeout(integer)
+
+Sets the request timeout to the number of seconds indicated by `integer`.
+
+Each individual request performed by the HTTP client will be limited by the
+given request timeout. This timeout includes connection time, any redirects,
+and reading the response body.
+
+A maximum of 30 seconds is allowed. Specifying a number larger than this
+will result in a runtime error.
+
+By default, requests will time out after 10 seconds.
+
+### client.timeout
+
+Returns the client's configured timeout in whole seconds as an integer.
+
+### client.without_certificate_verification()
+
+Skips verification of TLS certificates during secure HTTP operations,
+allowing for requests to `https://` endpoints which are secured with a TLS
+certificate signed by an untrusted certificate authority. Takes no arguments.
+
+By default, the HTTP client will trigger a runtime error if a TLS certificate
+presented by a server is from an untrusted certificate authority.
+
+Do not change this setting unless you are aware of the risks involved.
+Without verification, TLS accepts any certificate presented by the server
+and any host name in that certificate. In this mode, TLS is susceptible to
+man-in-the-middle attacks. This option should only be used for testing or in
+trusted networks with self-signed certificates.
+
+```sentinel
+http.without_certificate_verification().get(url)
+```
+
+### client.certificate_verification
+
+Returns true if the client requires TLS certificates to be verifiable.
+
+## Type: request
+
+### request.url
+
+Returns the request URL as a string.
+
+### request.headers
+
+Returns the configured request headers as a string-keyed map of strings.
+
+### request.body
+
+Returns the configured body as a string.
+
+### request.with_header(key, value)
+
+Returns a `request` with the header `key` (string) set to `value` (string).
+
+Values are overridden on subsequent calls to the same `key`. To specify
+multiple values, use the existing value when setting the new one.
+
+```sentinel
+original = http.request("http://example.hashicorp.com").with_header("Foo", "bar")
+overridden = original.with_header("Foo", "baz")
+multi_value = original.with_header("Foo", original.headers["Foo"] + ",baz")
+
+main = rule {
+ original.headers["Foo"] is "bar" and
+ overridden.headers["Foo"] is "baz" and
+ multi_value.headers["Foo"] is "bar,baz"
+}
+```
+
+### request.with_basic_auth(username, password)
+
+Returns a `request` with the `Authorization` header set to use HTTP Basic
+Authentication with the provided username and password.
+
+Note that with HTTP Basic Authentication the provided username and password
+are encoded, but not encrypted.
+
+### request.with_body(body)
+
+Returns a `request` with the `body` set. This value will then be sent as a body
+as part of the request. Commonly used along with the [`post()`](#client-post-url_or_request) function.
+
+```sentinel
+req = http.request("http://example.hashicorp.com")
+ .with_header("Content-Type", "application/json")
+ .with_body(json.marshal({ "foo": "bar" }))
+
+resp = http.post(req)
+```
+
+## Type: response
+
+### response.status_code
+
+The response status code as an integer, e.g. `200`.
+
+### response.headers
+
+The response headers as a map.
+
+### response.body
+
+The response body as a string. Callers should unmarshal the content to a useful
+representation as necessary based on the content type. For example, if the
+response is in JSON:
+
+```sentinel
+import "http"
+import "json"
+
+# This example endpoint returns {"some_key": true}
+req = http.request("https://example.hashicorp.com/some-json")
+
+resp = json.unmarshal(http.get(req).body)
+main = rule { keys(resp) contains "some_key" and resp["some_key"] is true }
+```
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/imports/index.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/imports/index.mdx
new file mode 100644
index 0000000000..e45bf81b81
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/imports/index.mdx
@@ -0,0 +1,18 @@
+---
+page_title: Sentinel Language - Standard Imports
+sidebar_current: docs-imports
+description: >-
+ Standard Imports are the built-in imports that are available to all Sentinel
+ policies.
+layout: docs
+---
+
+# Standard Imports
+
+Standard Imports are the built-in [imports](/sentinel/language/imports)
+that are available to all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in import.
+
+Sentinel-embedded applications can choose to allow or deny certain
+standard imports. Please reference the documentation for the Sentinel-enabled
+application you're using to determine if all standard imports are available.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/imports/json.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/imports/json.mdx
new file mode 100644
index 0000000000..3355458f14
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/imports/json.mdx
@@ -0,0 +1,31 @@
+---
+page_title: 'Import: json'
+sidebar_current: docs-imports-json
+description: The json import enables a Sentinel policy to parse and access a JSON document.
+layout: docs
+---
+
+# Import: json
+
+The json import enables a Sentinel policy to parse and access a JSON
+document.
+
+### json.unmarshal(obj)
+
+Unmarshals the JSON object `obj` into a native Sentinel structure.
+
+All native JSON types can be represented perfectly as Sentinel native types.
+
+```sentinel
+// Typically the input for this would come from an external source.
+config = json.unmarshal("{ \"foo\": 42 }")
+config.foo // 42
+config.bar // undefined (as usual for accessing a non-existent map key)
+```
+
+### json.marshal(obj)
+
+Marshals the Sentinel object `obj` into a JSON object encoded as a string.
+
+Functions and `undefined` cannot be natively encoded as JSON. If values of
+either type are found in the structure, this will return undefined.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/imports/runtime.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/imports/runtime.mdx
new file mode 100644
index 0000000000..6493bbbb22
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/imports/runtime.mdx
@@ -0,0 +1,16 @@
+---
+page_title: 'Import: runtime'
+sidebar_current: docs-imports-runtime
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: runtime
+
+The runtime import contains various information about the Sentinel runtime. It
+can be used to get information about the runtime as it's embedded in the CLI or
+a specific integration, such as validating a specific runtime version.
+
+### runtime.version
+
+Returns the version of Sentinel within this implementation.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/imports/sockaddr.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/imports/sockaddr.mdx
new file mode 100644
index 0000000000..a6842ba5f0
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/imports/sockaddr.mdx
@@ -0,0 +1,41 @@
+---
+page_title: 'Import: sockaddr'
+sidebar_current: docs-imports-sockaddr
+description: The sockaddr import makes working with IP addresses easy.
+layout: docs
+---
+
+# Import: sockaddr
+
+The sockaddr import makes working with IP addresses easy.
+
+### sockaddr.is_contained(outer, inner)
+
+The is_contained function returns true if `inner` is an address that
+is contained within `outer`.
+
+```sentinel
+sockaddr.is_contained("192.168.0.0/24", "192.168.0.32") // true
+sockaddr.is_contained("192.168.0.0/24", "192.174.1.1") // false
+```
+
+### sockaddr.is_equal(addr1, addr2)
+
+The is_equal function returns true if addr1 is equal to addr2.
+
+```sentinel
+sockaddr.is_equal("192.168.12.24", "192.168.12.24") // true
+```
+
+### sockaddr.is_ipv4(addr)
+
+The is_ipv4 function returns true if addr is an IPv4 address.
+
+```sentinel
+sockaddr.is_ipv4("192.168.12.24") // true
+sockaddr.is_ipv4("2001:0db8:85a3:0000:0000:8a2e:0370:7334") // false
+```
+
+### sockaddr.is_ipv6(addr)
+
+The is_ipv6 function returns true if addr is an IPv6 address.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/imports/strings.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/imports/strings.mdx
new file mode 100644
index 0000000000..823c5d8f07
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/imports/strings.mdx
@@ -0,0 +1,91 @@
+---
+page_title: 'Import: strings'
+sidebar_current: docs-imports-strings
+description: The strings import exposes common string operations.
+layout: docs
+---
+
+# Import: strings
+
+The strings import exposes common string operations.
+
+### strings.has_prefix(s, prefix)
+
+Returns true if `s` starts with `prefix`.
+
+```sentinel
+strings.has_prefix("billing-id", "billing-") // true
+strings.has_prefix("bill-id", "billing-") // false
+```
+
+### strings.has_suffix(s, suffix)
+
+Returns true if `s` ends with `suffix`.
+
+```sentinel
+strings.has_suffix("billing-id", "id") // true
+strings.has_suffix("billing-name", "id") // false
+```
+
+### strings.join(a, sep)
+
+Joins a list with the string supplied by `sep`.
+
+Multi-dimensional lists are supported, and non-string primitive
+types will be converted to their string equivalents. Maps and
+other complex non-list types are not supported.
+
+```sentinel
+strings.join(["foo", "bar", "baz"], ".") // "foo.bar.baz"
+strings.join([["foo", "bar"], "baz"], ".") // "foo.bar.baz"
+strings.join(["a", 1, 1.01, true], ",") // "a,1,1.01,true"
+```
+
+### strings.trim_prefix(s, prefix)
+
+Trim the prefix from the string `s`. If the string doesn't have the
+prefix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_prefix("billing-id", "billing-") // "id"
+strings.trim_prefix("bill-id", "billing-") // "bill-id"
+```
+
+### strings.trim_suffix(s, suffix)
+
+Trim the suffix from the string `s`. If the string doesn't have the
+suffix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_suffix("billing-id", "-id") // "billing"
+strings.trim_suffix("bill-id", "-foo") // "bill-id"
+```
+
+### strings.to_lower(s)
+
+Lowercase the string s.
+
+```sentinel
+strings.to_lower("FoO") // "foo"
+```
+
+### strings.to_upper(s)
+
+Uppercase the string s.
+
+```sentinel
+strings.to_upper("FoO") // "FOO"
+```
+
+### strings.split(s, sep)
+
+Split the string 's' via a substring 'sep'. If 'sep' is not contained in
+'s', the return value will be a single-element list containing only 's'.
+As a special-case, if the input is already a list, it will be returned
+as-is. This allows calling the split function when not knowing if the input
+is already a list or not.
+
+```sentinel
+strings.split("foo,bar,baz", ",") // ["foo", "bar", "baz"]
+strings.split(["foo", "bar", "baz"], ",") // ["foo", "bar", "baz"]
+```
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/imports/time.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/imports/time.mdx
new file mode 100644
index 0000000000..3a675e2bbd
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/imports/time.mdx
@@ -0,0 +1,257 @@
+---
+page_title: 'Import: time'
+sidebar_current: docs-imports-time
+description: The time import provides access to the execution time and time functions.
+layout: docs
+---
+
+# Import: time
+
+The time import provides access to the execution time and time functions.
+
+During the execution of a policy the time is fixed to the moment of
+execution. This gives the illusion that a policy instantly executes at a
+single moment of time.
+
+The import uses Coordinated Universal Time (UTC).
+
+As an example of this, if a policy accessed `time.now.second` multiple times,
+for each access the same value would be returned even if they are several seconds apart.
+This is the execution `timespace`, which is mapped to `time.now`.
+
+It is also possible to run functions on a custom time by using the
+`time.load()` function to create a new timespace from the specified value.
+See the documentation for available actions on timespaces.
+
+Times specified as the reference time or via the `time.load()` function can
+be specified in a `timeish` fashion. This is either one of:
+
+- The number of seconds since the Unix epoch (Thursday, 1 January 1970,
+ 00:00:00 UTC),
+- A timestamp matching the format outlined in RFC3339, either in the
+ format `2006-01-02T15:04:05+07:00`, which includes a timezone offset, or
+ `2006-01-02T15:04:05Z`, which indicates UTC.
+
+### time.now
+
+Create a `timespace` locked either to execution time or, if set, the
+reference time. In a given execution, this will always produce the same
+value.
+
+```sentinel
+time.now // {"day": 17, "hour": 23, "minute": 19, "month": 11 ... }
+```
+
+### time.load(timeish)
+
+Create a timespace using the given value. Please see the import
+documentation for valid values for "timeish".
+
+```sentinel
+time.load("2019-11-16T01:20:30Z") // {"day": 16, "hour": 1, "minute": 20, "month": 11 ... }
+```
+
+### time.nanosecond
+
+A nanosecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.nanosecond * 1) // 2019-11-16T01:20:30.000000001Z
+```
+
+### time.microsecond
+
+A microsecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.microsecond * 1) // 2019-11-16T01:20:30.000001Z
+```
+
+### time.millisecond
+
+A millisecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.millisecond * 1) // 2019-11-16T01:20:30.001Z
+```
+
+### time.second
+
+A second duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.second * 1) // 2019-11-16T01:20:31Z
+```
+
+### time.minute
+
+A minute for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.minute * 1) // 2019-11-16T01:21:30Z
+```
+
+### time.hour
+
+An hour duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+## Type: timespace
+
+### timespace.hour
+
+The hour from 0 to 23.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").hour // 1
+```
+
+### timespace.minute
+
+The minute from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").minute // 20
+```
+
+### timespace.second
+
+The second from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").second // 30
+```
+
+### timespace.day
+
+The day of the month, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").day // 16
+```
+
+### timespace.month
+
+The month of the year as an integer, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month // 11
+```
+
+### timespace.month_name
+
+The name of the month of the year, in English. Example: `January`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month_name // November
+```
+
+### timespace.weekday
+
+The weekday as an integer, where 0 is Sunday and 6 is Saturday.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday // 6
+```
+
+### timespace.weekday_name
+
+The name of the day of the week, in English. Example: `Sunday`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday_name // Saturday
+```
+
+### timespace.year
+
+The year as an integer.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").year // 2019
+```
+
+### timespace.unix
+
+The number of seconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix // 1573867230
+```
+
+### timespace.unix_nano
+
+The number of nanoseconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix_nano // 1573867230000000000
+```
+
+### timespace.zone
+
+The timezone offset, in seconds. This can be a negative number to indicate
+time west of UTC.
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone // -28800
+```
+
+### timespace.zone_string
+
+The timezone offset, in the format `+HH:MM` (example: `-08:00` for PST).
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone_string // -08:00
+```
+
+### timespace.before(timeish_or_timespace)
+
+Check if the time in the timespace is before the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").before("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.after(timeish_or_timespace)
+
+Check if the time in the timespace is after the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").after("2019-11-15T01:20:30Z") // true
+```
+
+### timespace.equal(timeish_or_timespace)
+
+Check if two times are equivalent
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").equal("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.add(duration)
+
+Add a duration in nanoseconds to the time in the timespace and return a new timespace. The
+duration can be negative. The duration should be specified as an integer
+multiplied with the correct unit.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+### timespace.sub(timeish_or_timespace)
+
+Subtract a time from the time in the timespace and return a duration in nanoseconds.
+
+```sentinel
+// Gives a duration of 3 hours.
+duration = time.load(
+ "2019-11-16T01:20:30Z",
+).sub("2019-11-16T04:20:30Z")
+
+// Returns 2019-11-16T01:20:30Z
+time.load(
+ "2019-11-16T04:20:30Z",
+).add(duration)
+```
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/imports/types.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/imports/types.mdx
new file mode 100644
index 0000000000..fcfdbd2ae5
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/imports/types.mdx
@@ -0,0 +1,35 @@
+---
+page_title: 'Import: types'
+sidebar_current: docs-imports-types
+description: The types import enables a Sentinel policy to parse an object's type.
+layout: docs
+---
+
+# Import: types
+
+The types import enables a Sentinel policy to parse an object's type.
+
+### types.type_of(obj)
+
+Returns the object's type as a string
+
+```sentinel playground
+import "types"
+
+// Typically the inputs would come from an external source.
+is_boolean = rule { types.type_of(true) is "bool" }
+is_string = rule { types.type_of("Hello!") is "string" }
+is_integer = rule { types.type_of(42) is "int" }
+is_float = rule { types.type_of(42.123) is "float" }
+is_null = rule { types.type_of(null) is "null" }
+is_undefined = rule { types.type_of(undefined) is "undefined" }
+
+main = rule {
+ is_boolean and
+ is_string and
+ is_integer and
+ is_float and
+ is_null and
+ is_undefined
+}
+```
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/imports/units.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/imports/units.mdx
new file mode 100644
index 0000000000..e2ad3442f9
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/imports/units.mdx
@@ -0,0 +1,39 @@
+---
+page_title: 'Import: units'
+sidebar_current: docs-imports-units
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: units
+
+The units import provides access to quick calculations for various byte
+units.
+
+Note that this import uses base-2 terminology:
+
+- One kilobyte is 1024 bytes
+- One megabyte is 1024^2 = 1048576 bytes
+- One gigabyte is 1024^3 = 1073741824 bytes
+- One terabyte is 1024^4 = 1099511627776 bytes
+- One petabyte is 1024^5 = 1125899906842624 bytes
+
+### units.kilobyte
+
+The base-2 representation of a kilobyte (1024 bytes).
+
+### units.megabyte
+
+The base-2 representation of a megabyte (1048576 bytes).
+
+### units.gigabyte
+
+The base-2 representation of a gigabyte (1073741824 bytes).
+
+### units.terabyte
+
+The base-2 representation of a terabyte (1099511627776 bytes).
+
+### units.petabyte
+
+The base-2 representation of a petabyte (1125899906842624 bytes).
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/imports/version.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/imports/version.mdx
new file mode 100644
index 0000000000..59366c1087
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/imports/version.mdx
@@ -0,0 +1,151 @@
+---
+page_title: 'Import: version'
+sidebar_current: docs-imports-version
+description: |-
+ The version import provides functions for parsing versions and version constraints,
+ and verifying versions against a set of constraints.
+layout: docs
+---
+
+# Import: version
+
+The version import provides functions for parsing versions and version constraints,
+and verifying versions against a set of constraints.
+
+Versions are created through the `new` function, which accepts a string type in dotted-tri format (i.e. 1.0.0-alpha.1+001) that can represent a version.
+
+### version.new(v)
+
+Constructs a version from string value.
+
+```sentinel
+version.new("1.0.0-alpha.1+001")
+```
+
+### version.major
+
+An integer representation of the Major number for a given version .
+
+```sentinel
+version.new("1.0.0-alpha.1+001").major // 1
+```
+
+### version.minor
+
+An integer representation of the Minor number for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").minor // 0
+```
+
+### version.patch
+
+An integer representation of the Patch number for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").patch // 0
+```
+
+### version.prerelease
+
+A string representation of the Prerelease for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").prerelease // "alpha.1"
+```
+
+### version.metadata
+
+A string representation of the Metadata for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").metadata // "001"
+```
+
+### version.version
+
+A string representation of the version including pre-release and metadata information.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").version // "1.0.0-alpha.1+001"
+```
+
+### version.greater_than(v)
+
+Tests that the version is greater than a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").greater_than("0.12.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").gt("1.0.1-alpha.1+001") // False
+```
+
+### version.greater_than_or_equals(v)
+
+Tests that the version is greater than or equal to a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").greater_than_or_equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").gte("1.0.1-alpha.1+001") // False
+```
+
+### version.less_than(v)
+
+Tests that the version is less than a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").less_than("1.0.1-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").lt("0.12.0-alpha.1+001") // False
+```
+
+### version.less_than_or_equals(v)
+
+Tests that the version is less than or equal to a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").less_than_or_equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").lte("0.12.0-alpha.1+001") // False
+```
+
+### version.less_than_or_equals(v)
+
+Tests that the version equals a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").eq("0.12.0-alpha.1+001") // False
+```
+
+### version.compare(v)
+
+Compares one version with a given version. Compare returns -1, 0, or 1 if the version is less, equal, or greater than the other version, respectively.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").compare("0.12.0-alpha.1+001") // -1
+version.new("1.0.0-alpha.1+001").compare("1.0.0-alpha.1+001") // 0
+version.new("0.12.0-alpha.1+001").cmp("1.0.0-alpha.1+001") // 1
+```
+
+### version.satisfies(v, x)
+
+Tests that a version adheres to one or more version constraints. Satisfies supports the following restriction operators:
+
+- =
+- !=
+- \>
+- <
+- \>=
+- <=
+- ~>
+
+Satisfies supports the following usage scenarios:
+
+- A constraint with a pre-release can only match a pre-release version that contains the same major, minor and patch identifiers.
+- A constraint without a pre-release can only match a version without a pre-release.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").satisfies("> 1.0.0-alpha.1+001") // false
+version.new("1.0.0-alpha.1+001").satisfies(">= 1.0.0-alpha.1+001") // true
+version.new("1.0.0-alpha.1+001").satisfies(">= 1.0.0-alpha.1+001, < 1.0.0-beta+001") // true
+version.new("1.0.0").sat("= 1.0.0") // true
+version.new("0.12.7").sat("~> 0.12.0") // true
+```
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/index.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/index.mdx
new file mode 100644
index 0000000000..4497b40b83
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/index.mdx
@@ -0,0 +1,31 @@
+---
+layout: 'docs'
+page_title: 'Documentation'
+sidebar_current: 'docs-home'
+description: |-
+ Sentinel is a language and framework for policy built to be embedded in existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+[introduction](/sentinel/intro) to understand what Sentinel is, how it
+compares to other software, and more.
+
+
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/language/arithmetic.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/language/arithmetic.mdx
new file mode 100644
index 0000000000..06b1256a1b
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/language/arithmetic.mdx
@@ -0,0 +1,67 @@
+---
+page_title: Sentinel Language - Arithmetic
+sidebar_current: docs-language-arith
+description: >-
+ Sentinel supports arithmetic operators for integers and floats. Sentinel
+ supports sum, difference, product, quotient, and remainder.
+layout: docs
+---
+
+# Language: Arithmetic
+
+Sentinel supports arithmetic operators for integers and floats. Sentinel
+supports sum, difference, product, quotient, and remainder.
+
+```plaintext
++ sum
+* difference
+* product
+/ quotient
+% remainder
+```
+
+These operators work in a typical infix style:
+
+```sentinel
+4 + 8 // 12
+8 * 2 // 16
+8 / 4 // 2
+8 / 5 // 1
+8 % 5 // 3
+```
+
+## Order of Operations
+
+Arithmetic follows a standard mathematical order of operations.
+Grouping with parentheses can be used to affect ordering.
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+(4 + 5) * 2 // 18
+```
+
+A full table of operator precendence can be found on the
+[boolean expressions page](/sentinel/language/boolexpr). This
+shows how arithmetic operators relate to other operators.
+
+## Integer Division
+
+Integer division with a remainder rounds down to the nearest integer.
+Example: `8 / 3 is 2`.
+
+If the divisor is zero, an error occurs.
+
+## Mixed Numeric Operations
+
+Mixed numeric operations between integer and floating-point values are
+permitted. The result is a floating-point operation with the integer converted
+to a floating-point value for purposes of calculation.
+
+```sentinel
+import "types"
+
+a = 1.1 + 1 // 2.1
+types.type_of(a) // "float"
+```
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/language/boolexpr.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/language/boolexpr.mdx
new file mode 100644
index 0000000000..f1c518c3bf
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/language/boolexpr.mdx
@@ -0,0 +1,225 @@
+---
+page_title: Sentinel Language - Boolean Expressions
+sidebar_current: docs-language-boolexpr
+description: >-
+ Boolean expressions are expressions that evaluate to a boolean value from the
+ result of a combination of one or more comparisons and logical operators.
+layout: docs
+---
+
+# Language: Boolean Expressions
+
+Boolean expressions are expressions that evaluate to a boolean value
+from the result of a combination of one or more comparisons and logical
+operators. Boolean expressions form the basis of policy since policy can be broken
+down to a set of logical decisions that turn into true or false.
+
+A single boolean expression is the body of a [rule](/sentinel/language/rules).
+Additionally, boolean expressions are used with [conditionals](/sentinel/language/conditionals),
+and more.
+
+## Order of Operations
+
+The precedence of operators is shown below. Operators with higher
+precedence values (larger numbers) are evaluated first. Operators with
+the same precedence value are evaluated left-to-right.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Examples of this precedence are shown below:
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+```
+
+## Logical Operators
+
+Logical operators are used to change or combine logical expressions.
+There are three binary operators `and`, `or`, and `xor`. There is
+a single unary operator `not` (which can also be specified as `!`).
+
+The binary operators require two boolean operands and the unary
+operator requires a single boolean operand. In both case, the operands
+are values or expressions that are boolean types.
+
+Below is a basic explanation of the logical operators directly from
+the specification:
+
+```sentinel
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+Using parentheses to group boolean expressions, you can combine
+boolean expressions to become more complex or affect ordering:
+
+```sentinel
+(p and q) or r
+p and (q or r)
+```
+
+## Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+For any comparison, the two operands must be equivalent types. The one
+exception are integers and floats. When an integer is compared with
+a float, the integer is promoted to a floating point value.
+
+The available comparison operators are:
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+The behavior of `is` with `==` and `is not` with `!=` is identical.
+
+Example comparisons:
+
+```sentinel
+name is "Mitchell"
+idnumber > 42
+idnumber <= 12
+```
+
+Using the logical operators, these can be combined:
+
+```sentinel
+name is "Mitchell" and idnumber > 42
+```
+
+The language specification provides more detail on exactly
+[how comparison behaves](/sentinel/language/spec#comparison-operators).
+
+## Set Operators
+
+The set operators `contains` and `in` test for inclusion in a collection
+(a list or map).
+
+Set operators may 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.
+
+`contains` tests if the left-hand collection contains the right-hand value.
+`in` tests if the right-hand collection contains the left-hand value. For
+maps, "contains" looks at the keys, not the values.
+
+Examples:
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+```
+
+## Matches Operator
+
+The `matches` operator tests if a string matches a regular expression.
+The `matches` operator can be negated by prefixing the operator with
+`not`, such as `not matches`.
+
+The left-hand value is the string to test. The right-hand value is
+a string value representing a regular expression. The syntax of the regular
+expression is the same general syntax used by Python, Ruby, and other languages.
+The precise syntax accepted is the syntax accepted by RE2.
+
+Regular expressions are not anchored by default; any anchoring must be
+explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+## Any, All Expressions
+
+`any` and `all` expressions allow testing that any or all elements of a
+collection match some boolean expression. The result of an `any` and `all`
+expression is a boolean.
+
+`any` returns the boolean value `true` if any value in the collection expression
+results in the body expression evaluating to `true`. If the body expression
+evalutes to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression
+result in the body expression evaluating to `true`. If any value in the
+collection expression result in the body expression evaluating to `false`,
+the `all` expression returns `false`.
+
+For empty collections, `any` returns `false` and `all` returns `true`.
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" }
+any group.tasks as t { t.driver is "vmware" }
+```
+
+Since `any` and `all` expressions are themselves boolean expressions, they
+can be combined with other operators:
+
+```sentinel
+any ["a", "b"] as char { char is "a" } or
+other_value is "another"
+```
+
+## Emptiness Comparison
+
+The expressions `is empty` and `is not empty` provide a convenience
+method for determining the emptiness of a value. It supports the same types
+as the [built-in length](/sentinel/functions/length), collections and strings.
+
+```sentinel
+[] is empty // true
+[] is not empty // false
+["foo"] is empty // false
+["foo"] is not empty // true
+undefined is empty // undefined
+undefined is not empty // undefined
+```
+
+## Defined Comparison
+
+The expressions `is defined` and `is not defined` provide a convenience
+method for determining if a value has been defined. In other words, any value
+other than `undefined` can be considered as `defined`.
+
+```sentinel
+[] is defined // true
+4 is defined // true
+true is defined // true
+{} is defined // true
+undefined is defined // false
+[] is not defined // false
+4 is not defined // false
+true is not defined // false
+undefined is not defined // true
+```
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/language/collection-operations.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/language/collection-operations.mdx
new file mode 100644
index 0000000000..41da8f3584
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/language/collection-operations.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Sentinel Language - Collection Operations
+sidebar_current: docs-language-collection-operations
+description: |-
+ Operations for interacting with collections (list, map).
+layout: docs
+---
+
+# Language: Collection Operations
+
+Collection operations are expressions that are performed on a list or map to
+return a variation of the initial data.
+
+At the moment, `filter` is the only collection operation available.
+
+## Filter Expression
+
+`filter` is a [quantifier
+expression](/sentinel/language/spec#quantifier-expressions-any-all-filter-map) that
+returns a subset of the provided collection. Only elements whose filter body
+returns true will be returned. If any of the elements filter body returns
+undefined, the final result will be undefined.
+
+Filter uses the same syntax as the `any` and `all` [boolean
+expressions](/sentinel/language/boolexpr#any-all-expressions):
+
+```sentinel
+filter list as value { condition } // Single-iterator, list
+filter list as idx, value { condition } // Double-iterator, list
+
+filter map as key { condition } // Single-iterator, map
+filter map as key, value { condition } // Double-iterator, map
+```
+
+Examples:
+
+```sentinel
+l = [1, 1, 2, 3, 5, 8]
+evens = filter l as v { v % 2 is 0 } // [2, 8]
+
+m = { "a": "foo", "b": "bar" }
+matched_foo = filter m as _, v { v is "foo" } // { "a": "foo" }
+```
+
+## Map Expression
+
+`map` is a [quantifier
+expression](/sentinel/language/spec#quantifier-expressions-any-all-filter-map) that
+returns a list, regardless of the input collection type. Each element within the
+input collection is evaluted according to the map expression body and appended
+to the result.
+
+```sentinel
+l = [1, 2]
+r = map l as v { v % 2 } // [false, true]
+
+m = { "a": "foo", "b": "bar" }
+r = map m as k, v { v } // ["foo", "bar"]
+```
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/language/conditionals.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/language/conditionals.mdx
new file mode 100644
index 0000000000..1736795dbb
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/language/conditionals.mdx
@@ -0,0 +1,161 @@
+---
+page_title: Sentinel Language - Conditionals
+sidebar_current: docs-language-conditional
+description: |-
+ Conditional statements allow your policy to behave differently depending on a condition.
+layout: docs
+---
+
+# Language: Conditionals
+
+Conditional statements allow your policy to behave differently depending
+on a condition.
+
+Conditional statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## If Statements
+
+`if` statements only execute their bodies if a condition is met.
+The syntax of an `if` statement is:
+
+```sentinel
+if condition {
+ // ... this is executed if condition is true
+}
+```
+
+The `condition` must result in a boolean, such as by calling a function
+or evaluating a [boolean expression](/sentinel/language/boolexpr). If
+the `condition` is `true`, the body (within the `{}`) is executed. Otherwise,
+the body is skipped.
+
+Examples:
+
+```sentinel
+// This would execute the body
+value = 12
+if value is 18 {
+ print("condition met")
+}
+
+// Direct boolean values can be used
+value = true
+if value {
+ print("condition met")
+}
+
+// This would not execute the body since the boolean expression will
+// result in undefined.
+value = {}
+if value["key"] > 12 {
+ print("condition met")
+}
+```
+
+### Else, Else If
+
+An `else` clause can be given to an `if` statement to execute a body
+in the case the condition is _not met_. By putting another `if` statement
+directly after the `else`, multiple conditions can be tested for.
+The syntax is:
+
+```sentinel
+if condition {
+ // ...
+} else {
+ // ...
+}
+
+if condition {
+ // ...
+} else if other_condition {
+ // ...
+} else {
+ // ...
+}
+```
+
+### Scoping
+
+The body of an `if` statement does not create a new
+[scope](/sentinel/language/scope). Any variables assigned within the body
+of an if statement will modify the scope that the `if` statement itself
+is in.
+
+Example:
+
+```sentinel
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+```sentinel
+a = 18
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+## Case Statements
+
+`case` statements are a selection control mechanism that execute a clause based
+on matching expressions. It is worth noting that the expression for `case` is
+optional. When no expression is provided, it defaults the expression to `true`.
+Additionally, the order of clauses is important, as they are evaluated from top
+to bottom, executing the first match.
+The syntax of a case statement is:
+
+```sentinel
+case expression {
+ when clause_expression:
+ // executed when clause_expression and expression are equal
+ else:
+ // executed if no clause matches expression
+}
+```
+
+### When Clause
+
+Any clause that has an expression for comparison must use the `when` keyword.
+It accepts a list of expressions, seperated by a `,`.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+}
+```
+
+```sentinel
+case {
+ when x > 40:
+ return true
+}
+```
+
+### Else Clause
+
+The `else` keyword allows for capturing any expressions that have no matching
+`when` clause.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+ else:
+ return false
+}
+```
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/language/functions.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/language/functions.mdx
new file mode 100644
index 0000000000..8214078b6b
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/language/functions.mdx
@@ -0,0 +1,229 @@
+---
+page_title: Sentinel Language - Functions
+sidebar_current: docs-language-functions
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Functions
+
+Functions allow you to create reusable code to perform computations.
+
+Below is a trivial example function that doubles a number and shows
+the main rule using the function:
+
+```sentinel playground
+double = func(x) {
+ return x * 2
+}
+
+main = rule { double(12) is 24 }
+```
+
+Functions may contain any expressions,
+[conditionals](/sentinel/language/conditionals),
+and [loops](/sentinel/language/loops). They must return a value
+at the end of their execution. If no reasonable return value exists,
+the function should return [undefined](/sentinel/language/undefined).
+
+## Function Types
+
+### Named Functions
+
+-> **NOTE:** Named functions must be created within the [package scope](/sentinel/language/scope#package-scope).
+
+Named functions are declared using the `func` keyword as its own statement.
+They provide a safe method of creating functions and have additional
+restrictions that do not apply to anonymous functions.
+
+Firstly, named functions cannot be re-assigned, and also cannot use a name
+that is already used elsewhere. For instance, the below example will error due
+to the attempt to reassign the named function identifier to a new value:
+
+```sentinel
+func sum(a, b) {
+ return a + b
+}
+
+sum = 4
+```
+
+Additionally, the following will error due to the named function attempting
+to make use of an already assigned identifier:
+
+```sentinel
+sum = 4
+
+func sum(a, b) {
+ return a + b
+}
+```
+
+Named functions are helpful for policy authors to declare critical functions
+whose value or implementation should not be changed.
+
+### Anonymous Functions
+
+An anonymous function is created by assigning a variable to a `func`. The
+variable can be reassigned at any time including to different value types.
+Anonymous functions are helpful for use cases like closures, where a function
+can return another function.
+
+```sentinel
+func makeAdder(a) {
+ return func(b) {
+ return a + b
+ }
+}
+```
+
+## Creating a Function
+
+A function can have zero or more parameters. These parameters are specified
+within parentheses `()` separated by commas. If a function has no parameters,
+the parentheses must still be present.
+
+A function must terminate with a `return` statement to return a value back to
+the caller. Only a single value can be returned. The type of a return can
+vary between return statements.
+
+Anonymous function example:
+
+```sentinel
+add1 = func(x) { return x + 1 }
+```
+
+Named function example:
+
+```sentinel
+func add1(x) {
+ return x + 1
+}
+```
+
+Both examples create a function that adds 1 to the parameter `x`.
+
+## Calling a Function
+
+Functions are called by accessing their name followed by parentheses with
+a list of comma-separated values for the parameters. If the function takes no
+parameters, an empty set of parentheses must still be used to denote a
+function call.
+
+To call the function in the example above:
+
+```sentinel
+x = 1
+y = add1(x)
+```
+
+## Scoping
+
+The body of a `func` creates a new [scope](/sentinel/language/scope).
+
+The parent scope is the scope in which the function is created. This allows
+for the creation of functions within functions that can access their outer
+scopes.
+
+Example:
+
+```sentinel
+f = func() {
+ a = 42
+ print(a) // 42
+ return undefined
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+f = func() {
+ a = 42
+ return undefined
+}
+
+print(a) // 18
+```
+
+And below is an example of creating a function in a function which uses
+outer values:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double()
+}
+
+print(f()) // 84
+```
+
+A more complex example below shows how scoping works when passing
+functions around as arguments or results:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double
+}
+
+double = f()
+print(double()) // 84
+```
+
+## Pass By Value
+
+The parameters to a function are passed by value, but not deep copied.
+This means that elements of collections can still be modified but the
+root value cannot be changed.
+
+Example:
+
+```sentinel
+f = func(x) {
+ x = "value"
+ return x
+}
+
+x = "outside"
+f(x)
+print(x) // "outside"
+```
+
+```sentinel
+f = func(x) {
+ append(x, "value")
+ return x
+}
+
+x = []
+f(x)
+print(x) // ["value"]
+```
+
+## Recursion
+
+A function is allowed to call other functions, including itself.
+This can be used to implement recursion. For example, the fibonacci sequence
+is implemented below:
+
+```sentinel
+fib = func(x) {
+ if x <= 0 {
+ return undefined
+ }
+
+ if x == 1 {
+ return 1
+ } else {
+ return x + fib(x - 1)
+ }
+}
+```
+
+Note that this example also shows using
+[undefined](/sentinel/language/undefined)
+as a return value in cases where a function has undefined behavior.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/language/imports.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/language/imports.mdx
new file mode 100644
index 0000000000..0ed1541b85
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/language/imports.mdx
@@ -0,0 +1,116 @@
+---
+page_title: Sentinel Language - Imports
+sidebar_current: docs-language-imports
+description: >-
+ Imports enable a Sentinel policy to access reusable libraries and external
+ data and functions.
+layout: docs
+---
+
+# Language: Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external
+data and functions. The exact list of available imports is dependent on the
+host system. Host systems may also make the available imports configurable
+via plugins.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that
+the import `calendar` allows access to engineer calendars. This import
+only exists for the purpose of this example and at the time of writing is
+not a real available import.
+
+```sentinel
+import "calendar"
+
+// Get the calendar for Bob for today
+bob_calendar = calendar.for("bob").today
+
+// Allow this policy to pass if Bob is not on vacation.
+main = rule { not bob_calendar.has_event("vacation") }
+```
+
+This example shows an import being used to deny a behavior if Bob is on
+vacation. Hopefully real policies do not depend on a single person being
+in the office, but the example shows the power of imports.
+
+In addition to consuming imports, you can also
+[extend Sentinel by writing custom imports](/sentinel/extending)
+This allows you to add any arbitrary behavior to your Sentinel-protected
+systems.
+
+## Importing
+
+Whether accessing a built-in library or an external plugin, the syntax
+for an import is the same:
+
+```sentinel
+import "name"
+```
+
+This adds the import with the name `name`. It can be referenced using
+the identifier `name`. For example, if the import above exposed first
+and last name data, you could access it via `name.first` or `name.last`.
+
+You can shadow imports by assigning a variable. In the example below,
+the import `name` becomes unavailable within the function because it is
+shadowed by a variable of the same name.
+
+Shadowing an import is not the same as overwriting a variable. Imports
+are not part of the [scope](/sentinel/language/scope), meaning that
+the shadow only exists for the scope it is created within. For everything
+else, the import works even after it is shadowed. The example below shows that
+as well.
+
+```sentinel
+import "name"
+
+f = func() {
+ name = "jane"
+ return name
+}
+
+print(f()) // "jane"
+print(name.first) // "bob"
+```
+
+## Aliases
+
+An import can be aliased to another name using the syntax below:
+
+```sentinel
+import "name" as other
+```
+
+This would make the import "name" available as `other`.
+
+An import may only be imported once, regardless of aliases. The following
+would be **invalid** and would result in an error:
+
+```sentinel
+import "name" as one
+import "name" as two
+```
+
+## Accessing Import Data
+
+Imports are accessed with dot-separated identifiers, such as `name.first` or
+`name.stage.first`. These are called _selector expressions_. An import may
+return nested data that further can be accessed via selector expressions.
+
+In the first example on this page with the "calendar" import, we see this:
+
+```sentinel
+import "calendar"
+
+a = calendar.for("bob") // selector expression calendar.for
+b = a.today // selector expression on the result
+c = b.vacation // accessing further nested data
+```
+
+The exact structure of an import is dependent on the import. Please reference
+the documentation for an import to learn more.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/language/index.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/language/index.mdx
new file mode 100644
index 0000000000..7f588c94cd
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/language/index.mdx
@@ -0,0 +1,114 @@
+---
+page_title: Sentinel Language
+sidebar_current: docs-language-basics
+description: |-
+ Sentinel policies are written using the Sentinel language. This language
+ is easy to learn and easy to write. You can learn the Sentinel language
+ and be productive within an hour. Learning Sentinel doesn't require any
+ formal programming experience.
+layout: docs
+---
+
+# Sentinel Language
+
+Sentinel policies are written using the Sentinel language. This language
+is easy to learn and easy to write. You can learn the Sentinel language
+and be productive within an hour. Learning Sentinel doesn't require any
+formal programming experience.
+
+This language guide will contain many details that are not necessary to
+be productive with Sentinel or are targeted to those who are looking for
+exact information about the language (such as what types of line endings are
+allowed). You can safely ignore these sentences.
+
+You may also view the
+[official language specification](/sentinel/language/spec)
+This is a specific and detailed document on the syntax and behavior of
+the language primarily intended for implementation creators and to disambiguate
+the language.
+
+## Simplest Example
+
+The example below is about the simplest practical example of Sentinel.
+It is reasonable to imagine this as a realistic policy. This shows that
+in most cases, Sentinel will be extremely simple:
+
+```sentinel
+main = rule { request.method is "GET" and request.headers contains "X-Key" }
+```
+
+## Files
+
+Sentinel policies are single files that end in the `.sentinel` file extension.
+There is currently no built-in mechanism to Sentinel for merging multiple
+files. This is purposefully done to make Sentinel policies easy to submit
+to systems that support Sentinel policies.
+
+Sentinel policy files must be UTF-8 encoded and can end in both
+Unix (LF) or Windows (CRLF) line breaks. When running the
+[auto-formatter](#),
+line endings will always use Unix line breaks.
+
+## Ordering
+
+Sentinel policies are executed top-down. For example:
+
+```sentinel
+a = 1 // a = 1 here
+b = a + 1 // b = 2 here
+a = 3 // a = 3, b = 2
+```
+
+In this example, the value of `a` and `b` is shown at each line. Since Sentinel
+executes values top-down, the final value of `a` is 3 and `b` is 2. `b` _does
+not_ become 4.
+
+## Main
+
+Sentinel expects there to be a `main` [rule](/sentinel/language/rules).
+The value of this rule is the result of the entire policy.
+
+The result of the policy depends on the evaluated contents of the `main` rule.
+For booleans, a policy passes on a `true` value, and fails on a `false` value.
+Other types generally follow a zero or zero-length pattern for determining
+success.
+
+| Type | Passing Condition | Failing Condition |
+| ------- | ----------------- | -------------------------- |
+| Boolean | `true` | `false` |
+| String | `""` | Any non-zero length string |
+| Integer | `0` | Any non-zero value |
+| Float | `0.0` | Any non-zero value |
+| List | `[]` | Any non-zero length list |
+| Map | `{}` | Any non-zero length map |
+
+A value of main that falls outside of the above types will result in a policy
+error. As a special case, if `main` evaluates to an undefined value, the error
+message will indicate as such, with a reference to where the undefined value was
+encountered.
+
+## More Complex Example
+
+The simple example above is a full working example. In our experience with
+Sentinel, many policies can be representing using this simple form. However,
+to show more features of the language, a more complex example is shown below.
+This example is also a realistic example of what Sentinel may be used for.
+
+```sentinel
+import "units"
+
+memory = func(job) {
+ result = 0
+ for job.groups as g {
+ for g.tasks as t {
+ result += t.resources.memory else 0
+ }
+ }
+
+ return result
+}
+
+main = rule {
+ memory(job) < 1 * units.gigabyte
+}
+```
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/language/lists.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/language/lists.mdx
new file mode 100644
index 0000000000..bc5269d8eb
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/language/lists.mdx
@@ -0,0 +1,135 @@
+---
+page_title: Sentinel Language - Lists
+sidebar_current: docs-language-lists
+description: Lists are a collection of zero or more values.
+layout: docs
+---
+
+# Language: Lists
+
+Lists are a collection of zero or more values.
+
+Lists can be created using by wrapping values in `[]` and separating them
+by commas. An optional trailing comma is allowed. List elements can be
+differing types. Examples:
+
+```sentinel
+[] // An empty list
+["foo"] // Single element list
+["foo", 1, 2, true] // Multi element list with different types
+["foo", [1, 2]] // List containing another list
+```
+
+A list can be [sliced](/sentinel/language/slices) to create sublists.
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for value inclusion in a list.
+
+## Accessing Elements
+
+List elements can be accessed with the syntax `name[index]` where
+`index` is zero-indexed.
+
+A negative index accesses the list in reverse. It is the same as
+reversing a list and then using a positive index. Similar to a positive
+index, it is bounded by the length of the list as a negative value.
+
+Accessing beyond the length of the list results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+a = ["foo", 1, true, [1, 2]]
+
+a[0] // "foo"
+a[2] // true
+a[4] // undefined
+a[-2] // true
+a[-4] // "foo"
+a[-5] // undefined
+a[3][1] // 2
+```
+
+## List Append
+
+Values can be appended to a list using the built-in
+[append](/sentinel/functions/append) function.
+
+This modifies the list in-place and returns
+[undefined](/sentinel/language/undefined). For more information on
+why `append` behaves this way, please read the full documentation for the
+[append](/sentinel/functions/append) function.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append([1,2], "foo") // [1, 2, "foo"]
+append([1,2], [3]) // [1, 2, [3]]
+append(1, 3) // error()
+```
+
+## List Concatenation
+
+Two lists can be concatenated using the `+` operator or the shorthand
+`+=` assignment operator. For the `+` operator, a new list is returned.
+For `+=`, the left-hand list is modified in place.
+
+Examples:
+
+```sentinel
+[1] + [2] // [1, 2]
+[1] + [[1]] // [1, [1]]
+[1] + 1 // error
+
+a = [1]
+a += [2] // a = [1, 2]
+a += 3 // error
+```
+
+## List Length
+
+The length of a list can be retrieved using the
+[length](/sentinel/functions/length) function.
+
+Examples:
+
+```sentinel
+length([]) // 0
+length(["foo"]) // 1
+```
+
+## Removing Items From a List
+
+You can use a combination of [list concatenation](#list-concatenation) and
+[slicing](/sentinel/language/slices) to remove elements from a list.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+a = a[:2] + a[3:] // [1, 2, 4, 5]
+```
+
+The shorthand shown here is effectively the same as `a[0:2] + a[3:length(a)]`,
+which creates a new list out of the concatenation two sub-lists composed of the
+first two elements, and the rest of the list starting at index 3. This
+effectively removes the 3rd element from the list (index 2).
+
+## List Comparison
+
+Lists may be [compared](/sentinel/language/boolexpr#comparison-operators)
+for equality. Lists are equal if they are of equal length and their
+corresponding elements are comparable and equal. Lists with the same elements
+but in a different order are not equal.
+
+```sentinel
+[1, 2] is [1, 2] // true
+[1, 2] is [2, 1] // false
+["a"] is ["a", "b"] // false
+["a", ["b", "c"]] is ["a", ["b", "c"]] // true
+```
+
+List comparison speed is O(N), meaning that the speed of the comparison is
+_linearly_ proportional to the number of elements in the list. The more
+elements, the more iterations that are necessary to verify equality.
+
+-> The N-value quoted above should account for the sum of the elements of _all_
+lists in the subjects of comparison, as list comparison will recurse into these
+lists to check for equality.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/language/logging-errors.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/language/logging-errors.mdx
new file mode 100644
index 0000000000..705a72cbd4
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/language/logging-errors.mdx
@@ -0,0 +1,52 @@
+---
+page_title: Sentinel Language - Logging and Errors
+sidebar_current: docs-language-log
+description: The built-in functions `print` and `error` can be used for logging and errors.
+layout: docs
+---
+
+# Language: Logging and Errors
+
+The built-in functions `print` and `error` can be used for logging
+and errors. Logging is helpful to understand how a policy is executing
+in development. And errors are useful to halting execution immediately in
+certain scenarios.
+
+## Print
+
+The `print` function is used to output debug information. The exact location
+where this print statements go is dependent on the host application and
+the Sentinel runtime itself.
+
+The `print` function takes zero or more Sentinel values as arguments.
+Each value is formatted for human-friendly output and space-separated.
+Example:
+
+```sentinel
+value = 42
+print("the value is", value) // the value is 42
+
+map = { "foo": false }
+print(map) // { "foo": false }
+
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // false
+```
+
+## Errors
+
+Certain actions in Sentinel, such as accessing an undefined variable,
+attempting to add two incompatible types, etc. result in _runtime errors_.
+These errors halt the execution of a policy immediately. The pass/fail result
+of a policy is considered fail.
+
+Runtime errors can be manually triggered using the `error` function.
+The `error` function behaves exactly like the `print` function except that
+execution is halted immediately.
+
+This should only be used in scenarios where the policy _must fail_ due to
+some unexpected or invalid condition. The line between `error` and
+[undefined](/sentinel/language/undefined) can sometimes be unclear. Undefined
+is recommended when a policy can conceivably recover or safely ignore the
+undefined behavior. An error should be used when the policy should fail and
+notify someone regardless.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/language/loops.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/language/loops.mdx
new file mode 100644
index 0000000000..622732bd35
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/language/loops.mdx
@@ -0,0 +1,92 @@
+---
+page_title: Sentinel Language - Loops
+sidebar_current: docs-language-loops
+description: >-
+ Loop statements allow you to execute a body of code for each element in a
+ collection or for some fixed number of times.
+layout: docs
+---
+
+# Language: Loops
+
+Loop statements allow you to execute a body of code for each element in
+a collection or for some fixed number of times.
+
+Loop statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## For Statements
+
+`for` statements allow repeated execution of a block for each
+element in a collection.
+
+Example:
+
+```sentinel
+// A basic sum
+count = 0
+for [1, 2, 3] as num {
+ count += num
+}
+```
+
+The syntax is `for COLLECTION as value`. This will iterate over the
+collection, assigning each element to `value`. In the example above,
+each element is assigned to `num`. The body is executed for each element.
+In the example above, the body adds `num` to the `count` variable. This
+creates a basic sum of all values in the collection.
+
+For a map, the assigned element is the key in the map. In the example
+below, `name` would be assigned map keys.
+
+```sentinel
+list = []
+for { "a": 1, "b": 2 } as name {
+ append(list, name)
+}
+
+print(list) // ["a" "b"]
+```
+
+An alternate syntax is `for COLLECTION as key, value`. This will assign
+both the key and value to a variable. For a list, the key is the element
+index. For a map, it is the key and value is assigned the element value.
+Example:
+
+```sentinel
+count = 0
+for { "a": 1, "b": 2 } as name, num {
+ count += num
+}
+
+print(count) // 3
+```
+
+## Scoping
+
+The body of a `for` statement creates a new
+[scope](/sentinel/language/scope). If a variable is assigned within
+the body of a for statement that isn't assigned in a parent scope,
+that variable will only exist for the duration of the body execution.
+
+Example:
+
+```sentinel
+for list as value {
+ a = 42
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+for list as value {
+ a = 42
+}
+
+print(a) // 18
+```
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/language/maps.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/language/maps.mdx
new file mode 100644
index 0000000000..298bf9a567
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/language/maps.mdx
@@ -0,0 +1,121 @@
+---
+page_title: Sentinel Language - Maps
+sidebar_current: docs-language-maps
+description: >-
+ Maps are a collection of zero or more key/value pairs. These are useful for
+ storing values that are looked up by a unique key.
+layout: docs
+---
+
+# Language: Maps
+
+Maps are a collection of zero or more key/value pairs. These are useful
+for storing values that are looked up by a unique key.
+
+Maps can be created using `{}` and specifying key/value pairs. Keys and
+values can be differing types. An optional trailing comma is allowed.
+Examples:
+
+```sentinel
+// Empty map
+{}
+
+// Map with a single value on one line
+{ "key": "value" }
+
+// Map with multiple values with differing types on multiple lines
+{
+ "key": "value",
+ 42: true,
+}
+```
+
+Maps are **unordered**. When
+[looping](/sentinel/language/loops)
+over a map, the key/value pairs can be returned in any order.
+
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for key inclusion in a map.
+
+## Accessing Elements
+
+Map elements can be accessed with the syntax `name[key]`. This looks up
+a value by a key.
+
+Accessing a key that doesn't exist results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+map = { "key": "value", 42: true, }
+
+map["key"] // "value"
+map[42] // true
+map[0] // undefined
+```
+
+## Modifying or Adding Elements
+
+Elements can be added or modified in a map by assigning to `name[key]`.
+If the key doesn't exist, the value is added. If the key already exists,
+the value is overridden.
+
+```sentinel
+map = { "key": "value" }
+
+map[42] = true // Add a new key/value
+map["key"] = 12 // Modify the value of "key"
+```
+
+## Deleting Elements
+
+An element can be deleted from a map using the
+[delete](/sentinel/functions/delete) function.
+
+Examples:
+
+```sentinel
+map = { "key": "value" }
+delete(map, "key") // map is now empty
+delete(map, "other") // no effect for non-existent key
+```
+
+## Keys and Values
+
+The keys and values of a map can be retrieved as
+[lists](/sentinel/language/lists) using the
+[keys](/sentinel/functions/keys) and
+[values](/sentinel/functions/values) functions.
+
+Because maps are unordered, the keys and values are returned in an
+unspecified order. It should not be assumed that keys and values will be
+returned in a consistent order.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+## Map Comparison
+
+Maps may be [compared](/sentinel/language/boolexpr#comparison-operators) for
+equality. Maps are equal if they are of equal length and both their
+corresponding keys and values are comparable and equal.
+
+```sentinel
+{"foo": "bar"} is {"foo": "bar"} // true
+{"foo": "bar"} is {"baz": "bar"} // false
+{"foo": "bar"} is {"foo": "baz"} // false
+{"foo": "bar"} is {"foo": "bar", "baz": "qux"} // false
+{1: "a"} is {1.0: "a"} // true (int/float comparable)
+
+// also true (maps are not ordered):
+{"m": {"a": "b"}, "l": ["a"]} is {"l": ["a"], "m": {"a": " b"}}
+```
+
+-> The current worst-case comparison speed for maps is O(N²), or _quadratic
+time_. This is the square of the cumulative elements or the parent and all child
+maps contained within the map. Consider this when making comparisons on larger,
+more complex maps. This may be optimized in the future.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/language/parameters.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/language/parameters.mdx
new file mode 100644
index 0000000000..348acf2593
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/language/parameters.mdx
@@ -0,0 +1,133 @@
+---
+page_title: Sentinel Language - Parameters
+sidebar_current: docs-language-parameters
+description: >-
+ Parameteres allow a Sentinel policy to describe variables that are expected
+ to be supplied by the calling application, in addition to any default value.
+layout: docs
+---
+
+# Language: Parameters
+
+Sentinel allows a policy author to supply parameters to help facilitate policy
+reuse and ensure sensitive values do not need to be hard-coded in a policy.
+
+Parameters are supplied by using the `param` keyword, followed by an identifier.
+A default value can also be supplied by using the `default` keyword.
+
+```sentinel
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+```
+
+Once declared, parameters can be used like any other variable, including being
+re-assigned.
+
+```sentinel
+param foo default 1 // 1 (default)
+
+foo = foo + 1 // 2
+```
+
+## Variable Descriptions
+
+You can supply a description to a parameter by adding a comment at the top of
+it. This value can be communicated to a specific implementation of Sentinel to
+provide information about what the parameter is for during configuration.
+
+```sentinel
+// An example parameter. Must be supplied or the policy will fail.
+param foo
+```
+
+## Supplying Parameter Values Using the Sentinel CLI
+
+In a production implementation, supplying parameters to a policy is an
+implementation-specific detail - see the documentation for your particular
+implementation for details.
+
+Using the [Sentinel CLI](/sentinel/commands), you can supply parameters one of
+four ways.
+
+### Supplying Parameter Values Using the Configuration File
+
+You can supply parameters using the
+[`param`](/sentinel/configuration#parameters) section of the
+[configuration file](/sentinel/configuration).
+
+```hcl
+param "foo" {
+ value = "bar"
+}
+```
+
+This method works for both `sentinel apply` and `sentinel test`.
+
+### Supplying Parameter Values Using the Environment
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can supply a value using environment variables - prefix the parameter with
+`SENTINEL_PARAM_`, using the name of the parameter to supply.
+
+```plaintext
+SENTINEL_PARAM_foo=bar sentinel apply policy.sentinel
+```
+
+### Supplying Parameter Values Using the CLI
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can also use the `-param` CLI argument to supply parameter in a `key=value`
+pair.
+
+```plaintext
+sentinel apply -param foo=bar policy.sentinel
+```
+
+### Interactive CLI Prompting
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+If a required value has not been supplied when a policy is run with `sentinel apply`, it will be prompted for, along with its description:
+
+
+
+ $ sentinel apply policy.sentinel
+
+ policy.sentinel:2:7: requires value for parameter foo
+
+ An example parameter. Must be supplied or the policy will fail.
+
+
+ Values can be strings, floats, or JSON array or object values. To
+ force
+
+ strings, use quotes.
+
+
+ Enter a value: bar
+
+
+
+ Pass
+
+
+
+
+### CLI Value Format
+
+-> **NOTE:** This section contains details for the parameter features supported by `sentinel apply`.
+
+The CLI takes either strings, or JSON numbers, arrays, or maps. If you need a
+literal string value, quote the value.
+
+```sentinel
+foo // string
+42 // number (float)
+"42" // string ("42", without quotes)
+[1, 2] // array (list)
+{"a": "b"} // object (map)
+```
+
+-> **NOTE:** Boolean values are not supported by this method.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/language/rules.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/language/rules.mdx
new file mode 100644
index 0000000000..e31924d414
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/language/rules.mdx
@@ -0,0 +1,204 @@
+---
+page_title: Sentinel Language - Rules
+sidebar_current: docs-language-rules
+description: >-
+ Rules form the basis of a policy by representing behavior that is either
+ passing or failing (true or false).
+layout: docs
+---
+
+# Language: Rules
+
+Rules form the basis of a policy by representing behavior that is either passing
+or failing. A policy can be broken down into a set of rules. Breaking down a
+policy into a set of rules can make it more understandable and aids with
+testing.
+
+An example is shown below:
+
+```sentinel playground
+weather = "sunny"
+day = "wednesday"
+is_sunny = rule { weather is "sunny" }
+is_wednesday = rule { day is "wednesday" }
+
+main = rule {
+ is_sunny and
+ is_wednesday
+}
+```
+
+A rule contains a single expression. This can be a simple [boolean
+expression](/sentinel/language/boolexpr), or an expression representing the
+discovery of a set of violations using more in-depth expressions like [`filter`
+and `map`](/sentinel/language/collection-operations). You can split expressions
+into multiple lines for readability, as you would with any other expression
+within Sentinel.
+
+A rule is also _lazy_ and _memoized_. _Lazy_ means that the rule is only
+evaluated when it is actually required, not at the point that it is
+created. This is covered in more detail in the [lazy](#lazy-and-memoized) section.
+_Memoized_ means that the value is only computed once and then saved. Once
+a rule is evaluated, the result of it is reused anytime the rule is referenced
+again.
+
+## Making rules easier to understand
+
+Rules are an abstraction that make policies much more understandable
+by making it easier for humans to see what the policy is trying to do.
+
+For example, consider the policy before which doesn't abstract into rules:
+
+```sentinel
+main = rule {
+ ((day is "saturday" or day is "sunday") and homework is "") or
+ (day in ["monday", "tuesday", "wednesday", "thursday", "friday"] and
+ not school_today and homework is "")
+}
+```
+
+Assume that `day`, `homework`, and `school_day` are available.
+
+In plain English, this policy is trying to say: you can play with your friends
+only on the weekend as long as there is no homework, or during the week if
+there is no school and no homework.
+
+Despite the relatively simple nature of this policy (a few lines, about 5
+logical expressions), it is difficult to read and understand, especially if
+you've not seen it before.
+
+The same policy using rules as an abstraction:
+
+```sentinel
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+is_valid_weekend = rule { is_weekend and homework is "" }
+is_valid_weekday = rule { not is_weekend and not school_today and homework is "" }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+By reading the names of the rules, its much clearer to see what each individual
+part of the policy is trying to achieve. The readability is improved even
+further when adding comments to the rules to explain them further, which is
+a recommended practice:
+
+```sentinel
+// A weekend is Sat or Sun
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+// A valid weekend is a weekend without homework
+is_valid_weekend = rule { is_weekend and homework is "" }
+
+// A valid weekday is a weekday without school
+is_valid_weekday = rule { not is_weekend and not school_today and homework is "" }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+## "When" Predicates
+
+A rule may have an optional `when` predicate attached to it. When this is
+present, the rule is only evaluated when the predicate results in `true`.
+Otherwise, the rule is not evaluated and the result of the rule is always
+`true`.
+
+"When" predicates should be used to define the context in which the rule
+is semantically meaningful. It is common when translating human language
+policy into Sentinel to have scenarios such as "when the key has a prefix
+of `/account/`, the remainder must be numeric." In that example, when the
+key _does not_ have the specified prefix, the policy doesn't apply.
+
+Assume you have rules `is_prefix` and `is_numeric` to check both the
+conditions in the example above. An example is shown with and without
+the "when" predicate:
+
+```sentinel
+example_no_when = rule { (is_prefix and is_numeric) or not is_prefix }
+
+example_when = rule when is_prefix { is_numeric }
+```
+
+The rules are equivalent in behavior for all values of `is_prefix` and
+`is_numeric`, but the second rule is more succint and easier to understand.
+
+## Testing
+
+To verify a policy works correct, the
+[built-in Sentinel test framework](/sentinel/writing/testing)
+uses rules as the point where you can assert behavior. You say that
+you expect certain rules to be true or false. By doing so, you ensure that
+the policy results in the value you expect using the rule flow that you
+expect.
+
+Therefore, in addition to readability, we recommend splitting policies into
+rules to aid testability.
+
+## Non-Boolean Values
+
+Sentinel supports non-boolean values in rules. This can be useful for passing
+more detail along to a calling integration when more detail is required than a
+boolean value would be able to communicate. This data shows up in the [policy
+trace](/sentinel/writing/tracing) and can be utilized in different ways
+depending on the integration you are using Sentinel with.
+
+Consider a different take on our policy above:
+
+```sentinel
+// A policy to check a set of scheduled days to determine what days you can't
+// come out and play, based on the day not being a weekend day and there being
+// homework to do.
+
+param days
+
+main = rule {
+ filter days as d {
+ d.day not in ["saturday", "sunday"] and
+ d.homework is not ""
+ }
+}
+```
+
+When given a value in the set of `days` that has a `day` that is a weekday, and
+`homework` present, the policy will fail. Additional, when tracing was enabled,
+the days that were detected as violating the policy would be given in the trace
+for the `main` rule.
+
+Generally, for non-boolean values, a policy fails on non-zero data. See the
+details on [the `main` rule](/sentinel/language#main) for more details.
+
+The result of a rule must be either a boolean, string, integer, float, list, or
+map. All other types will result in a runtime error.
+
+## Lazy and Memoized
+
+A rule is _lazy_ and _memoized_.
+
+_Lazy_ means that the rule is only evaluated when it is actually required,
+not at the point that it is created. And _memoized_ means that the value is
+only computed once and then saved. Once a rule is evaluated, the result of it
+is reused anytime the rule is referenced again.
+
+Both of these properties have important implications for Sentinel
+policies:
+
+**Performance:** Rules are a way to improve the performance of a Sentinel
+policy. If you have a boolean expression that is reused a lot, a rule will
+only be evaluated once. In the example shown above, `is_weekend` is used
+multiple times, but will only have to be evaluated once.
+
+**Behavior:** Rules accessing variables see the value of those variables
+at _the time they are evaluated_. This can lead to surprising behavior
+in some cases. For example:
+
+```sentinel playground
+a = 1
+b = rule { a == 1 }
+a = 2
+main = b
+```
+
+In this example, `main` will actually result to `false`. It is evaluated
+when it is needed, which is when the policy executes `main`. At this point,
+`a` is now 2 and `b` has not been evaluated yet. Therefore, `b` becomes `false`.
+All future references to `b` will return `false` since a rule is memoized.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/language/scope.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/language/scope.mdx
new file mode 100644
index 0000000000..f498e26dc2
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/language/scope.mdx
@@ -0,0 +1,47 @@
+---
+page_title: Sentinel Language - Scope
+sidebar_current: docs-language-scope
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Scope
+
+Scope is the context in which variables are created and accessed. If a
+variable exists in a parent scope, it is accessed and can be modified.
+Otherwise, the variable is created in your current scope.
+
+Scopes are created with _blocks_. A block is a possibly empty sequence
+of statements within matching brace brackets `{}`. You may nest blocks
+to create nested scopes.
+
+## Package Scope
+
+The package scope is the top level scope within a policy file and encapsulates
+the entire file contents. Imports, parameters and named functions must be
+declared within the package scope.
+
+## Implicit Scopes
+
+Each `any`, `all`, and `for` statement is considered to be in its own block.
+Note that `if` statements _do not_ create their own block.
+
+## Examples
+
+The example policy below shows various effects of scopes:
+
+```sentinel
+a = 1
+print(a) // 1
+
+f = func() {
+ print(a) // 1
+ a = 12
+ b = 1
+ return undefined
+}
+f()
+
+print(a) // 12
+print(b) // undefined
+```
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/language/slices.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/language/slices.mdx
new file mode 100644
index 0000000000..82dc7f4c18
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/language/slices.mdx
@@ -0,0 +1,45 @@
+---
+page_title: Sentinel Language - Slices
+sidebar_current: docs-language-slices
+description: >-
+ Slices are an efficient way to create a substring or sublist from an existing
+ string or list, respectively.
+layout: docs
+---
+
+# Language: Slices
+
+Slices are an efficient way to create a substring or sublist from an
+existing string or list, respectively.
+
+The syntax for creating a slice is:
+
+```sentinel
+a[low : high]
+```
+
+`low` is the lowest index to slice from. The resulting slice includes
+the value at `low`. `high` is the index to slice to. The resulting slice
+will not include the value at `high`. The length of the resulting list
+or string is always `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+
+a = "hello"
+b = a[1:4] // "ell"
+```
+
+## Convenience Shorthands
+
+Some convenience shorthands are available by omitting the value for either the
+low or high index in the slice expression: omitting `low` implies a low index of
+0, and omitting `high` implies a high index of the length of the list.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+
+b = a[:2] // [1, 2] (same as a[0:2])
+b = a[2:] // [3, 4, 5] (same as a[2:length(a)])
+```
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/language/spec.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/language/spec.mdx
new file mode 100644
index 0000000000..0877897422
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/language/spec.mdx
@@ -0,0 +1,1330 @@
+---
+page_title: Sentinel Language Specification
+sidebar_current: docs-language-spec
+description: This is the specification for the Sentinel policy language.
+layout: docs
+---
+
+# Sentinel Language Specification
+
+This is the specification for the Sentinel policy language.
+
+The Sentinel language is designed with policy enforcement in mind. It is dynamically typed and garbage collected and has explicit support for _rule_ construction representing boolean logic.
+
+The language is designed to be easy to learn and use by non-programmers. It is expected to be embedded within applications.
+
+## Table of Contents
+
+
+
+
+- [Source code representation](#source-code-representation)
+- [Declarations and Scope](#declarations-and-scope)
+- [Blocks](#blocks)
+- [Lexical Elements](#lexical-elements)
+ - [Comments](#comments)
+ - [Identifiers](#identifiers)
+ - [Keywords](#keywords)
+ - [Operators and Delimiters](#operators-and-delimiters)
+ - [Integer Literals](#integer-literals)
+ - [Floating-point Literals](#floating-point-literals)
+ - [String Literals](#string-literals)
+ - [Implicit Line Joining](#implicit-line-joining)
+ - [Whitespace](#whitespace)
+ - [Semicolons](#semicolons)
+- [Variables](#variables)
+- [Undefined](#undefined)
+- [Expressions](#expressions)
+ - [Operand](#operand)
+ - [Primary Expressions](#primary-expressions)
+ - [Null](#null)
+ - [Booleans](#booleans)
+ - [Boolean Literals](#boolean-literals)
+ - [Boolean Expressions](#boolean-expressions)
+ - [List Literals](#list-literals)
+ - [Map Literals](#map-literals)
+ - [Function Literals](#function-literals)
+ - [Rule Expressions](#rule-expressions)
+ - [Index Expressions](#index-expressions)
+ - [Selectors](#selectors)
+ - [Slice Expressions](#slice-expressions)
+ - [Calls](#calls)
+ - [Operators](#operators)
+ - [Operator Precedence](#operator-precedence)
+ - [Arithmetic Operators](#arithmetic-operators)
+ - [Integer operators](#integer-operators)
+ - [Integer overflow](#integer-overflow)
+ - [Floating-point operators](#floating-point-operators)
+ - [String Concatenation](#string-concatenation)
+ - [List Concatenation](#list-concatenation)
+ - [Comparison Operators](#comparison-operators)
+ - [Logical Operators](#logical-operators)
+ - [Set Operators](#set-operators)
+ - [Matches Operator](#matches-operator)
+ - [Else Operator](#else-operator)
+ - [Quantifier Expressions (any, all, filter, map)](#quantifier-expressions-any-all-filter-map)
+- [Statements](#statements)
+ - [Expression Statements](#expression-statements)
+ - [Assignments](#assignments)
+ - [If Statements](#if-statements)
+ - [Case Statements](#case-statements)
+ - [For Statements](#for-statements)
+ - [Break Statements](#break-statements)
+ - [Continue Statements](#continue-statements)
+ - [Return Statements](#return-statements)
+- [Imports](#imports)
+- [Parameters](#parameters)
+- [Built-in Functions](#built-in-functions)
+ - [Length](#length)
+ - [Collections](#collections)
+ - [List Append](#list-append)
+ - [Map Delete](#map-delete)
+ - [Keys and Values](#keys-and-values)
+ - [Range](#range)
+ - [Type Conversion](#type-conversion)
+ - [Printing](#printing)
+ - [Errors](#errors)
+- [Program Execution](#program-execution)
+
+
+
+## Source code representation
+
+Source code is Unicode text encoded in UTF-8. A "character" referenced in this document refers to a Unicode code point. Each code point is distinct: upper and lower case letters are different characters.
+
+The underscore character \_ (U+005F) is considered a "letter".
+
+```ebnf
+letter = unicode_letter | "_" .
+decimal_digit = "0" … "9" .
+octal_digit = "0" … "7" .
+hex_digit = "0" … "9" | "A" … "F" | "a" … "f" .
+```
+
+## Declarations and Scope
+
+A declaration binds an identifier to a value. An identifier is declared at the point it is first assigned. An assignment is considered a first assignment if the identifier hasn't already been previously declared in the current scope or any parent scopes.
+
+The scope of an identifier is the extent of source text in which the identifier denotes the specified value. Sentinel is lexically scoped using blocks.
+
+An identifier declared in a block may be redeclared in an inner block. While the identifier of the inner declaration is in scope, it denotes the value assigned in the inner declaration.
+
+## Blocks
+
+A block is a possibly empty sequence of statements within matching brace brackets.
+
+```ebnf
+Block = "{" StatementList "}" .
+StatementList = { Statement ";" } .
+```
+
+Blocks nest and affect scoping.
+
+In addition to explicit blocks in the source code, there are implicit blocks:
+
+1. The universe block encompasses all source text.
+2. Each program has a program block containing all source text for that program.
+3. Each "any", "all", and "for" statements is considered to be in its own implicit block. "if" does not create an implicit block.
+
+## Lexical Elements
+
+### Comments
+
+Comments are sections of source text used for documentation.
+
+```plaintext
+# Single line comment
+// Single line comment
+/* multi
+line
+ comment */
+```
+
+Three forms of comments are supported: two single-line forms and one multi-line form. A single-line comment begins with the token `//` or `#`. Everything between the starting token and the end of the line is ignored.
+
+A multi-line comment begins with the token `/*` and ends with the token `*/`. Everything between `/*` and `*/` is ignored.
+
+A comment may not start inside a string literal or inside another comment.
+
+A multi-line comment containing no newlines acts like a space.
+
+### Identifiers
+
+Identifiers name program entities such as rules, variables, and functions. An identifier is a sequence of one or more letters and digits. The first character in an identifier must be a letter.
+
+```ebnf
+identifier = letter { letter | unicode_digit } .
+```
+
+```sentinel
+a
+_a
+_A
+αβ
+```
+
+#### Pre-Declared Identifiers
+
+The following identifiers are implicitly declared in the [universe block](#blocks):
+
+```plaintext
+Constants:
+ false null true undefined
+
+Functions:
+ append bool delete error float int keys length print range string values
+```
+
+### Keywords
+
+The following keywords are reserved and may not be used as identifiers:
+
+```plaintext
+all
+any
+as
+break
+case
+continue
+default
+else
+empty
+filter
+for
+func
+if
+import
+map
+param
+return
+rule
+when
+```
+
+### Operators and Delimiters
+
+The following character sequences represent operators, delimiters, and other special tokens:
+
+```plaintext
++
+-
+*
+/
+%
++=
+-=
+*=
+/=
+%=
+==
+<
+>
+=
+!
+!=
+<=
+>=
+( )
+[ ]
+{ }
+,
+.
+:
+;
+and
+contains
+else
+in
+is
+matches
+not
+or
+xor
+```
+
+As a special case, `else` is both a keyword and operator, depending on the context. See [Else Operator](#else-operator) for more details.
+
+### Integer Literals
+
+An integer literal is a sequence of digits representing an integer constant. An optional prefix sets a non-decimal base: `0` for octal, `0x` or `0X` for hexadecimal. In hexadecimal literals, letters `a-f` and `A-F` represents values 10 through 15.
+
+Integers are signed 64-bit values (-9223372036854775808 to 9223372036854775807).
+
+```ebnf
+int_lit = decimal_lit | octal_lit | hex_lit .
+decimal_lit = ( "1" … "9" ) { decimal_digit } .
+octal_lit = "0" { octal_digit } .
+hex_lit = "0" ( "x" | "X" ) hex_digit { hex_digit } .
+```
+
+```
+42
+0600
+0xBadFace
+170141183460469231731687303715884105727
+```
+
+### Floating-point Literals
+
+A floating-point literal is a decimal representation of a floating-point constant. It has an integer part, a decimal point, a fractional part, and an exponent part. The integer and fractional part comprise decimal digits; the exponent part is an e or E followed by an optionally signed decimal exponent. One of the integer part or the fractional part may be elided; one of the decimal point or the exponent may be elided.
+
+Floating-point numbers are IEEE-754 64-bit floating numbers.
+
+```ebnf
+float_lit = decimals "." [ decimals ] [ exponent ] |
+ decimals exponent |
+ "." decimals [ exponent ] .
+decimals = decimal_digit { decimal_digit } .
+exponent = ( "e" | "E" ) [ "+" | "-" ] decimals .
+```
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+### String Literals
+
+String literals are character sequences between double quotes, as in "bar". Within the quotes, any character may appear except newline and unescaped double quote. The text between the quotes forms the value of the literal.
+
+A multi-character sequence beginning with a backslash encode values in various formats.
+
+Backslash escapes allow arbitrary values to be encoded as ASCII text. There are four ways to represent the integer value as a numeric constant: `\x` followed by exactly two hexadecimal digits; `\u` followed by exactly four hexadecimal digits; `\U` followed by exactly eight hexadecimal digits, and a plain backslash `\` followed by exactly three octal digits. In each case the value of the literal is the value represented by the digits in the corresponding base.
+
+After a backslash, certain single-character escapes represent special values:
+
+```plaintext
+\a U+0007 alert or bell
+\b U+0008 backspace
+\f U+000C form feed
+\n U+000A line feed or newline
+\r U+000D carriage return
+\t U+0009 horizontal tab
+\v U+000b vertical tab
+\\ U+005c backslash
+\" U+0022 double quote
+```
+
+The three-digit octal (\nnn) and two-digit hexadecimal (\xnn) escapes represent individual bytes of the resulting string; all other escapes represent the (possibly multi-byte) UTF-8 encoding of individual characters. Thus inside a string literal \377 and \xFF represent a single byte of value 0xFF=255, while ÿ, \u00FF, \U000000FF and \xc3\xbf represent the two bytes 0xc3 0xbf of the UTF-8 encoding of character U+00FF.
+
+A string value is a (possibly empty) sequence of bytes. Strings are immutable: once created, it is impossible to change the contents of a string.
+
+The length of a string s (its size in bytes) can be discovered using the built-in function `length`. An individual character (of type string) can be accessed by integer indices 0 through `length(s)-1`.
+
+```ebnf
+string_lit = `"` { unicode_value | byte_value } `"` .
+unicode_value = unicode_char | little_u_value | big_u_value | escaped_char .
+byte_value = octal_byte_value | hex_byte_value .
+octal_byte_value = `\` octal_digit octal_digit octal_digit .
+hex_byte_value = `\` "x" hex_digit hex_digit .
+little_u_value = `\` "u" hex_digit hex_digit hex_digit hex_digit .
+big_u_value = `\` "U" hex_digit hex_digit hex_digit hex_digit
+ hex_digit hex_digit hex_digit hex_digit .
+escaped_char = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | `"` ) .
+```
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+### Implicit Line Joining
+
+Expressions can be split over more than one line. For example:
+
+```sentinel
+a or
+b or # This is a comment
+c
+```
+
+Implicitly continued lines can have trailing comments. Blank continued lines are allowed.
+
+### Whitespace
+
+Whitespace is needed to separate tokens, but no distinction is made between the number and combination of whitespace characters. Whitespace characters can be space (U+0020), horizontal tabs (U+0009), carriage returns (U+000D), and newlines (U+000A).
+
+```sentinel
+a or b
+
+a or b
+
+a or
+ b
+
+rule { a or b }
+
+rule {
+ a or b }
+
+rule {
+ a or
+ b
+}
+```
+
+### Semicolons
+
+The formal grammar uses semicolons ";" as terminators in a number of productions.
+It is idiomatic Sentinel source to omit semicolons in most cases.
+
+A semicolon is automatically inserted into the token stream immediately after
+a line's final token if that token is:
+
+- An identifier
+- An integer, float, or string literal
+- The keyword `break`, `continue`, or `return`
+- The delimiter `)`, `]`, or `}`
+
+## Variables
+
+A variable is a storage location for holding a value.
+
+A variable has a dynamic type, which is the concrete type of the value assigned to the variable at run time. The dynamic type may vary during execution and change as a result of further assignments.
+
+A variable's value is retrieved by referring to the variable in an expression; it is the most recent value assigned to the variable. If a variable has not yet been declared (initially assigned), it is an error.
+
+```sentinel
+x = 7 // x is type int
+x = "foo" // x is now type string
+
+x = y // error if y is not previously assigned
+```
+
+## Undefined
+
+The value denoted by the keyword `undefined` represents undefined behavior or values. It can be created directly using the keyword `undefined`. It is also returned as a result of expressions in specified cases.
+
+`undefined` is a valid operand for any operations. Only `undefined or true` will result in true. All other operations result in `undefined`. An exception is if `undefined` is not reached as a result of short-circuit operations.
+
+```sentinel
+undefined OR true = true
+undefined OR false = undefined
+undefined OR undefined = undefined
+undefined AND true = undefined
+undefined AND false = undefined
+undefined AND undefined = undefined
+undefined XOR true = undefined
+undefined XOR false = undefined
+undefined XOR undefined = undefined
+
+// Short-circuit examples
+false OR true OR undefined = true
+false OR undefined OR true = true
+true AND false AND undefined = false
+true AND undefined AND false = undefined
+
+// Non-logical operators
+undefined + 5 = undefined
+-undefined = undefined
+!undefined = undefined
+```
+
+If the result of the main rule is `undefined`, it is treated as `false` but is indicative of erroneous logic.
+
+## Expressions
+
+### Operand
+
+Operands denote the elementary values in an expression. An operand may be a literal, an identifier denoting a variable, rule, or function, or a parenthesized expression.
+
+```ebnf
+Operand = Literal | OperandName | MethodExpr | "(" Expression ")" .
+Literal = BasicLit | CompositeLit | FunctionLit | RuleLit .
+BasicLit = int_lit | float_lit | string_lit .
+OperandName = identifier | QualifiedIdent.
+```
+
+### Primary Expressions
+
+Primary expressions are the operands for unary and binary expressions.
+
+```ebnf
+PrimaryExpr =
+ Operand |
+ PrimaryExpr Selector |
+ PrimaryExpr Index |
+ PrimaryExpr Slice |
+ PrimaryExpr Arguments .
+
+Selector = "." identifier .
+Index = "[" Expression "]" .
+Slice = "[" [ Expression ] ":" [ Expression ] "]" |
+ "[" [ Expression ] ":" Expression ":" Expression "]" .
+Arguments = "(" [ ( ExpressionList | Type [ "," ExpressionList ] ) [ "..." ] [ "," ] ] ")" .
+```
+
+```sentinel
+x
+2
+(s + ".txt")
+f(3.1415, true)
+m["foo"]
+s[i : j + 1]
+obj.color
+f.p[i].x()
+x is empty
+x is not empty
+```
+
+### Null
+
+The reserved word `null` denote the singleton value `null`. Null represents the explicit absense of a value. Behavior of `null` within expressions is specified explicitly for each expression.
+
+### Booleans
+
+### Boolean Literals
+
+The reserved words `true` and `false` denote objects that represent the boolean values `true` and `false`, respectively. These are boolean literals.
+
+```ebnf
+BoolLit = "true" | "false" .
+```
+
+#### Boolean Expressions
+
+Boolean expressions are expressions that must result in a boolean value. Any other value type becomes the `undefined` value.
+
+```ebnf
+BoolExpr = Expression .
+```
+
+### List Literals
+
+A list literal denotes a list, which is an integer indexed collection of values.
+
+```ebnf
+ListLit = "[" [ ElementList [ "," ] ] "]" .
+ElementList = Element { "," Element } .
+Element = Expression | LiteralValue .
+```
+
+A list may contain zero or more values. The number of values in a list is its length.
+
+A list has a set of indices. An empty list has an empty set of indices. A non-empty list has the index set `{0...n - 1}` where `n` is the length of the list. Attempting to access a list using an index that is not a member of its set of indices results in the `undefined` value.
+
+### Map Literals
+
+A map literal denotes a map, which is an unordered group of elements indexed by a set of unique keys.
+
+```ebnf
+MapLit = "{" [ KeyedElementList [ "," ] ] "}" .
+KeyedElementList = KeyedElement { "," KeyedElement } .
+KeyedElement = Element ":" Element .
+```
+
+Keys can only be a boolean, numeric, or string type.
+
+The value of a non-existent key is the `undefined` value.
+
+### Function Literals
+
+A function literal represents a function.
+
+```ebnf
+FunctionLit = "func" Function .
+Function = Parameters FunctionBody .
+FunctionBody = Block .
+Parameters = "(" [ IdentifierList [ "," ] ] ")" .
+IdentifierList = identifier { "," identifier } .
+```
+
+```sentinel
+func(a, b) { return b }
+```
+
+Function literals are only allowed in the file scope. They may refer to variables defined in surrounding blocks.
+
+A function must terminate with a return statement. If a function has no meaningful return value, it should return `undefined`.
+
+### Rule Expressions
+
+A rule is an expression that is evaluated lazily and the result is memoized.
+
+If the optional "when" predicate is present, the rule is evaluated only when
+the "when" boolean expression results in true. If the predicate is false,
+the rule is not evaluated and returns true. The predicate is evaluated when
+the rule would be evaluated; it is also lazy and memoized in the same way.
+
+```ebnf
+RuleExpr = "rule" [ "when" BoolExpr ] "{" Expr "}" .
+```
+
+```sentinel
+rule { x is y }
+
+rule {
+ x == "value" or
+ y == "other"
+}
+
+rule when x is y { y > 42 }
+
+rule { map ["a", "b", "c"] as _, id {
+ { "id": id }
+}}
+```
+
+### Index Expressions
+
+A primary expression of the form `a[x]` denotes the element of a list or map indexed by x. The value x is called the index or key.
+
+For `a` of type map:
+
+- If `a` contains a key `x`, `a[x]` is the map value with key `x`.
+- If `a` does not contain key `x`, `a[x]` is the `undefined` value.
+
+For `a` of type list:
+
+- `x` must be an integer
+- `x` must be in the range `[-1 * length(a), length(a)-1]`
+- If `x` is contained in the set of indices of `a`, `a[x]` is the list element at index `x`. If `x` is negative, `a[x]` is equivalent to `a[length(a)+x]`.
+- If `x` is not contained in the set of indices of `a`, `a[x]` is the `undefined` value.
+
+For `a` of value `null`:
+
+- `a[x]` is the `undefined` value.
+
+Otherwise `a[x]` is an error.
+
+### Selectors
+
+For a primary expression x, the selector expression `x.f` denotes the field `f` of the value `x`. The identifier `f` is called the selector. The type of the selector expression is the type of the selector.
+
+As a special case, selectors can be [reserved words](#keywords) and keyword [operators](#operators-and-delimiters), but cannot be any other non-identifier element.
+
+Selectors are used to access data from [imports](#imports). The first primary expression `x` in `x.f` denotes the import name. The field `f` is the selector to access data from the import.
+
+Selectors may also be used to access map data with static keys. They are syntactic sugar over index expressions. `math.pi` is equivalent to `math["pi"]` and exhibit the same limitations and behavior as an index expression. Selectors cannot, however, be used to assign values to map data.
+
+Selectors on undefined result in undefined.
+
+```sentinel
+math.pi
+time.pst.hour
+```
+
+### Slice Expressions
+
+Slice expressions construct a substring or list from a string or list.
+
+The primary expression
+
+```sentinel
+a[low : high]
+```
+
+constructs a substring or list. The indices `low` and `high` select which elements of operand `a` appear in the result. The result has indices starting at 0 and length equal to `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+```
+
+For convenience, any of the indices may be omitted. A missing `low` index defaults to zero; a missing `high` index defaults to the length of the sliced operand:
+
+```sentinel
+a[2:] // same as a[2 : length(a)]
+a[:3] // same as a[0 : 3]
+a[:] // same as a[0 : length(a)]
+```
+
+The indices are in range if `0 <= low <= high <= length(a)`, otherwise they are out of range. If the indices are out of range at run time, the result is the `undefined` value.
+
+If `a` is the value `null`, the result is the `undefined` value.
+
+If `a` is any other value type, it is an error.
+
+### Calls
+
+Given an expression `f` where `f` is a function value:
+
+```sentinel
+f(a1, a2, … an)
+```
+
+calls `f` with arguments `a1, a2, ... an`. The type of the expression is the result of `f`. The arguments are evaluated left to right. Arguments are passed by value.
+
+### Operators
+
+```ebnf
+Expression = UnaryExpr | Expression binary_op Expression .
+UnaryExpr = PrimaryExpr | unary_op UnaryExpr .
+
+binary_op = logical_op | set_op | rel_op | add_op | mul_op | else_op .
+logical_op = "and" | "or" | "xor" .
+set_op = ["not"] ( "contains" | "in" ).
+rel_op = "==" | "!=" | "<" | "<=" | ">" | ">=" |
+ "is" | "is not" | "matches" | "not matches" .
+add_op = "+" | "-" .
+mul_op = "*" | "/" | "%" .
+else_op = "else" .
+unary_op = "+" | "-" | "!" | "not" | empty_op | defined_op .
+empty_op = "is empty" | "is not empty" .
+defined_op = "is defined" | "is not defined" .
+```
+
+#### Operator Precedence
+
+Unary operators have the highest precedence.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Binary operators of the same precedence associate from left to right. For instance, x / y _ z is the same as (x / y) _ z.
+
+### Arithmetic Operators
+
+Arithmetic operators apply to numeric values and yield a result of the same type when both operands are the same.
+
+Arithmetic operations between integer and floating-point types result in a floating-point value with the integer operand treated as a floating-point value for purposes of calculation.
+
+Arithmetic operations between numeric values (integer, floating-point) and non-numeric values (strings, lists) are not permitted.
+
+All five supported arithmetic operators (+, -, \*, /, %) apply to both integer and floating-point types; + also applies to strings.
+
+```plaintext
++ sum integers, floats, strings, lists
+* difference integers, floats
+* product integers, floats
+/ quotient integers, floats
+% remainder integers, floats
+```
+
+#### Integer operators
+
+For two integer values x and y, the integer quotient `q = x / y` and remainder `r = x % y` satisfy the following relationships:
+
+```sentinel
+x = q*y + r and |r| < |y|
+```
+
+with x / y truncated towards zero.
+
+```plaintext
+ x y x / y x % y
+ 5 3 1 2
+-5 3 -1 -2
+ 5 -3 -1 2
+-5 -3 1 -2
+```
+
+As an exception to this rule, if the dividend x is the most negative value for the int type of x (-9223372036854775808), the quotient `q = x / -1` is equal to x (and r = 0).
+
+If the divisor is a constant, it must not be zero. If the divisor is zero at run time, an error occurs.
+
+#### Integer overflow
+
+For signed integers, the operations `+`, `-`, and `*` may legally overflow and the resulting value exists and is deterministically defined by the signed integer representation, the operation, and its operands. No exception is raised as a result of overflow.
+
+#### Floating-point operators
+
+For floating-point numbers, +x is the same as x, while -x is the negation of x. The result of a floating-point division by zero is not specified beyond the IEEE-754 standard.
+
+#### String Concatenation
+
+Strings can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = "hi"
+y = "hello"
+x = x + ", " + y // "hi, hello"
+x += " and good bye" // "hi, hello and good bye"
+```
+
+String addition creates a new string by concatenating the operands.
+
+#### List Concatenation
+
+Lists can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = [1, 2]
+x = x + [2, 3] // [1, 2, 2, 3]
+x += [4] // [1, 2, 2, 3, 4]
+```
+
+List addition creates a new list by concatenating the operands.
+
+### Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+In any comparison, the two operands must be equivalent types. The only exception are integers and floats, which are considered numeric and may be compared. Any comparison between other non-matching types results in the `undefined` value.
+
+The equality operators `==`, `!=`, `is`, and `is not` apply to operands that are comparable. The ordering operators `<`, `<=`, `>`, and `>=` apply to operands that are ordered. The behavior of `is` with `==` and `is not` with `!=` is identical. These terms and the result of the comparisons are defined as follows:
+
+- Boolean values are comparable. Two boolean values are equal if they are either both true or both false.
+- Integer values are comparable and ordered, in the usual way.
+- Floating point values are comparable and ordered, as defined by the IEEE-754 standard.
+- An integer compared with floating point value treats the integer as the converted floating point value.
+- String values are comparable and ordered, lexically byte-wise.
+- Lists are comparable. Lists are equal if they are of equal length and their
+ corresponding elements are comparable and equal.
+- Maps are comparable. Maps are equal if they are of equal length and both their
+ corresponding keys and values are comparable and equal.
+
+If either operand is the `undefined` value, the result of the expression is the `undefined` value.
+
+### Emptiness Comparisons
+
+Checking the emptiness of an object can be achieved by using one of the emptiness expressions, `is empty` or `is not empty`. Although similar to [Comparison Operators](#comparison-operators) in that they yield a boolean value and read as though a comparison is taking place, both `is empty` and `is not empty` are evaluated as a multi-word expression.
+
+An emptiness comparison can only be performed against collections, strings or `undefined`, with the same rules as the [built-in length](/sentinel/functions/length) function.
+
+```sentinel
+"" is empty // true
+"foo" is empty // false
+[] is empty // true
+[1] is empty // false
+{} is empty // true
+{"a": "b"} is empty // false
+
+"" is not empty // false
+"foo" is not empty // true
+[] is not empty // false
+[1] is not empty // true
+{} is not empty // false
+{"a": "b"} is not empty // true
+
+undefined is empty // undefined
+undefined is not empty // undefined
+```
+
+### Logical Operators
+
+Logical operators apply to boolean values and yield a boolean result.
+
+Logical operators are evaluated left-to-right and perform short-circuit logic. The right-hand side is not guaranteed to be evaluated if a short-circuit can be performed.
+
+```plaintext
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+### Set Operators
+
+The set operators `contains` and `in` test for set inclusion for lists
+and maps, and substring inclusion for strings.
+
+Set operators may be negated by prefixing the operator with `not`: `not contains` and `not in`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+`contains` tests if the left-hand collection contains the right-hand value. For lists, this tests equality of any value. For maps, this tests equality of any key. For strings, this tests for substring existence.
+
+`in` tests if the right-hand collection contains the left-hand value. The behavior is equivalent to `contains`.
+
+The collection must be a list, map, or string. If it is the `undefined` value, the result is the `undefined` value. For any other value, the result is an error.
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+
+"test" contains "est" // true
+"test" contains "best" // false
+"test" in "testing" // true
+"best" in "testing" // false
+```
+
+### Matches Operator
+
+The matches operator tests if a string matches a regular expression.
+
+The matches operators may be negated by prefixing the operator with `not`: `not matches`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+The left-hand value is the string to test. The right-hand value is a string value representing a regular expression.
+
+The syntax of the regular expression is the same general syntax used by Python, Ruby, and other languages. More precisely, it is the syntax accepted by RE2. The regular expression is not anchored by default; any anchoring must be explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+If either operand is `undefined`, the result is the `undefined` value. For any other non-string value, the result is an error.
+
+### Else Operator
+
+Else operators can be used to provide a default value for an expression that may evaluate to the `undefined` value. Else operators return their left-hand value unless it is `undefined`, otherwise they return their right-hand value.
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else null
+```
+
+### Quantifier Expressions (any, all, filter, map)
+
+Quantifiers are expressions that apply a predicate to a collection (list or map). The purpose of the quantifier is to produce a result based on its particular type.
+
+`any` and `all` expressions are existential and universal quantifiers, respectively. `any` expressions are equivalent to a chain of `or` and `all` expressions are equivalent to a chain of `and`. Both expressions implement short-circuiting equivalent to logical operators.
+
+The `map` expression is an apply-to-all quantifier and returns a new list for all values in the input collection. The output list will be the same length as the input collection.
+
+The `filter` expression is a subset quantifier and returns the subset of all values in the input collection for which the supplied predicate applies. This will be zero or more elements, up to the length of the input.
+
+The body of a quantifier expression is a boolean expression.
+
+`any` returns the boolean value `true` if any value in the collection expression results in the body expression evaluating to `true`. If the body expression evaluates to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression result in the body expression evaluating to `true`. If any value in the collection expression result in the body expression evaluating to `false`, the all expression returns `false`.
+
+For empty collections, `any` returns false and `all` returns `true`.
+
+`map` always returns a list, regardless of if the input collection is a list itself; maps (as in the data type) also return lists when processed through `map`. Each element is the result of the supplied expression body.
+
+`filter` returns a list or map, the same type as its input collection. Only the elements for which the expression body evaluated to `true` will be returned. If an iteration of the expression body results in `undefined`, the entire result set is `undefined`.
+
+When the collection is a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When the collection is a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+QuantExpr = QuantOp Expression "as" [ identifier "," ] identifier "{" BoolExpr "}" .
+QuantOp = "all" | "any" | "filter" | "map" .
+```
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" } // true if every iterations is true
+any group.tasks as t { t.driver is "vmware" } // true if any iteration is true
+map group.tasks as t { { "driver": t.driver } } // [{"driver": "vmware"}, ...]
+filter group.tasks as t { t.driver is "vmware" } // subset of tasks that is true
+```
+
+## Statements
+
+### Expression Statements
+
+Function call expressions may exist in the statement context.
+
+```ebnf
+ExpressionStmt = Expression .
+```
+
+### Assignments
+
+Assignments set the value of an expression to the value of another expression.
+
+```ebnf
+Assignment = AssignExpr assign_op Expression .
+AssignExpr = identifier | IndexExpr .
+assign_op = [ add_op | mul_op ] "=" .
+```
+
+The assignment `x op= y` is equivalent to `x = x op (y)` for supported values of `op`.
+
+```sentinel
+a = 1
+b[12] = 42
+c["key"] = "value"
+
+a *= 12
+b[12] += 8
+```
+
+Assignments to lists and maps can be carried out using [index expressions](#index-expressions), with the operands of the index expression being evaluated alongside the right hand side expression in a right-to-left (RHS, LHS) order.
+
+In addition to the rules detailed in the section describing their use, the following rules apply when assigning to maps or lists using index expressions:
+
+- The [variable](#variables) must exist and be a list or map. Attempts to use an index assignment on an unknown variable, or a variable that not a list or map, results in a runtime error.
+- Assigning to a list or map index that already exists overwrites that index's data with evaluated right hand side's value.
+- Assigning to an unknown key in a map creates a key for that map with the evaluated right hand side's value assigned to that key.
+- Attempting to assign a value to a list index that is out of range results in a runtime error.
+
+### If Statements
+
+If statements specify the conditional execution of two branches according to the value of a boolean expression. If the expression evaluates to true, the "if" branch is executed, otherwise, if present, the "else" branch is executed.
+
+```ebnf
+IfStmt = "if" BoolExpr Block [ "else" ( IfStmt | Block ) ] .
+```
+
+```sentinel
+if x < y {
+ return x
+} else if x > z {
+ return z
+} else {
+ return y
+}
+```
+
+### Case Statements
+
+Case statements specify a selection control mechanism to conditionally execute a branch based on expression equality.
+
+Each clause that wishes to provide an expression must use the "when" keyword. Multiple expressions can be provided, separated with a comma (",").
+
+There can be one, optional, "else" clause within a `case` statement. The "else" clause is executed if all other clauses failed to run.
+
+The clauses are evaluated in the order they are listed, with the first successful evaluation being executed.
+
+A `case` statement can optionally provide an expression. If no expression is provided, it is the equivalent of writing `case true {`.
+
+```ebnf
+CaseStmt = "case" [ Expression ] "{" { CaseWhenClause } "}" .
+CaseWhenClause = CaseWhenCase ":" StatementList .
+CaseWhenCase = "when" Expression { "," Expression } | "else" .
+```
+
+```sentinel
+case x {
+when y, z:
+ return true
+else:
+ return false
+}
+
+case {
+when x > 42:
+ return true
+else:
+ return false
+}
+```
+
+### For Statements
+
+A `for` statement specifies repeated execution of a block.
+
+The expression must evaluate to a list or a map.
+
+When iterating over a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When iterating over a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+ForStmt = "for" Expressions "as" [ identifier "," ] identifier Block .
+```
+
+```sentinel
+for [1, 2, 3] as v { count += v } // basic sum
+
+for [1, 2, 3] as idx, v {
+ if idx > 1 { count += v }
+}
+
+data = { "a": 12, "b": 32 }
+for data as k { count += data[k] } // sum map values
+for data as k, v { count += v }
+```
+
+### Break Statements
+
+A `break` statement terminates execution of the innermost `for` statement
+within the same function.
+
+```ebnf
+BreakStmt = "break" .
+```
+
+```sentinel
+// Will only print "1"
+for [1, 2, 3] as v {
+ print(v)
+ break
+}
+```
+
+### Continue Statements
+
+A `continue` statement begins the next iteration of the innermost `for` loop.
+The `for` loop must be within the same function.
+
+```ebnf
+ContinueStmt = "continue" .
+```
+
+```sentinel
+// Will print 1, 3
+for [1, 2, 3] as v {
+ if v == 2 {
+ continue
+ }
+
+ print(v)
+ break
+}
+```
+
+### Return Statements
+
+A return statement in a function F terminates the execution of F and provides the result value.
+
+```ebnf
+ReturnStmt = "return" Expression .
+```
+
+A function must terminate with a return statement.
+
+The return statement can be invoked at any time during a function to terminate execution.
+
+## Imports
+
+An import adds an assignment to the global scope to external definitions.
+
+The import declarations must only appear at the top of the source file, before any other statements. Comments may appear before imports.
+
+```ebnf
+ImportDecl = "import" Name ["as" identifier] .
+Name = string_lit .
+```
+
+An import name and identifier must be distinct. Two names may not repeated even if they're declared with different identifiers.
+
+If an identifier is not specified, the identifier is the name of the import itself.
+
+An import is not a valid value. The identifier representing the import may not be used as an expression, such as in assignment statements or call expressions.
+
+Values in imports are not assignable directly via their selectors. Function calls may be used to alter the state of the external data represented by the import. Whether or not this is supported is specific to the import implementation. The exception to this assignment restriction is the memoized data returned by a call to an import (see below).
+
+Data returned by imports can be memoized, meaning that data is returned by the import in the form of a map which is then used as much as possible without having to return to the import for more data. For example, `t = time.now` returns a map so that `t.hour` will be able to be looked up without having to go back to the import for that data.
+
+Data returned by imports may be callable, meaning that methods may be called on the memoized data returned by an import. For example, given `t = time.now`, `t.after(some_previous_time)` is valid, with the receiver data being set to the local memoized data stored in `t`. It is a valid case to accept modifications to the receiver data (example: assignment to the memoized data via index expressions), as long as all data in in the receiver continues to be string-keyed. It is an invalid case to accept receiver data with non-string-typed keys. Methods may also alter the contents of receiver data so that it is different after the call is finished.
+
+As with methods, imports may also have callable keys where the data may not be memoized. Example: in the event of `v = foo.bar`, if `v.baz` is not included in the memoized data, a call will be made to the import to fetch it. The same rules and restrictions to receiver data apply to callable keys as do to method calls.
+
+The support for callable methods and non-memoized keys on data returned by imports is specific to the import implementation.
+
+The handling of import loading is specific to the runtime implementation.
+
+Implicit imports may be added by the runtime, for example for standard library definitions. An explicit import of the same name should override a matching implicit import. For example, if "time" is an implicit import and the program specifies `import "time" as cal`, then only `cal` is defined.
+
+```sentinel
+import "time"
+import "math" as m
+```
+
+## Parameters
+
+```ebnf
+ParamDecl = "param" identifier [ "default" Expression ]
+```
+
+A parameter is a way for a policy to describe variables that are expected to be supplied by the calling application, in addition to any default value.
+
+Parameter declarations must appear at the top of the source file, following imports.
+
+Like imports, comments may appear before parameters; this is mainly pertinent when the policy is not importing anything, which would make parameters the first declarations that appear in a policy.
+
+A parameter declaration describes a valid variable that is added to the scope of a policy at runtime. This variable has the same properties and rules as other variables assigned during the course of policy evaluation as defined by the specification. This includes having a dynamic type and being able to be re-assigned during execution.
+
+Parameters may only be strings, integers, floating point numbers, booleans, lists, or maps. More complex types such as rules or functions are not allowed.
+
+A parameter can specify a default value by adding one in the declaration via use of the "default" keyword, and supplying a literal for the default value. The literal for a default is a very limited expression, comprising of:
+
+- For strings, a simple string literal;
+- For integers and floating-point numbers, either a literal denoting the value, or a unary expression with the literal, and the operators - or +;
+- For booleans, the pre-declared identifiers true or false;
+- For lists and maps, their respective literals composed of values and keys (for maps) of the above values only.
+
+Any other type of expression or identifier is not allowed.
+
+A parameter that does not have a default value defined is required by the policy. Evaluating a policy with a required parameter that has not been supplied at execution results in a runtime error.
+
+Parameters must not conflict with imports or any other identifiers currently defined in the global scope, including any reserved or pre-declared identifiers. Attempting to assign a parameter to an existing value results in a runtime error.
+
+```sentinel
+import "time"
+
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+param time default "11:00" // error, conflicts with "time" import
+param undefined // error, reserved identifier
+```
+
+## Built-in Functions
+
+Built-in functions are predeclared. They are called like any other function.
+
+### Length
+
+The built-in function `length` returns the length of a collection of string.
+
+The length of a string is the number of bytes in the string. It is not necessarilly the number of characters.
+
+The length of a collection is the number of elements in that collection.
+
+The length of `undefined` is `undefined`.
+
+### Collections
+
+#### List Append
+
+The built-in function `append` appends a value to the end of a list in-place.
+
+Appending to a non-list value results in an immediate `fail`.
+
+The return value of `append` is always the `undefined` value.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+#### Map Delete
+
+The built-in function `delete` deletes elements from a map by key. The map is modified in-place.
+
+Deleting a key that does not exist does nothing.
+
+Calling delete for a non-map value results in an immediate `fail`.
+
+The return value of `delete` is always the `undefined` value.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
+
+#### Keys and Values
+
+The built-in function `keys` and `values` return the keys and values of a map, respectively. The return value is a list. Both functions return values in an unspecified order.
+
+The `keys` or `values` of `undefined` is `undefined`.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+### Range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it defaults to `1`.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4]
+range(1, 5, 2) // [1,3]
+range(0, -3, -1) // [0,-1,-2]
+```
+
+### Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a value of that type according to the rules below.
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
+
+### Printing
+
+The built-in function `print` can be used to output formatted debug information. The runtime may omit print function calls depending on its execution mode. The runtime may choose where the output of a print function goes.
+
+`print` is a variadic function, accepting one or more values to be printed; values are separated by a single whitespace character (`" "`).
+
+The return value of `print` is always `true` to allow it to be used in boolean expressions.
+
+```sentinel
+print("hello") // "hello"
+print("hello", "world") // "hello world"
+print("The", "number", "is", 42) // "The number is 42"
+print([1, 2, 3]) // "[1, 2, 3]"
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // "false"
+```
+
+### Errors
+
+The built-in function `error` is equivalent to `print` but immediately causes execution to halt and the main rule to evaluate to `false`. The program execution is considered to have failed.
+
+## Program Execution
+
+Program execution begins by evaluating the source top to bottom. If evaluation is successful, the `main` rule is evaluated and its result is returned. Execution can be interrupted at any time by an error, which can be called explicitly or implicitly through illegal or undefined behavior.
+
+---
+
+> The content of this page is licensed under the [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
+>
+> Based on [The Go Programming Language Specification](https://golang.org/ref/spec) licensed under [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/language/undefined.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/language/undefined.mdx
new file mode 100644
index 0000000000..dc51531783
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/language/undefined.mdx
@@ -0,0 +1,97 @@
+---
+page_title: Sentinel Language - Undefined
+sidebar_current: docs-language-undefined
+description: >-
+ The value `undefined` is a special value representing undefined behavior or an
+ unknown value. Any operation on an `undefined` value results in `undefined`.
+layout: docs
+---
+
+# Language: Undefined
+
+The value `undefined` is a special value representing undefined behavior
+or an unknown value.
+
+Undefined is not an error because there are situations where the undefined
+value can be safely ignored and the language also provides construts for
+recovering from an undefined value.
+
+The undefined value can be created manually with `undefined`. In most cases,
+undefined is created by accessing a non-existent list element or value.
+The undefined value is created in a number of other circumstances. The documentation
+will note when an undefined value may be created.
+
+Almost any operation with `undefined` results in `undefined`. For example,
+performing math on undefined, attempting to call a function that is undefined,
+etc.
+
+## Main and Undefined
+
+If the value `undefined` is returned from the top-level `main` rule, then
+the policy is considered `false`. However, the runtime provides mechanisms
+for the host application to determine the policy failure was caused by
+an undefined value and report that to the user.
+
+The `main` rule returning the undefined value should be considered a logical
+error in most cases and should probably be corrected by the policy writer.
+
+## Debugging Undefined
+
+When an `undefined` value is first created (either explicitly or implicitly),
+the runtime will record the position where the undefined was created. This
+location can be used to find logic that could be erroneous.
+
+## Boolean Logic and Undefined
+
+Boolean logic is one way to safely recover from `undefined`. If boolean
+logic can safely determine a result despite an undefined value, that result
+will be returned.
+
+For example: `undefined or true` will result in `true`, because no matter
+what actual value `undefined` could've been if the policy behaved properly,
+the boolean expression would still be true.
+
+Another scenario where `undefined` can be safely ignored is short-circuit
+logic with `and`. `false and undefined` will result in `false`.
+
+The full table of logical operations on `undefined` is below:
+
+```sentinel
+undefined or true = true
+undefined or false = undefined
+undefined or undefined = undefined
+undefined and true = undefined
+undefined and false = undefined
+undefined and undefined = undefined
+undefined xor true = undefined
+undefined xor false = undefined
+undefined xor undefined = undefined
+
+// Short-circuit examples
+false or true or undefined = true
+false or undefined or true = true
+true and false and undefined = false
+true and undefined and false = undefined
+```
+
+## Else Expressions
+
+The `else` expression allows explicit recovery from undefined and is useful
+for setting default values.
+
+`else` is an operator where the left and right hand side are values. If the
+left-hand value is `undefined`, the right-hand value is returned. Otherwise,
+the left-hand value is returned.
+
+Examples:
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else "default"
+
+// In more complex scenarios
+
+foo() else 42 == 12
+a = config.value else "default"
+```
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/language/values.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/language/values.mdx
new file mode 100644
index 0000000000..f932480340
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/language/values.mdx
@@ -0,0 +1,210 @@
+---
+page_title: Sentinel Language - Values
+sidebar_current: docs-language-values
+description: Values are the data that Sentinel policies operate on.
+layout: docs
+---
+
+# Language: Values
+
+Values are the _data_ that Sentinel policies operate on. You can create this
+data yourself, for example by just typing the number `42`, or you may access
+this data from an external source to make policy decisions.
+
+Values have a _type_, which determines what kind of operations can be performed
+on the data. Example types are booleans (true and false), numbers,
+and strings (text).
+
+This page documents all the available value types. You can also
+[convert between types](#type-conversion).
+
+## Boolean
+
+A boolean is a value that is either true or false.
+
+A boolean value is created literally with `true` or `false`.
+
+As a policy language, booleans are central to the behavior of Sentinel.
+Booleans are used as conditions in [if statements](/sentinel/language/conditionals),
+are the result of [rules](/sentinel/language/rules), and more. The ultimate
+result of a Sentinel policy is true or false.
+
+## Integer
+
+An integer is a whole number.
+
+An integer is a 64-bit value. This means it can represent numbers from
+-9,223,372,036,854,775,808 to 9,223,372,036,854,775,808.
+
+Integers are created by typing them out literally with no
+separators (such as a comma). An optional prefix can set a non-decimal base:
+`0` for octal, and `0x` for hexadecimal. In hexadecimal literals, letters
+`a-f` and `A-F` represent values 10 to 15.
+
+Example integers are shown below:
+
+```sentinel
+42
+0600
+0xBadFace
+170141183460469
+```
+
+Integers are used for math and numerical comparison.
+
+## Float
+
+A float is a number with a decimal point. It has an integer part, a decimal
+point, a fractional part, and optionally an exponent part. Example
+floats are shown below:
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+Floats are IEEE-754 64-bit floating point numbers.
+
+## String
+
+Strings are text values.
+
+Strings are created by wrapping text in double quotes, such as `"hello"`.
+Within the quotes, any character may appear except newline and an unescaped
+double quote. The text between the quotes is the value.
+
+Because Sentinel policies must be UTF-8 encoded text, strings themselves are
+UTF-8 encoded text. This means you can put any value UTF-8 character into
+a string, such as `"日本語"`.
+
+You can have a string with a literal double-quote by _escaping_ it with
+a backslash. For example: `"they said \"hello\""` turns into the value
+`they said "hello"`.
+
+Backslash escapes can be used for much more than only escaping a double quote.
+Newlines are `\n`, a literal backslash is `\\`, and many more. The full list
+of available backslash escapes can be found
+[in the language specification](/sentinel/language/spec#string-literals).
+
+Example strings:
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+Strings do not support indexing, however it is possible to achieve a similar
+result through the combination of the [strings](/sentinel/imports/strings)
+standard import and list indexing.
+
+```sentinel playground
+import "strings"
+
+asciistr = "Hello, world!"
+utf8str = "日本語"
+
+utf8split = rule {
+ strings.split(utf8str, "")[2] == "語"
+}
+
+asciisplit = rule {
+ strings.split(asciistr, "")[5] == ","
+}
+
+main = rule { utf8split and asciisplit }
+```
+
+Strings support [slicing](/sentinel/language/slices) to efficiently create
+substrings.
+
+## List
+
+See [Lists](/sentinel/language/lists).
+
+## Map
+
+See [Maps](/sentinel/language/maps).
+
+## Rule
+
+See [Rules](/sentinel/language/rules).
+
+## Function
+
+See [Functions](/sentinel/language/functions).
+
+## Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a
+value of that type. Some examples are shown below followed by a list of exact
+rules for type conversion.
+
+```sentinel
+int(42) // 42
+int("42") // 42
+int(42.8) // 42
+int(true) // 1
+
+float(1.2) // 1.2
+float(1) // 1.0
+float("4.2") // 4.2
+float(true) // 1.0
+
+string("foo") // "foo"
+string(88) // "88"
+string(0xF) // "15"
+string(true) // "true"
+
+bool("true") // true
+bool(1) // true
+bool(-1) // true
+bool(0.1) // true
+bool("false") // false
+bool(0) // false
+```
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/language/variables.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/language/variables.mdx
new file mode 100644
index 0000000000..7516890212
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/language/variables.mdx
@@ -0,0 +1,99 @@
+---
+page_title: Sentinel Language - Variables
+sidebar_current: docs-language-vars
+description: Variables store values that can be used later.
+layout: docs
+---
+
+# Language: Variables
+
+Variables store values that can be used later.
+
+Most notably, a variable assignment of `main` is required for all
+Sentinel policies. This is the main [rule](/sentinel/language/rules) that
+is executed to determine the result of a policy. The `main` rule may
+use other variables that are assigned in the policy.
+
+Example:
+
+```sentinel
+a = 1
+b = a + 1
+```
+
+In this example, two variables are assigned: `a` and `b`. `a` is given
+the value of "1" and `b` uses the `a` to calculate its own value.
+
+## Syntax
+
+The syntax for assigning a variable is:
+
+```sentinel
+IDENTIFIER = VALUE
+```
+
+On the left of the equal sign is an _identifier_. This is a name for your
+variable and will be how you reference it later. An identifier is any
+combination of letters and digits, but must start with a letter. An
+underscore `_` is also a valid letter.
+
+On the right is any valid Sentinel value or expression. A value is a
+literal value such as a number or string. An expression is some computed
+value such as doing math, calling a function, etc.
+
+## Assignment and Reassignment
+
+A variable is _assigned_ when it is given a value. You can also _reassign_
+variables at any time by setting it to a new value. The new value takes
+effect for any subsequent use of that variable. A variable can be reassigned
+to a different type.
+
+For example:
+
+```sentinel
+a = 1 // a = 1
+b = a // b = 1
+a = "value" // a = "value", b = 1
+c = a // c = "value", b = 1
+```
+
+In the above example you can see that the variables are set and reassigned.
+Notice that the value of a variable is the _current_ value, and that reassigning
+a variable only affects _future_ uses of that variable. You can see this with
+`c` and `b` being different values.
+
+## Unassigned Variables
+
+Using a variable that is _unassigned_ is an error.
+
+In the example below, the first line would result in the policy erroring.
+Sentinel is executed top-down and the value of `c` is not available yet
+on the first line.
+
+```sentinel
+a = c // Error!
+c = 1
+```
+
+## Type Conversion
+
+While a topic more related to [values][lang-values], variables can be assigned
+(or-reassigned) a different value type via type conversion.
+
+[lang-values]: /sentinel/language/values
+
+```sentinel
+s = "1.1"
+a = int(s) // a = 1
+a = float(s) // a = 1.1
+
+a = 1
+s = string(a) // s = "1"
+```
+
+For more details, see the type conversion sections in the
+[values][lang-values-type-conversion] page, or the [Sentinel Language
+Specification][lang-spec-type-conversion].
+
+[lang-values-type-conversion]: /sentinel/language/values#type-conversion
+[lang-spec-type-conversion]: /sentinel/language/spec#type-conversion
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/nomad.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/nomad.mdx
new file mode 100644
index 0000000000..a105b0337d
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/nomad.mdx
@@ -0,0 +1,51 @@
+---
+page_title: Nomad and Sentinel
+sidebar_current: docs-app-nomad
+description: >-
+ Nomad Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies can currently execute on job
+ submission (creation, update).
+layout: docs
+---
+
+# Nomad
+
+Nomad is a simple, flexible scheduler and workload orchestrator.
+[Nomad Enterprise](https://www.hashicorp.com/products/nomad/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies can currently execute on job submission (creation, update).
+
+Sentinel policies have full access to the job structure. This allows the
+Sentinel policy to control behavior based on any attribute within a job,
+such as the driver, resource requests, network configuration, volume
+configuration, and more. The information that Sentinel policies have access
+to will expand over time.
+
+Nomad fully supports all [enforcement levels](/sentinel/concepts/enforcement-levels).
+For soft mandatory policies, the `sentinel-override` capability must be
+available on the user's ACL policy to allow override. Overrides are always
+logged.
+
+The Nomad integration with Sentinel is documented in depth in the
+[Nomad Enterprise documentation](https://www.nomadproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Only allow Docker-based jobs.**
+
+```sentinel
+# Test policy only allows Docker based tasks
+main = rule { all_drivers_docker }
+
+# all_drivers_docker checks that all the drivers in use are Docker
+
+all_drivers_docker = rule {
+ all job.task_groups as tg {
+ all tg.tasks as task {
+ task.driver is "docker"
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/terraform.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/terraform.mdx
new file mode 100644
index 0000000000..5922fd0cfb
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/terraform.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Terraform and Sentinel
+sidebar_current: docs-app-terraform
+description: >-
+ Sentinel can be used with Terraform and Terraform Enterprise to enforce policy
+ on Terraform configurations, states, and plans.
+layout: docs
+---
+
+# Terraform
+
+[Terraform Enterprise](https://www.hashicorp.com/products/terraform/) uses Sentinel to enforce
+policy on [Terraform](https://www.terraform.io) configurations, states, and plans.
+
+The Sentinel integration with Terraform runs within
+[Terraform Enterprise](https://www.hashicorp.com/products/terraform/)
+after a `terraform plan` and before a `terraform apply`. The policies
+have access to the created plan, the state at the time of the plan,
+and the configuration at the time of the plan.
+
+The Terraform integration with Sentinel is documented in depth in the [Terraform Enterprise documentation](https://www.terraform.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show basic examples.
+
+### Examples
+
+**Example: All AWS instances must have a tag**
+
+```sentinel
+import "tfplan"
+
+main = rule {
+ all tfplan.resources.aws*instance as *, instances {
+ all instances as \_, r {
+ (length(r.applied.tags) else 0) > 0
+ }
+ }
+}
+```
+
+**Example: Only allow GCP instance sizes smaller than `n1-standard-16`**
+
+```sentinel
+import "tfplan"
+
+allowed_machine_types = [
+ "n1-standard-1",
+ "n1-standard-2",
+ "n1-standard-4",
+ "n1-standard-8",
+]
+
+main = rule {
+ all tfplan.resources.google_compute_instance as _, instances {
+ all instances as _, r {
+ r.applied.machine_type in allowed_machine_types
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/vault.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/vault.mdx
new file mode 100644
index 0000000000..6db2f8af22
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/vault.mdx
@@ -0,0 +1,64 @@
+---
+page_title: Vault and Sentinel
+sidebar_current: docs-app-vault
+description: >-
+ Vault Enterprise uses Sentinel to augment the built-in policy system to
+ provide Role Governing Policies (RGPs) and Endpoint Governing Policies (EGPs)
+ to enable complex, flexible policies across identities and endpoints.
+layout: docs
+---
+
+# Vault
+
+[Vault Enterprise](https://www.hashicorp.com/products/vault/) uses Sentinel
+to augment the built-in policy system to provide Role Governing Policies (RGPs)
+and Endpoint Governing Policies (EGPs) to enable complex, flexible policies
+across identities and endpoints.
+
+**Role Governing Policies (RGPs)** are Sentinel policies that are tied to
+particular tokens, Identity entities, or Identity groups. They have access to
+a rich set of controls across various aspects of Vault. These are evaluated
+whenever a token they're attached to is used.
+
+**Endpoint Governing Policies (EGPs)** are Sentinel policies that are tied to
+particular paths instead of tokens. They have access to as much request
+information as possible, but they can take effect even on unauthenticated
+paths, such as login paths.
+
+The Vault integration with Sentinel is documented in depth in the
+[Vault Enterprise documentation](https://www.vaultproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Endpoint policy that requires MFA authentication from a corporate network.**
+
+```sentinel
+import "sockaddr"
+
+# We expect logins to come only from our private IP range
+cidrcheck = rule {
+ sockaddr.is_contained(request.connection.remote_addr, "10.20.0.0/16")
+}
+
+# Require Ping MFA validation to succeed
+ping_valid = rule {
+ mfa.methods.ping.valid
+}
+
+main = rule when request.path is "auth/ldap/login" {
+ ping_valid and cidrcheck
+}
+```
+
+**Example: Endpoint policy that disallows tokens created before a certain time.**
+
+```sentinel
+import "time"
+import "strings"
+
+main = rule when not strings.has_prefix(request.path, "auth/ldap/login") {
+ time.load(token.creation_time).unix > time.load("2017-09-17T13:25:29Z").unix
+}
+```
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/writing/basic.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/writing/basic.mdx
new file mode 100644
index 0000000000..b62533466e
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/writing/basic.mdx
@@ -0,0 +1,113 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-basic
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Policy Basics
+
+Sentinel policies are easy to write while still supporting advanced constructs
+for creating complex policies. This page will explain the basics of writing
+Sentinel policies to get started. You don't need any prior Sentinel knowledge,
+but we do recommend reading the
+[getting started guide](/sentinel/intro/getting-started/first-policy) and
+[language guide](/sentinel/language) after this.
+
+Sentinel policies are text files written using the [Sentinel language](/sentinel/language).
+The policies are evaluated top-to-bottom. The value of `main` after execution
+determines whether a policy passes or fails.
+
+## The Simplest Policy
+
+Sentinel only requires that a policy have a `main` variable that evaluates to
+a boolean value.
+
+A valid example is shown below:
+
+```sentinel playground
+main = 10 > 5
+```
+
+This type of minimal policy is not purely academic. In practice, simple
+policies can often be reduced to a single line logical statement resulting
+in `true` or `false`. However, the expression is usually wrapped in a
+[rule](/sentinel/language/rules) for testing reasons.
+
+You can verify Sentinel will execute this minimal policy using the CLI:
+
+```
+$ sentinel apply minimal.sentinel
+Pass
+```
+
+## Logical Expressions
+
+Policy is at its core a set of logic: you can or can not perform some action
+under a certain set of circumstances. Those circumstances are logical
+expressions. Therefore, Sentinel policies primarily translate into logical
+expressions.
+
+Detailed documentation on boolean expressions is
+[available in the language guide](/sentinel/language/boolexpr).
+
+A simple numerical comparison was seen in the first example on this page.
+Sentinel also provides inclusion operators such as `contains`, `any`, `all`, and
+more. Sentinel allows some operators to have aliases to promote readability
+while remaining programmer-familiar, such as `==` which can equivalently be `is`.
+
+The example below verifies that all numbers in a list are even:
+
+```sentinel playground
+numbers = [6, 22, 8, 4, 12]
+main = all numbers as n { n % 2 is 0 }
+```
+
+## Variables
+
+A policy will very often use variables. Applications such as
+[Nomad](https://www.nomadproject.io) inject variables into the global
+scope of a policy for making policy decisions. For example, Nomad injects
+the job that is being run into the policy scope. Knowing how to use
+variables is critical to effectively using Sentinel.
+
+Detailed documentation on how to define and access variables is
+[available in the language guide](/sentinel/language/variables).
+
+Variables can be defined and used explicitly. For example:
+
+```sentinel playground
+value = 10
+main = value > 5
+```
+
+But they may also be introduced implictly by the host system. Nomad
+injects `job` into policies to describe the job that is being run. The
+policy below is a valid policy that requires a job have two task groups.
+Notice that `job` is not defined anywhere. It is implicitly inserted by
+the host application (in this case Nomad). Refer to the application you're
+writing policy for to determine if it implicitly inserts values.
+
+```json sentinel
+{
+ "policy": "main = length(job.task_groups) is 2",
+ "globals": {
+ "job": {
+ "task_groups": ["foo", "bar"]
+ }
+ }
+}
+```
+
+## And more!
+
+The Sentinel language supports many more features such as functions,
+loops, and more. You can learn about all of this in the
+[complete language guide](/sentinel/language).
+
+The other pages in the [writing policy](/sentinel/writing)
+will cover other information you need to know about writing Sentinel
+policies that isn't simply a language reference.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/writing/debugging.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/writing/debugging.mdx
new file mode 100644
index 0000000000..7dc284d222
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/writing/debugging.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-debugging
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Debugging
+
+As policies become more complex, it becomes harder to understand exactly why
+a policy may be behaving differently than you expect.
+
+Prior to actively debugging, there are multiple best practices we recommend
+following. We recommend breaking your policy down into a set of
+[rules](/sentinel/writing/rules). This will make it easier to understand
+[traces](/sentinel/writing/tracing) and make it easier to
+[test](/sentinel/writing/testing) your policies.
+This should help to debug policies up to a significant complexity.
+
+## Print Logging
+
+The built-in [print function](/sentinel/language/logging-errors#print)
+can be used to log data. The print output is only shown during failures
+or when [tracing is explicitly enabled](/sentinel/writing/tracing).
+
+The `print` function outputs each argument, which can be of any type, converted
+to a human-friendly string format. Arguments are separated with a single space.
+
+Example:
+
+```sentinel playground
+value = 42
+print("the number is", value) // the number is 42
+
+object = { "foo": false }
+print(object) // { "foo": false }
+
+main = rule { true }
+```
+
+The `print` function returns the value `true` so that it can also be
+used within rule expressions. Note that the `print` function should be
+used at the beginning of statements otherwise they may never be
+reached. This is due to internal performance techniques within Sentinel
+like [short-circuiting](https://en.wikipedia.org/wiki/Short-circuit_evaluation).
+
+Example:
+
+```sentinel playground
+// rule_a and rule_b will evaluate to false
+rule_a = rule { print("rule_a is printed") and false } // "rule_a is printed"
+rule_b = rule { false and print("rule_b is printed") } // Nothing is printed
+
+// rule_c and rule_d will evaluate to true
+rule_c = rule { print("rule_c is printed") or true } // "rule_c is printed"
+rule_d = rule { true or print("rule_d is printed") } // Nothing is printed
+
+main = rule { rule_a or rule_b or rule_c and rule_d }
+```
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/writing/imports.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/writing/imports.mdx
new file mode 100644
index 0000000000..74ac751ee4
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/writing/imports.mdx
@@ -0,0 +1,117 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom import](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only local
+context for making policy decisions.
+
+Sentinel also comes with a set of [standard imports](/sentinel/imports).
+Standard imports are available to every Sentinel policy to help policy writers
+with common tasks such as working with the time, network addresses, and more.
+
+-> **This page is about writing policies that _use_ imports.** If you're
+interested in _creating_ a new import, please see the section on [extending
+Sentinel](/sentinel/extending) for information on how to write modules and
+import plugins.
+
+## Using Imports
+
+To use an import, you use the `import` keyword at the top of your policy. This
+specifies the name of the import you want to use. The application you're writing
+the policy for must already be
+[configured](/sentinel/configuration#imports) to provide that
+import.
+
+Details on imports can be found in the
+[import section in the language reference](/sentinel/language/imports).
+
+In the example below, we use the [time](/sentinel/imports/time) import:
+
+```sentinel playground
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+There are two options to develop a policy locally when using imports:
+configure Sentinel to launch the import on `apply`, or mock the import
+using `mock`. The former requires access to the import while the
+latter is faster (doesn't have to launch a process for plugins) and
+doesn't require the import.
+
+## Mocking Imports
+
+The first option to developing policies locally is to mock the import values.
+When mocking an import, you don't need the import to be available. This can be
+useful since some imports may not be available as a plugin and may only be
+available to the application the policy runs in.
+
+Mocks are specified via the [configuration
+file](/sentinel/configuration). Mocks can also be used for
+[testing](/sentinel/writing/testing).
+
+You can supply mock configuration one of two ways, depending on your use case:
+
+- **[Using static data](/sentinel/configuration#mocking-static-data):**
+ Use this method when you can accurately represent your mock data in JSON and
+ do not need to mock complex Sentinel features such as functions.
+- **[Using Sentinel code](/sentinel/configuration#mocking-with-sentinel-code):**
+ Use this method when using a static JSON object is insufficient, such as when
+ you need to mock functions or other complex Sentinel features.
+
+Our example does not require complex data to be mocked, so a static object
+is sufficient:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ hour = 12
+ weekday_name = "Tuesday"
+ }
+ }
+}
+```
+
+This can be used via the CLI:
+
+```
+$ sentinel apply -config=config.hcl policy.sentinel
+Pass
+```
+
+## Launching Import Plugins
+
+If you have access to the plugin binary, you can launch the import. The
+benefit of this is that it is really using the import to test your
+policy. If the import changes, your policies may start failing. If you
+only use mock data and the import changes, your policies will still
+appear to work.
+
+Imports are configured in the configuration file:
+
+```hcl
+import "plugin" "custom_time" {
+ source = "/path/to/sentinel-time-import"
+}
+```
+
+This would require the `sentinel-time-import` binary. For this example
+this doesn't currently exist. We plan on writing one to provide for this
+section of the documentation.
+
+## Custom Imports
+
+You can also [create your own imports](/sentinel/extending).
+
+If your policy decisions could benefit from accessing external information,
+then you can use custom imports as a way to do this.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/writing/index.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/writing/index.mdx
new file mode 100644
index 0000000000..064069d389
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/writing/index.mdx
@@ -0,0 +1,35 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing
+description: >-
+ Sentinel provides a language and workflow for building policy across any
+ system that embeds Sentinel. By learning Sentinel once, you are able to
+ effectively control access to many systems using Sentinel's powerful features.
+ Basic concepts that are important to understand for Vault usage.
+layout: docs
+---
+
+# Writing Sentinel Policy
+
+This section covers how to write Sentinel policies.
+
+It is recommended that you complete the
+[getting started guide](/sentinel/intro/getting-started/first-policy) prior to
+reading this section of the documentation. The getting started guide will
+explain all the basics of writing and testing policies. This section of the
+documentation will serve as more of a reference guide to all available
+features of Sentinel.
+
+Sentinel provides a language and workflow for building policy across any system
+that embeds Sentinel. By learning Sentinel once, you are able to effectively
+control access to many systems using Sentinel's powerful features.
+
+Sentinel also provides a [local CLI](/sentinel/commands) for developing and testing
+Sentinel policies. This CLI can be integrated into a daily developer's workflow
+along with CI to treat [policy as code](/sentinel/concepts/policy-as-code).
+
+Sentinel uses its own [language](/sentinel/language) for writing
+policies. You can view a [language reference](/sentinel/language)
+as well as the [specification](/sentinel/language/spec) for details. You
+don't have to read those documents immediately, since the language should
+be easy enough to pick up throughout this section.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/writing/rules.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/writing/rules.mdx
new file mode 100644
index 0000000000..ab4fc69d08
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/writing/rules.mdx
@@ -0,0 +1,65 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-rules
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Rules
+
+Rules form the basis of a policy by representing behavior that is either passing
+or failing. Rules are a first class language construct in Sentinel. A policy can
+and should be broken down into rules to aid with readability, testability, and
+performance.
+
+Rules provide:
+
+- **Readability:** A policy that is broken down into rules can be read
+ more easily. The logic becomes clearer to see for policy writers when
+ they come back to it.
+
+- **Reportability:** When [tracing](/sentinel/writing/tracing) is enabled,
+ rules form the foundation of the data returned. All evaluated rules are added
+ to the trace with the data the rule evaluated to, and their position within
+ policy or module source files.
+
+- **Testability:** [Policy testing](/sentinel/writing/testing) is
+ built on asserting the values of rules. By breaking logic down into
+ rules, you're able to more effectively test your policies.
+
+- **Performance:** Rules are only evaluated on demand, and are also only
+ evaluated once. This means that a rule referenced multiple times only has a
+ one-time performance cost. For complex logic, this could result in improved
+ performance.
+
+An example usage of rules is shown below:
+
+```json sentinel
+{
+ "policy": "is_sunny = rule { weather is \"sunny\" }\nis_wednesday = rule { day is \"wednesday\" }\nmain = rule { is_sunny and is_wednesday }",
+ "globals": {
+ "weather": "sunny",
+ "day": "wednesday"
+ }
+}
+```
+
+Rules can also contain non-boolean data. This can be useful for passing more
+detail to the caller than an opaque boolean value would be able to communicate.
+
+```sentinel playground
+days = [{"weather": "sunny"}]
+main = rule {
+ filter days as d {
+ d.weather is not "sunny"
+ }
+}
+```
+
+Details about rules and their behavior can be found in
+[the language reference](/sentinel/language/rules). Rules have important
+behavior so we recommend reading the language reference on rules.
+Rules will be used throughout the remainder of the documentation.
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/writing/testing.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/writing/testing.mdx
new file mode 100644
index 0000000000..61ab0ea46c
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/writing/testing.mdx
@@ -0,0 +1,491 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-testing
+description: >-
+ Sentinel has a built-in test framework to validate a policy behaves as
+ expected.
+layout: docs
+---
+
+# Testing
+
+Sentinel has a built-in test framework to validate a policy behaves as expected.
+
+With an ever-increasing amount of automation surrounding technology,
+the guardrails provided by policies are a critical piece towards ensuring
+expected behavior. As a reliance on correct policy increases, it is important
+to test and verify policies.
+
+Testing is a necessary step to fully realize
+[policy as code](/sentinel/concepts/policy-as-code). Just as good software
+is well tested, a good set of policies should be equally well tested.
+
+## Test Folder Structure
+
+Policies are tested by asserting that [rules](/sentinel/writing/rules)
+are the expected values given a pre-configured input. Tests are run
+by executing the [test command](/sentinel/commands/test).
+
+Sentinel is opinionated about the folder structure required for tests.
+This opinionated structure allows testing to be as simple as running
+`sentinel test` with no arguments. Additionally, it becomes simple to test
+in a CI or add new policies.
+
+The structure Sentinel expects is `test//*.[hcl|json]` where ``
+is the name of your policy file without the file extension. Within that folder
+is a list of HCL or JSON files. Each file represents a single test case.
+Therefore, each policy can have multiple tests associated with it.
+
+-> Note that the primary configuration file format for Sentinel is HCL. While
+you can write configuration files in a HCL-equivalent JSON, we only discuss the
+use of HCL on this page.
+
+## Test Case Format
+
+Each HCL file within the test folder for a policy is a single test case.
+
+The file is the same configuration format as the [CLI configuration
+file](/sentinel/configuration). The format lets you define mock data, imports to
+use, and more. This mock data is the key piece in being able to test policies:
+you craft a specific scenario and assert your policy behaves as you expect.
+
+Test cases also use the `test` block within the configuration file to assert the
+value of [rules](/sentinel/writing/rules). If the `test` key is omitted, the
+policy is expected to pass. If the test key is specified, only the rules
+specified in the map will be asserted. This means if you omit `main`, then the
+final policy result is not asserted.
+
+Example with assertions:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+
+test {
+ rules = {
+ main = false
+ is_open_hours = false
+ is_weekday = true
+ }
+}
+```
+
+The configuration above specifies some parameter data, and asserts the result of
+some rules. This is the same configuration used in the example section below.
+
+## Example
+
+Lets use the following file as an example. Save this file to a directory
+and name it `policy.sentinel`. It can be named anything with the `sentinel`
+extension, but by naming it `policy.sentinel` your output should match
+the example output on this page.
+
+```sentinel
+// The day of the week.
+param day
+
+// The hour of the day.
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+### A Passing Test
+
+Next, let's define a single test case. Relative to where you saved
+the policy, create a file at the path `test/policy/good.hcl`.
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 14
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/good.hcl
+```
+
+This passed because the policy passed. We didn't assert any specific
+rules. By not specifying any assertions, `test` expects the policy itself
+to fully pass.
+
+### A Failing Test
+
+Define another test case to fail. We want to verify our policy fails when expected, too.
+
+Save the following as `test/policy/7-am.hcl`:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+FAIL - policy.sentinel
+ FAIL - test/policy/7-am.hcl
+ expected "main" to be true, got: false
+
+ trace:
+ policy.sentinel:9:1 - Rule "main"
+ bool: false
+
+ policy.sentinel:8:1 - Rule "is_open_hours"
+ bool: false
+
+ PASS - test/policy/good.hcl
+```
+
+As you can see, the test fails because "main" is false. This is good
+because the policy _should_ have failed since we specified an invalid
+hour. But, we expect main to be false and don't want our test to fail!
+Update `7-am.hcl` to add test assertions:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+
+test {
+ rules = {
+ main = false
+ is_open_hours = false
+ }
+}
+```
+
+And when we run the tests:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/7-am.hcl
+ PASS - test/policy/good.hcl
+```
+
+The test passes. We asserted that we expect the `main` rule to be false,
+the `is_open_hours` rule to be false, and the `is_weekday` rule to be
+true. By asserting some rules are true, we can verify that our policy
+is failing for reasons we expect.
+
+## Mocking
+
+The above example demonstrates how to test by supplying different
+[parameters](/sentinel/language/parameters). Parameters in a policy can be
+specifically useful when you want to control user-defined input values to a
+policy.
+
+However, generally, when testing, you will need mimic the conditions you will
+see in production. Production implementations of Sentinel will supply data using
+one of two methods:
+
+- **Global data:** Data is injected directly into the policy's scope and is
+ accessible using normal identifiers, similar to variables.
+- **Imports:** Data is stored behind an [import](/sentinel/language/imports)
+ and loaded on demand as needed by the policy author.
+
+Proper testing of a policy requires that these values be able to be _mocked_ -
+or, in other words, simulated in a way that allows the accurate testing of the
+scenarios that a policy could reasonably pass or fail under.
+
+Mocking both globals and imports can be done by setting various parts of the
+configuration file.
+
+### Mocking Globals
+
+Demonstrating the mocking of globals can be seen by making a few modifications to
+our example policy, removing the `param` declarations:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Then, change the `param` section in the configuration file to
+[`global`](/sentinel/configuration#global).
+
+```json
+global "day" {
+ value = "monday"
+}
+
+global "hour" {
+ value = 14
+}
+```
+
+This test should still pass, as if nothing had happened, although what we've
+done is shifted our parameters to globals, simulating an environment where `day`
+and `hour` are already defined for us.
+
+### Mocking Imports
+
+To mock imports, we need to use the
+[`mock`](/sentinel/configuration#mock-imports) section of the
+configuration file.
+
+Let's say the above example is behind an import named `time`.
+
+-> **NOTE:** [`time`](/sentinel/imports/time) is a valid standard import.
+This example may not be accurate to the
+import's syntax.
+
+The code now looks like this:
+
+```sentinel
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To mock this import, we can [mock it as static
+data](/sentinel/configuration#mocking-static-data). The configuration
+file now looks like, without assertions:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ weekday_name = "Monday"
+ hour = 14
+ }
+ }
+}
+```
+
+The policy will now pass, with the `time` import mocked.
+
+Data can also be [mocked as Sentinel
+code](/sentinel/configuration#mocking-with-sentinel-code). In this case,
+the above configuration file would look like:
+
+```hcl
+mock "time" {
+ module {
+ source = "mock-time.sentinel"
+ }
+}
+```
+
+And a file named `mock-time.sentinel` would now hold your mock values:
+
+```sentinel
+day = "monday"
+hour = 14
+```
+
+Mocking as Sentinel code allows more complex details to be mocked as well, such
+as functions. Say we wanted to mock the `time.load()` function. To mock this,
+just add it to the `mock-time.sentinel` file:
+
+```sentinel
+load = func(_) {
+ return {
+ "weekday_name": "Monday",
+ "hour": 14,
+ }
+}
+```
+
+Your code can now be written as:
+
+```sentinel
+import "time"
+
+t = time.load("a_mock_timestamp")
+
+is_weekday = rule { t.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { t.hour > 8 and t.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To see more details, see the [Mock
+Imports](/sentinel/configuration#mock-imports) section in the
+configuration file.
+
+## Asserting Non-Boolean Rules
+
+Non-boolean rules can also be asserted by `sentinel test`.
+
+To assert non-boolean values, simply enter the expected value into the rule
+contents:
+
+```hcl
+param "maintenance_days" {
+ value = [
+ {
+ day = "wednesday"
+ hour = 9
+ },
+ {
+ day = "friday"
+ hour = 1
+ },
+ {
+ day = "sunday"
+ hour = 1
+ },
+ ]
+}
+
+test {
+ rules = {
+ main = [
+ {
+ day = "wednesday"
+ hour = 9
+ },
+ ]
+ }
+}
+```
+
+This would assert a policy checking for violations of a maintenance policy,
+saying that maintenance hours should happen before 6AM on any given day:
+
+```sentinel
+param maintenance_days
+
+main = rule {
+ filter maintenance_days as d {
+ d.hour >= 6
+ }
+}
+```
+
+## Test JSON Output
+
+Running `sentinel test` with the `-json` flag will give you the test results in
+a JSON output format, suitable for parsing by reporting software.
+
+-> **Note:** The JSON output format is currently under development and the
+format may change at a later time. Additionally, support for other well-known
+formats, such as JUnit, may become available in the future.
+
+The current format is an object with the only top-level key being `policies`,
+with each test grouped up by policy being run.
+
+The policy result fields are:
+
+- `path`: The path of the policy and the index of the test result in the
+ `policies` field in the root object.
+- `status`: A string representation of the policy's test status as a whole. Can
+ be one of `PASS`, `FAIL`, `ERROR`, or `?`. The final status, `?`, represents a
+ policy that has no tests to process, and acts like a passing test.
+- `errors`: An array of any error messages encountered during processing the
+ policy for testing. Usually reserved for policy file or parser-related errors.
+ For case-specific errors, see the error field for the particular case.
+- `cases`: A map of case results, indexed by case path.
+
+The case result fields are:
+
+- `path`: The path of the test case and the result's index in the `cases` field
+ in the policy object.
+- `status`: A string representation of the case's test status. Can be one of
+ `PASS`, `FAIL` or `ERROR`.
+- `errors`: An array of any error messages encountered during running this test
+ case.
+- `trace`: The trace for this policy in JSON format. See the
+ [tracing](/sentinel/writing/tracing) page for more details.
+- `rule_detail`: When the `status` of this test case is `FAIL`, contains an
+ object of assertion failure detail, indexed by rule. Only failures are counted
+ here; any rules not found here can be assumed to have passed or not asserted.
+- `config_warnings`: An array of strings denoting any configuration warnings
+ found while processing the configuration for this test case.
+- `config_legacy`: Denotes whether or not the configuration is a legacy JSON
+ configuration and needs to be modernized. This field may be removed in future
+ releases.
+
+A passing example is shown below:
+
+```json
+{
+ "policies": {
+ "policy.sentinel": {
+ "path": "policy.sentinel",
+ "status": "PASS",
+ "errors": null,
+ "cases": {
+ "test/policy/pass.hcl": {
+ "path": "test/policy/pass.hcl",
+ "status": "PASS",
+ "errors": null,
+ "trace": {
+ "description": "A very basic policy to determine if a run is within working hours.",
+ "error": null,
+ "print": "",
+ "result": true,
+ "rules": {
+ "is_open_hours": {
+ "desc": "Passes if run during business hours.",
+ "ident": "is_open_hours",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 290,
+ "line": 13,
+ "column": 1
+ },
+ "value": true
+ },
+ "is_weekday": {
+ "desc": "Passes if the day does not fall on the weekend.",
+ "ident": "is_weekday",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 193,
+ "line": 10,
+ "column": 1
+ },
+ "value": true
+ },
+ "main": {
+ "desc": "",
+ "ident": "main",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 338,
+ "line": 14,
+ "column": 1
+ },
+ "value": true
+ }
+ }
+ },
+ "rule_detail": {},
+ "config_warnings": null,
+ "config_legacy": false
+ }
+ }
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.23.x/content/sentinel/docs/writing/tracing.mdx b/content/sentinel/v0.23.x/content/sentinel/docs/writing/tracing.mdx
new file mode 100644
index 0000000000..f2069bd682
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/docs/writing/tracing.mdx
@@ -0,0 +1,401 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-tracing
+description: >-
+ Sentinel provides execution traces to better understand the execution of a
+ policy.
+layout: docs
+---
+
+# Traces
+
+Sentinel provides execution traces to better understand the execution of a policy.
+
+When using the Sentinel [CLI](/sentinel/commands), traces are shown
+on policy failure during `apply` or `test`, or when the `-trace` flag is
+explicitly specified. For Sentinel-enabled applications, traces are optional
+and may require special configuration to enable.
+
+The availability of the trace may vary from application to application. While
+some applications may only log traces on failure, others, such as [Terraform
+Cloud](https://www.terraform.io/cloud), log the trace on every execution. For
+more details, consult your implementation's documentation.
+
+-> **Note:** We're actively improving Sentinel tracing in each release
+to provide better data and improve readability. The exact format of a
+trace may not match the Sentinel version embedded in the application you're
+using, but the data should be similar. The canonical format for a trace should
+get more stable as we approach a 1.0 release.
+
+## Analyzing a Trace
+
+To show traces, let's use the example policy below with the CLI:
+
+```sentinel
+param day
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+
+main = rule { is_open_hours and is_weekday }
+```
+
+Let's cause the policy to fail so the trace is more interesting.
+If you're on a terminal that supports it, the trace output will be colored
+so you can more clearly see passes, failures, and rules.
+
+
+
+ $ sentinel apply main.sentinel{'\n'}
+
+ main.sentinel:1:7: requires value for parameter day
+
+ {'\n'}
+ {'\n'}
+ {'\n'}
+ {' '}Values can be strings, floats, or JSON array or object values. To force
+ {'\n'}
+ {' '}strings, use quotes.{'\n'}
+ {'\n'}
+ {' '}Enter a value: sunday{'\n'}
+ {'\n'}
+
+ main.sentinel:2:7: requires value for parameter hour
+
+ {'\n'}
+ {'\n'}
+ {'\n'}
+ {' '}Values can be strings, floats, or JSON array or object values. To force
+ {'\n'}
+ {' '}strings, use quotes.{'\n'}
+ {'\n'}
+ {' '}Enter a value: 14{'\n'}
+ {'\n'}
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+
+ Fail - main.sentinel
+
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:7:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}
+ false
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:5:1 - Rule "is_open_hours"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}true{'\n'}
+ {'\n'}
+
+ main.sentinel:4:1 - Rule "is_weekday"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}false{'\n'}
+
+
+
+We can see all of our rules neatly and clearly displayed along with the result
+of the policy. If you are getting the trace due to a failed policy and not
+because you explicitly used `-trace`, an informational message is displayed.
+
+As we can see from our basic example, the `main` rule has failed and its result
+is highlighted in red. All peripheral rules that were also evaluated as part of
+the policy are also displayed below the main rule. Source positions are also
+displayed so that you can find the references to the rules in your code.
+
+Via the trace, we can clearly see that while `is_open_hours` returned a true
+result, `is_weekday` did not, and per the logic in our code, the policy is
+rejected.
+
+## Non-Boolean Rules and the Trace
+
+The trace is particularly important when working with rules that return
+non-boolean data, such as rules where you want to see violations instead of a
+simple opaque boolean value. Let's take our example from the [rules](./rules)
+section, and write a small static policy for it:
+
+```sentinel playground
+days = [
+ {"weekday": "Sunday", "weather": "clouds"},
+ {"weekday": "Monday", "weather": "rain"},
+ {"weekday": "Tuesday", "weather": "sunny"},
+ {"weekday": "Wednesday", "weather": "sunny"},
+ {"weekday": "Thursday", "weather": "clouds"},
+ {"weekday": "Friday", "weather": "rain"},
+ {"weekday": "Saturday", "weather": "sunny"},
+]
+
+main = rule {
+ filter days as d {
+ d.weather is not "sunny"
+ }
+}
+```
+
+Here is the output of our policy:
+
+
+
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+ {'\n'}
+
+ Fail - weather.sentinel
+
+ {'\n'}
+ {'\n'}
+
+ weather.sentinel:11:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+
+ {' [\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Sunday"'}
+ {'\n'}
+ {' "weather": "clouds"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Monday"'}
+ {'\n'}
+ {' "weather": "rain"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Thursday"'}
+ {'\n'}
+ {' "weather": "clouds"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Friday"'}
+ {'\n'}
+ {' "weather": "rain"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' ]'}
+
+
+
+
+We can now see all of the weekdays with non-sunny weather.
+
+Note that to prevent formatting issues and excessive output, the human-readable
+trace is limited in the volume of output it will display for collections.
+
+## JSON Output
+
+The trace can also be displayed in JSON by adding `-json` to `sentinel apply`
+and `sentinel test`, and will display the trace in its entirety, unlike the
+standard CLI output.
+
+Here is the result of running `sentinel apply -json` against our weather policy:
+
+```json
+{
+ "can_override": false,
+ "error": null,
+ "policies": [
+ {
+ "allowed_failure": false,
+ "error": null,
+ "policy": "weather.sentinel",
+ "result": false,
+ "trace": {
+ "description": "",
+ "error": null,
+ "print": "",
+ "result": false,
+ "rules": {
+ "main": {
+ "desc": "",
+ "ident": "main",
+ "position": {
+ "filename": "weather.sentinel",
+ "offset": 328,
+ "line": 11,
+ "column": 1
+ },
+ "value": [
+ {
+ "weather": "clouds",
+ "weekday": "Sunday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Monday"
+ },
+ {
+ "weather": "clouds",
+ "weekday": "Thursday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Friday"
+ }
+ ]
+ }
+ }
+ }
+ }
+ ],
+ "result": false
+}
+```
+
+Note that the trace is always included with this output, regardless of whether
+or not the policy passes or fails; using `-trace` is unnecessary.
+
+Additionally, with `sentinel apply`, you can display the value of a single rule
+in JSON, instead of the entire trace. This is done with the `-json-rule` flag.
+
+-> **Note:** `sentinel apply -json-rule` needs to be run either against a single
+on-disk policy, or against a single policy within a policy set. It is ignored
+in full policy set executions and functions exactly like `-json` in these
+scenarios.
+
+Here is the result of executing `sentinel apply -json-rule main weather.sentinel`:
+
+```json
+[
+ {
+ "weather": "clouds",
+ "weekday": "Sunday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Monday"
+ },
+ {
+ "weather": "clouds",
+ "weekday": "Thursday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Friday"
+ }
+]
+```
+
+## Other Trace Details
+
+There are some other details that are good to know when working with the trace:
+
+- Only rules that get executed are added to the trace. Considering our first
+ example where the `main` rule is the expression `is_open_hours and is_weekday`,
+ if our expression was using an `or` operator instead of `and`, in addition to
+ the policy passing, `is_open_hours` would be present in the trace, but
+ `is_weekday` would not be, as the policy would have never evaluated that rule.
+- Descriptions for both rules and policies will show up in the trace as well if
+ present. Here is the output to our first policy with the appropriate
+ annotations:
+
+
+
+ ...{'\n\n'}
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+
+ Fail - main.sentinel
+
+ {'\n'}
+ {'\n'}
+ Description:{'\n'}
+ {' A very basic policy to determine if a run is within working\n'}
+ {' hours.\n'}
+ {'\n'}
+
+ main.sentinel:7:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}
+ false
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:5:1 - Rule "is_open_hours"
+
+ {'\n'}
+ {' '}
+ Description:
+ {'\n'}
+ {' Passes if run during business hours.\n'}
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}true{'\n'}
+ {'\n'}
+
+ main.sentinel:4:1 - Rule "is_weekday"
+
+ {'\n'}
+ {' '}
+ Description:
+ {'\n'}
+ {' Passes if the day does not fall on the weekend.\n'}
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}false{'\n'}
+
+
diff --git a/content/sentinel/v0.23.x/content/sentinel/intro/getting-started/first-policy.mdx b/content/sentinel/v0.23.x/content/sentinel/intro/getting-started/first-policy.mdx
new file mode 100644
index 0000000000..38d7b22c9a
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/intro/getting-started/first-policy.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Your First Sentinel Policy
+sidebar_current: intro-getting-started-first
+description: Let's begin by writing a working Sentinel policy.
+layout: intro
+---
+
+# Your First Sentinel Policy
+
+Sentinel is a system to enforce complex policies on an integrated application.
+
+Writing Sentinel policy requires minimal programming experience. The Sentinel
+language is designed to be approachable and learned quickly and easily. Whether
+you're a professional programmer or someone who uses SQL and Excel, you can
+learn to write Sentinel policies.
+
+Let's begin by writing a simple, working Sentinel policy:
+
+```sentinel playground
+hour = 4
+main = rule { hour >= 0 and hour < 12 }
+```
+
+This is a valid Sentinel policy. It will pass since we hardcoded the
+`hour` to be 4. In a real system, `hour` may be something that is provided
+to us and actually set to the current hour. We'll learn more about that later.
+
+For now, try running this policy locally. Save the above example to a file
+named `policy.sentinel` and execute it. Then, modify the policy to make it
+fail. Play around more if you'd like.
+
+```
+$ sentinel apply policy.sentinel
+Pass
+```
+
+## Main
+
+Every Sentinel policy must have a `main` rule. This is the rule that
+is evaluated to determine the result of a policy.
+
+A rule describes an expression that generally means one of two things:
+
+- Does a policy _pass a condition_ that would authorize an operation? In our
+ above example, describe a policy that checks the supplied hour (4) is within an
+ authorized time window (between 0 - midnight, and 12 noon).
+- Conversely, can a policy find any _violations_ that would block authorization
+ of the operation? Building on the above, consider a policy that takes a
+ schedule, and finds all time blocks that fall outside of the example time window
+ supplied in the above policy.
+
+It is easy to imagine that such a rule might be used in a system such
+as [Nomad](https://www.nomadproject.io) to restrict the times when a
+deploy can occur. The power of arbitrary logical statements within Sentinel
+allows Sentinel policies to restrict almost any behavior.
+
+Next, we'll
+[introduce and explain rules](/sentinel/intro/getting-started/rules)
+so we can use them in our policies.
diff --git a/content/sentinel/v0.23.x/content/sentinel/intro/getting-started/imports.mdx b/content/sentinel/v0.23.x/content/sentinel/intro/getting-started/imports.mdx
new file mode 100644
index 0000000000..dbbb146ecf
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/intro/getting-started/imports.mdx
@@ -0,0 +1,58 @@
+---
+page_title: Imports
+sidebar_current: intro-getting-started-imports
+description: Imports enable Sentinel to access external data and functions.
+layout: intro
+---
+
+# Imports
+
+Imports enable Sentinel to access external data and functions.
+
+Sentinel ships with a set of [standard imports](/sentinel/imports).
+Standard imports are available by default to every Sentinel policy. Note that
+the application embedding Sentinel may allow or deny the available
+set of imports.
+
+To use an import, use the `import` statement. Then, access data and
+functions on the import using the import name followed by a period and
+the field you want to access. The example below checks whether the request
+path starts with a prefix. Assume that `request_path` is available.
+
+```sentinel
+import "strings"
+
+main = rule { strings.has_prefix(request_path, "/admin") }
+```
+
+## External Data
+
+The true power in imports is their ability to reference external data.
+Imports can be added to a Sentinel-enabled application through
+[plugins](/sentinel/extending). This enables any Sentinel-enabled
+application to make policy decision based on any source of data.
+
+This ability allows the policy system to enforce almost any necessary
+organizational policy, since the ability of a policy isn't restricted
+purely to the embedding application's data model.
+
+For example, policies in [Nomad](https://www.nomadproject.io) can
+access data in [Consul](https://www.consul.io) to determine attributes
+of a policy. In the example below, we use a hypothetical Consul import:
+
+```sentinel
+import "consul"
+
+main = rule {
+ job.tasks[0].resources.memory <= int(consul.get("policy/nomad/max-memory"))
+}
+```
+
+In this example, a Nomad job's memory usage is limited to the value of
+a Consul key/value item. By simply changing a Consul KV entry, policy
+can be changed. Imports can do anything, you can [write your own import plugins](/sentinel/extending)
+to extend Sentinel.
+
+Next, we'll learn how to
+[test policies](/sentinel/intro/getting-started/testing)
+to ensure our policy logic is correct.
diff --git a/content/sentinel/v0.23.x/content/sentinel/intro/getting-started/index.mdx b/content/sentinel/v0.23.x/content/sentinel/intro/getting-started/index.mdx
new file mode 100644
index 0000000000..5c9b6b43c5
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/intro/getting-started/index.mdx
@@ -0,0 +1,16 @@
+---
+page_title: Install Sentinel CLI - Getting Started
+sidebar_current: intro-getting-started-overview
+description: The first step to using Sentinel is to get the CLI installed.
+layout: intro
+---
+
+# Getting Started With Sentinel
+
+This section covers getting started with Sentinel. Here, we will take you
+through the first steps that you will need to do to get started with installing
+the Sentinel CLI, writing your first policy, and getting familiar with rules,
+boolean logic, imports, and testing.
+
+You can select a topic on the menu to get started. The first step is to [install
+the Sentinel CLI](/sentinel/intro/getting-started/install).
diff --git a/content/sentinel/v0.23.x/content/sentinel/intro/getting-started/install.mdx b/content/sentinel/v0.23.x/content/sentinel/intro/getting-started/install.mdx
new file mode 100644
index 0000000000..e478fd532f
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/intro/getting-started/install.mdx
@@ -0,0 +1,56 @@
+---
+page_title: Install Sentinel CLI - Getting Started
+sidebar_current: intro-getting-started-install
+description: The first step to using Sentinel is to get the CLI installed.
+layout: intro
+---
+
+# Install Sentinel CLI
+
+Sentinel is a policy framework that is embedded in the enterprise versions of
+HashiCorp tools. The policies you write are deployed to these applications and
+enforced there.
+
+The Sentinel CLI is a command-line interface for local development and testing.
+For the getting started guide, we'll use the CLI to learn how to write policies
+for Sentinel-enabled applications. The Sentinel CLI is distributed as a [binary
+package](/sentinel/downloads) for all supported platforms and architectures.
+
+## Installation Instructions
+
+To install the Sentinel CLI, find the [appropriate package](/sentinel/downloads) for your
+system and download it. The CLI is packaged as a zip archive.
+
+After downloading Sentinel, unzip the package. The CLI runs as a single binary
+named `sentinel`. Any other files in the package can be safely removed and
+Sentinel will still function.
+
+The final step is to make sure that the `sentinel` binary is available on the `PATH`.
+See [this page](https://stackoverflow.com/questions/14637979/how-to-permanently-set-path-on-linux)
+for instructions on setting the PATH on Linux and Mac.
+[This page](https://stackoverflow.com/questions/1618280/where-can-i-set-path-to-make-exe-on-windows)
+contains instructions for setting the PATH on Windows.
+
+## Verifying the Installation
+
+After installing the Sentinel CLI, verify the installation worked by opening a
+new terminal session and checking that the `sentinel` binary is available. By
+executing `sentinel`, you should see help output similar to the following:
+
+```
+$ sentinel
+Usage: sentinel [--version] [--help] []
+
+Available commands are:
+ apply Execute a policy and output the result
+ fmt Format Sentinel policy to a canonical format
+ test Test policies
+ version Prints the Sentinel version
+```
+
+If you get an error that the binary could not be found, then your `PATH`
+environment variable was not setup properly. Please go back and ensure that your
+`PATH` variable contains the directory where Sentinel was installed.
+
+Otherwise, the CLI is installed and ready to go. Let's celebrate by [writing our
+first policy!](/sentinel/intro/getting-started/first-policy)
diff --git a/content/sentinel/v0.23.x/content/sentinel/intro/getting-started/logic.mdx b/content/sentinel/v0.23.x/content/sentinel/intro/getting-started/logic.mdx
new file mode 100644
index 0000000000..d3716a64ea
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/intro/getting-started/logic.mdx
@@ -0,0 +1,54 @@
+---
+page_title: Logic
+sidebar_current: intro-getting-started-logic
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Logic
+
+The core of a policy is a set of logical expressions to determine whether
+a behavior is allowed or not.
+Sentinel makes it easy to write readable logical expressions to express
+the intended policy.
+
+The logical expression syntax will be familiar to anyone who has programmed
+before. Sentinel also supports a couple more unique constructs to assist
+with common policy requirements. Detailed documentation on how to write
+logical expressions and the supported operators is available in
+[the boolean expression reference](/sentinel/language/boolexpr).
+
+Example logic:
+
+```sentinel
+a is b // Equality, you can also use ==
+a is not b // Inequality, you can also use !=
+
+a > b // Relational operators, comparison
+a < b
+a >= b
+a <= b
+
+a contains b // Inclusion check, substrings, etc.
+a not contains b // Negated version of above
+```
+
+The full list of available operators can be found in
+[the boolean expression reference](/sentinel/language/boolexpr).
+
+Logic can be combined with `and` or `or`. When combined with `and`, both
+sides must result in `true`. When combined with `or`, only one side needs
+to be true to result in `true`.
+
+With these building blocks, basic rules can be built up:
+
+```sentinel
+main = rule {
+ hour >= 8 and
+ hour < 17
+}
+```
+
+Next, we'll introduce
+[imports](/sentinel/intro/getting-started/imports)
+so that we can write rules that interact with external data.
diff --git a/content/sentinel/v0.23.x/content/sentinel/intro/getting-started/next-steps.mdx b/content/sentinel/v0.23.x/content/sentinel/intro/getting-started/next-steps.mdx
new file mode 100644
index 0000000000..23b470be59
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/intro/getting-started/next-steps.mdx
@@ -0,0 +1,21 @@
+---
+page_title: Next Steps
+sidebar_current: intro-getting-started-nextsteps
+description: That concludes the getting started guide for Sentinel.
+layout: intro
+---
+
+# Next Steps
+
+That concludes the getting started guide for Sentinel. Hopefully you're excited
+about the possibilities of Sentinel and ready to put this knowledge to use to
+improve the safety of your environments.
+
+We've covered the basics of the core features of Sentinel in this guide.
+We recommend the following as next steps:
+
+- [Documentation](/sentinel) - The documentation is an in-depth
+ reference guide of all the features of Sentinel.
+
+- [Language Reference](/sentinel/language) - The language reference
+ is a complete reference to the features of the Sentine policy language.
diff --git a/content/sentinel/v0.23.x/content/sentinel/intro/getting-started/rules.mdx b/content/sentinel/v0.23.x/content/sentinel/intro/getting-started/rules.mdx
new file mode 100644
index 0000000000..f12a61fe75
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/intro/getting-started/rules.mdx
@@ -0,0 +1,86 @@
+---
+page_title: Rules
+sidebar_current: intro-getting-started-rules
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Rules
+
+Rules in Sentinel are a first-class concept. Within a policy, rules serve a few
+purposes:
+
+- They make complex logic more understandable by allowing said logic to be
+ broken down.
+- They allow assertion of this logic through
+ [testing](/sentinel/intro/getting-started/testing) of the rule's contents.
+- They facilitate reporting of data as rules get published as part of the [policy
+ trace](/sentinel/writing/tracing).
+
+A rule functions in ways similar to both a variable and a function: they hold a
+value, but are lazily evaluated; a rule's value is not assigned until the first
+time it's referenced in a policy. Additionally, while the value of evaluated
+rules will be available within a policy's trace after it's evaluated, values of
+variables - and the return value of functions - are not. Finally, a rule value
+is memoized - further references to the rule will not change its result.
+
+Rules can hold more than just boolean data. For more advanced rule patterns, see
+[the language reference](/sentinel/language/rules).
+
+## Writing Rules
+
+Let's look at the Sentinel policy we wrote in the previous section:
+
+```sentinel playground
+hour = 4
+main = rule { hour >= 0 and hour < 12 }
+```
+
+The logic in this policy is straightforward and easy to understand. However,
+it is common for policy logic to become much more complicated. Rules are the
+key abstraction for making complex logic understandable. We can turn the
+policy above into a set of rules:
+
+```sentinel playground
+hour = 4
+
+past_midnight = rule { hour >= 0 }
+before_noon = rule { hour < 12 }
+
+main = rule {
+ past_midnight and
+ before_noon
+}
+```
+
+This policy is easier to read. Our original policy was already quite
+simple, but it is easy to imagine a more complex policy becoming much
+more readable by extracting logical expressions into a set of rules.
+
+Rules don't just exist to make a policy more readable. They're also a
+testing and debugging point. For testing, you can assert that certain rules
+are certain values given a specific input to verify that your policy works
+as you expect. Testing and debugging are covered in later sections.
+
+## Conditional Rules
+
+In many cases, a rule is only valid when something else is also true.
+
+Consider the human language statement "before you eat, you must wash your hands."
+The rule "you must wash your hands" is only valid "before you eat". In any
+other scenario, the rule is meaningless.
+This is also true in a technical context. Imagine the rule "if the driver
+is Docker, you must not expose any ports." For this example, assume
+rules `is_docker` and `has_no_exposed_ports` exist. You can write this rule
+like this:
+
+```sentinel
+main = rule when is_docker { has_no_exposed_ports }
+```
+
+When `is_docker` is true, the rule is evaluated as normal. However,
+when `is_docker` is false, the rule always returns `true`, because the
+logic of the rule is meaningless.
+
+Let's learn how to create more complex rules with
+[logical expressions](/sentinel/intro/getting-started/logic).
diff --git a/content/sentinel/v0.23.x/content/sentinel/intro/getting-started/testing.mdx b/content/sentinel/v0.23.x/content/sentinel/intro/getting-started/testing.mdx
new file mode 100644
index 0000000000..7149a7966f
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/intro/getting-started/testing.mdx
@@ -0,0 +1,72 @@
+---
+page_title: Testing
+sidebar_current: intro-getting-started-testing
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Testing
+
+It is important to ensure the policies you write are correct. Sentinel
+includes a built-in test framework that can be run locally and in CI
+environments to test that policies behave as expected.
+
+A common pitfall with many simple ACL systems is that they provide no easy
+way to verify their correctness. You basically have to set the ACL and try
+the behaviors against a real system to verify it is working as expected.
+This requires a lot of setup and is unique to each system.
+
+[Sentinel's built-in test framework](/sentinel/writing/testing) has zero
+dependencies. Contained within the [Sentinel CLI](/sentinel/commands), it can
+mock the data that real systems are exposing to the policy. It is designed to be
+CI-friendly and enables continuous testing of your policies. This is necessary
+for [policy as code](/sentinel/concepts/policy-as-code).
+
+Detailed documentation on testing policies is available in [the Sentinel Testing
+reference](/sentinel/writing/testing).
+
+## Writing Tests
+
+For this example, save the following policy as `policy.sentinel`:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Next, let's write a passing test case. This will test that the policy tests
+when we expect it to pass. Save the following in `test/policy/good.hcl`:
+
+```hcl
+global "day" {
+ value = "monday"
+}
+
+global "hour" {
+ value = 14
+}
+```
+
+And run `sentinel test`:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/good.json
+```
+
+The `sentinel test` command will automatically find all Sentinel policies
+and their associated test cases and run them all. The test framework will
+run all HCL files as individual test cases, allowing you to test a variety
+of scenarios for your policies.
+
+Try adding another test case to force the policy to fail. This test case can
+be saved at `test/policy/fail.hcl`. For test cases with intentional
+failures, you'll need to use the [test assertions described in the testing
+reference](/sentinel/writing/testing#a-failing-test).
+
+Now that you have a good grasp of what Sentinel is and how to use it, please feel
+free to look through the
+[next steps](/sentinel/intro/getting-started/next-steps)
+you can take to further your Sentinel knowledge.
diff --git a/content/sentinel/v0.23.x/content/sentinel/intro/index.mdx b/content/sentinel/v0.23.x/content/sentinel/intro/index.mdx
new file mode 100644
index 0000000000..785a442fb8
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/intro/index.mdx
@@ -0,0 +1,32 @@
+---
+layout: intro
+page_title: Documentation
+sidebar_current: docs-home
+description: >-
+ Sentinel is a language and framework for policy built to be embedded in
+ existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+[introduction](/sentinel/intro) to understand what Sentinel is, how it
+compares to other software, and more.
+
+
diff --git a/content/sentinel/v0.23.x/content/sentinel/intro/what.mdx b/content/sentinel/v0.23.x/content/sentinel/intro/what.mdx
new file mode 100644
index 0000000000..36853d366c
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/intro/what.mdx
@@ -0,0 +1,64 @@
+---
+page_title: Introduction
+sidebar_current: intro-what
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: intro
+---
+
+# Introduction to Sentinel
+
+Welcome to the intro guide to Sentinel! This guide is the best
+place to start with Sentinel.
+
+If you are already familiar with the basics of Sentinel, the
+[documentation](/sentinel) provides a better reference
+guide for all available features as well as internals.
+
+## What is Sentinel?
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+Sentinel provides a language for writing policy and a workflow for developing
+and testing policies independent of the software they'll be written to. We
+also provide a framework for developers to build plugins that allow a
+Sentinel-enabled system to access external information to make policy
+decisions.
+
+Most systems today have some degree of access control. You are able to define
+identities and what they have access to. These ACL systems solve an immediate
+and necessary problem of locking down a system in very broad strokes. Sentinel
+is a reusable system for more advanced software policy decisions. Sentinel
+enables:
+
+- **Fine-Grained Policy**: Most ACL systems only enable coarse-grained
+ behaviors: "read", "write", etc. Sentinel enables fine-grained behavior such
+ as disallowing a certain API call when specific parameters are present.
+
+- **Logic-Based Policy**: You can write policy using full
+ conditional logic. For example, you may only allow a certain application behavior
+ on Monday to Thursday unless there is a manager override.
+
+- **Accessing External Information**: Sentinel can source external information
+ to be used in policy decisions. For example, a policy that restricts the size
+ of a payload may read data from [Consul](https://www.consul.io) to determine
+ the payload size limit.
+
+- **Enforcement levels**: Sentinel allows policies to be defined along
+ with an "enforcement level" that dictates the pass/fail behavior of a policy.
+ Advisory policies warn if they fail, soft mandatory policies can have their
+ failures overridden, and hard mandatory policies must pass under all
+ circumstances. Having this as a built-in concept enables you to model
+ policy more accurately and completely for your organization.
+
+## Next Steps
+
+See the page on [Why Sentinel?](/sentinel/intro/why) to learn more
+about the origins of the product. Then, continue onwards with
+the [getting started guide](/sentinel/intro/getting-started/install) to write
+your first policies and see how Sentinel works in practice.
diff --git a/content/sentinel/v0.23.x/content/sentinel/intro/why.mdx b/content/sentinel/v0.23.x/content/sentinel/intro/why.mdx
new file mode 100644
index 0000000000..98b58aab92
--- /dev/null
+++ b/content/sentinel/v0.23.x/content/sentinel/intro/why.mdx
@@ -0,0 +1,49 @@
+---
+page_title: Why Sentinel?
+sidebar_current: intro-why
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: intro
+---
+
+# Why Sentinel?
+
+The growth of infrastructure and applications has been enabled in part
+by an increasing trend towards automation everywhere.
+Configuration as code (such as Chef or Puppet with [Packer](https://www.packer.io))
+has enabled automated machine configuration. Infrastructure
+as code (such as [Terraform](https://www.terraform.io)) has enabled
+automatic infrastructure creation. And schedulers (such as
+[Nomad](https://www.nomadproject.io) and Kubernetes) have enabled
+automatic application deployment.
+Sentinel enables guardrails to be put in place
+on automation while allowing the codification and automatic enforcement
+of business requirements in critical areas of your infrastructure.
+
+Meanwhile, businesses have business requirements and sometimes legal
+requirements which must be expressed in policies. Traditionally, these
+policies are enforced by humans. But in a highly automated world, the
+automation is only as fast as its slowest component. In many cases, this
+is the human verification step.
+
+As an example: before infrastructure as code and autoscaling, if an order
+came through for 5,000 new machines, a human would likely respond to the
+ticket verifying that the user really intended to order 5,000 new machines.
+Today, automation can almost always freely order 5,000 new compute instances
+without any hesitation, which can result in unintended expense or system
+instability.
+
+Sentinel introduces [policy as code](/sentinel/concepts/policy-as-code)
+and a powerful framework built-in to HashiCorp tooling to allow automation
+guardrails, business requirements, legal compliance, and more to be
+actively enforced by the running systems in realtime.
+
+With Sentinel, you can require an override to create certain numbers of
+infrastructure resources. You can disallow unsafe deployment configurations
+with Nomad. You can enforce certain key/value formats in Consul. You
+can restrict secret access by time in Vault. And more.
+
+Sentinel is available today in HashiCorp enterprise products. You can
+try Sentinel immediately with the [getting started guide](/sentinel/intro/getting-started/install) or learn more about the integrations in the
+[documentation](/sentinel).
diff --git a/content/sentinel/v0.23.x/data/docs-nav-data.json b/content/sentinel/v0.23.x/data/docs-nav-data.json
new file mode 100644
index 0000000000..0bfc027b10
--- /dev/null
+++ b/content/sentinel/v0.23.x/data/docs-nav-data.json
@@ -0,0 +1,366 @@
+[
+ {
+ "title": "Release Notes",
+ "path": "changelog"
+ },
+ {
+ "title": "Basic Concepts",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "concepts"
+ },
+ {
+ "title": "Policy as Code",
+ "path": "concepts/policy-as-code"
+ },
+ {
+ "title": "Policy Language",
+ "path": "concepts/language"
+ },
+ {
+ "title": "Imports",
+ "path": "concepts/imports"
+ },
+ {
+ "title": "Enforcement Levels",
+ "path": "concepts/enforcement-levels"
+ }
+ ]
+ },
+ {
+ "title": "Configuration File Syntax",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "configuration"
+ },
+ {
+ "title": "Override Files",
+ "path": "configuration/overrides"
+ },
+ {
+ "title": "Remote Sources",
+ "path": "configuration/remote-sources"
+ }
+ ]
+ },
+ {
+ "title": "Commands (CLI)",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "commands"
+ },
+ {
+ "title": "apply",
+ "path": "commands/apply"
+ },
+ {
+ "title": "fmt",
+ "path": "commands/fmt"
+ },
+ {
+ "title": "test",
+ "path": "commands/test"
+ }
+ ]
+ },
+ {
+ "title": "Writing Policy",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "writing"
+ },
+ {
+ "title": "Basics",
+ "path": "writing/basic"
+ },
+ {
+ "title": "Rules",
+ "path": "writing/rules"
+ },
+ {
+ "title": "Traces",
+ "path": "writing/tracing"
+ },
+ {
+ "title": "Testing",
+ "path": "writing/testing"
+ },
+ {
+ "title": "Imports",
+ "path": "writing/imports"
+ },
+ {
+ "title": "Debugging",
+ "path": "writing/debugging"
+ }
+ ]
+ },
+ {
+ "title": "Extending Sentinel",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "extending"
+ },
+ {
+ "title": "Modules",
+ "path": "extending/modules"
+ },
+ {
+ "title": "Plugins",
+ "path": "extending/plugins"
+ },
+ {
+ "title": "Static Imports",
+ "path": "extending/static-imports"
+ },
+ {
+ "title": "Internals",
+ "path": "extending/internals"
+ }
+ ]
+ },
+ {
+ "title": "Features",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "features"
+ },
+ {
+ "title": "terraform",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "features/terraform"
+ },
+ {
+ "title": "tfplan/v1",
+ "path": "features/terraform/tfplan-v1"
+ },
+ {
+ "title": "tfplan/v2",
+ "path": "features/terraform/tfplan-v2"
+ },
+ {
+ "title": "tfconfig/v1",
+ "path": "features/terraform/tfconfig-v1"
+ },
+ {
+ "title": "tfconfig/v2",
+ "path": "features/terraform/tfconfig-v2"
+ },
+ {
+ "title": "tfstate/v1",
+ "path": "features/terraform/tfstate-v1"
+ },
+ {
+ "title": "tfstate/v2",
+ "path": "features/terraform/tfstate-v2"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Language",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "language"
+ },
+ {
+ "title": "Variables",
+ "path": "language/variables"
+ },
+ {
+ "title": "Values",
+ "path": "language/values"
+ },
+ {
+ "title": "Lists",
+ "path": "language/lists"
+ },
+ {
+ "title": "Maps",
+ "path": "language/maps"
+ },
+ {
+ "title": "Rules",
+ "path": "language/rules"
+ },
+ {
+ "title": "Imports",
+ "path": "language/imports"
+ },
+ {
+ "title": "Parameters",
+ "path": "language/parameters"
+ },
+ {
+ "title": "Boolean Expressions",
+ "path": "language/boolexpr"
+ },
+ {
+ "title": "Arithmetic",
+ "path": "language/arithmetic"
+ },
+ {
+ "title": "Slices",
+ "path": "language/slices"
+ },
+ {
+ "title": "Conditionals",
+ "path": "language/conditionals"
+ },
+ {
+ "title": "Loops",
+ "path": "language/loops"
+ },
+ {
+ "title": "Collection Operations",
+ "path": "language/collection-operations"
+ },
+ {
+ "title": "Functions",
+ "path": "language/functions"
+ },
+ {
+ "title": "Scope",
+ "path": "language/scope"
+ },
+ {
+ "title": "Undefined",
+ "path": "language/undefined"
+ },
+ {
+ "title": "Logging and Errors",
+ "path": "language/logging-errors"
+ },
+ {
+ "title": "Specification",
+ "path": "language/spec"
+ }
+ ]
+ },
+ {
+ "title": "Builtin Functions",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "functions"
+ },
+ {
+ "title": "append",
+ "path": "functions/append"
+ },
+ {
+ "title": "delete",
+ "path": "functions/delete"
+ },
+ {
+ "title": "error",
+ "path": "functions/error"
+ },
+ {
+ "title": "keys",
+ "path": "functions/keys"
+ },
+ {
+ "title": "length",
+ "path": "functions/length"
+ },
+ {
+ "title": "print",
+ "path": "functions/print"
+ },
+ {
+ "title": "range",
+ "path": "functions/range"
+ },
+ {
+ "title": "values",
+ "path": "functions/values"
+ }
+ ]
+ },
+ {
+ "title": "Standard Imports",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "imports"
+ },
+ {
+ "title": "base64",
+ "path": "imports/base64"
+ },
+ {
+ "title": "decimal",
+ "path": "imports/decimal"
+ },
+ {
+ "title": "http",
+ "path": "imports/http"
+ },
+ {
+ "title": "json",
+ "path": "imports/json"
+ },
+ {
+ "title": "runtime",
+ "path": "imports/runtime"
+ },
+ {
+ "title": "sockaddr",
+ "path": "imports/sockaddr"
+ },
+ {
+ "title": "strings",
+ "path": "imports/strings"
+ },
+ {
+ "title": "time",
+ "path": "imports/time"
+ },
+ {
+ "title": "types",
+ "path": "imports/types"
+ },
+ {
+ "title": "units",
+ "path": "imports/units"
+ },
+ {
+ "title": "version",
+ "path": "imports/version"
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Consul",
+ "path": "consul"
+ },
+ {
+ "title": "Nomad",
+ "path": "nomad"
+ },
+ {
+ "title": "Terraform",
+ "path": "terraform"
+ },
+ {
+ "title": "Vault",
+ "path": "vault"
+ }
+]
diff --git a/content/sentinel/v0.23.x/data/intro-nav-data.json b/content/sentinel/v0.23.x/data/intro-nav-data.json
new file mode 100644
index 0000000000..c95181af70
--- /dev/null
+++ b/content/sentinel/v0.23.x/data/intro-nav-data.json
@@ -0,0 +1,47 @@
+[
+ {
+ "title": "What is Sentinel?",
+ "path": "what"
+ },
+ {
+ "title": "Why Sentinel?",
+ "path": "why"
+ },
+ {
+ "title": "Getting Started",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "getting-started"
+ },
+ {
+ "title": "Installing the CLI",
+ "path": "getting-started/install"
+ },
+ {
+ "title": "Your First Policy",
+ "path": "getting-started/first-policy"
+ },
+ {
+ "title": "Rules",
+ "path": "getting-started/rules"
+ },
+ {
+ "title": "Logic",
+ "path": "getting-started/logic"
+ },
+ {
+ "title": "Imports",
+ "path": "getting-started/imports"
+ },
+ {
+ "title": "Testing",
+ "path": "getting-started/testing"
+ },
+ {
+ "title": "Next Steps",
+ "path": "getting-started/next-steps"
+ }
+ ]
+ }
+]
diff --git a/content/sentinel/v0.23.x/img/sentinel-import-topology.svg b/content/sentinel/v0.23.x/img/sentinel-import-topology.svg
new file mode 100644
index 0000000000..36f7c79bc9
--- /dev/null
+++ b/content/sentinel/v0.23.x/img/sentinel-import-topology.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/content/sentinel/v0.23.x/redirects.jsonc b/content/sentinel/v0.23.x/redirects.jsonc
new file mode 100644
index 0000000000..d9e73f4a49
--- /dev/null
+++ b/content/sentinel/v0.23.x/redirects.jsonc
@@ -0,0 +1,15 @@
+[
+ {
+ "source": "/",
+ "destination": "/sentinel",
+ "permanent": true,
+ },
+ {
+ "source": "/sentinel/commands/config",
+ "destination": "/sentinel/configuration",
+ "permanent": true,
+ },
+ // disallow ".html" or "/index.html" in favor of cleaner, simpler paths
+ { "source": "/:path*/index", "destination": "/:path*", "permanent": true },
+ { "source": "/:path*.html", "destination": "/:path*", "permanent": true },
+]
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/changelog.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/changelog.mdx
new file mode 100644
index 0000000000..272039b896
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/changelog.mdx
@@ -0,0 +1,843 @@
+---
+page_title: Sentinel Runtime Release Notes
+sidebar_current: docs-changelog
+description: >-
+ These are the public release notes for the Sentinel runtime. See this document
+ for the latest updates.
+layout: docs
+---
+
+# Sentinel Runtime Release Notes
+
+These are the release notes for the Sentinel runtime.
+
+Each version of the runtime is released with a corresponding version of
+[Sentinel CLI](/sentinel/commands). To download the CLI, see the [install
+page](/sentinel/install).
+
+Sentinel integrations and embedded runtimes may not always have the latest
+version installed, depending on the product's individual release cycle. For more
+information, contact the support team for your specific integration.
+
+
+## 0.24.4 (Mar 21, 2024)
+
+ENHANCEMENTS:
+
+- `runtime/format`: Null values will now print correctly for rule value outputs.
+- `config`: Some internal changes to configuration parsing workflow.
+
+## 0.24.3 (Feb 9, 2024)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.24.2 (Jan 31, 2024)
+
+BUG FIXES:
+
+- `imports/static`: Fixed an issue where `nil` values provided to the built-in
+static import were being treated as `undefined` in policy.
+
+## 0.24.1 (Jan 19, 2024)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where the Sentinel cache was unstable due to concurrent
+tests.
+
+## 0.24.0 (Dec 7, 2023)
+
+ENHANCEMENTS:
+
+- `cmd/apply`, `cmd/test`: Custom import plugins can now be fetched from remote
+sources.
+- `cmd/apply`, `cmd/test`: Static imports can now be fetched from remote
+sources.
+- `features/terraform`: Added support for `resource_drift` in the `tfplan/v2` import.
+
+## 0.23.1 (Oct 19, 2023)
+
+BUG FIXES:
+
+- `cmd/apply`: Warnings and errors are included in the JSON output if the json flag is enabled.
+
+## 0.23.0 (Sept 5, 2023)
+
+FEATURES:
+
+- `features/apply_all`: Adds the `apply-all` feature, allowing for all policies to evaluate regardless of result, instead of exiting on first failure.
+
+BUG FIXES:
+
+- `features/terraform`: The `tfplan/v1` import correctly handles nested attribute schemas.
+
+## 0.22.1 (June 22, 2023)
+
+BUG FIXES:
+
+- `sentinel/eval`: Under certain conditions, per-policy parameters would cause
+ Sentinel to panic. This has been resolved.
+
+## 0.22.0 (May 31, 2023)
+
+- `config`: Configuration now supports a `sentinel` block to manage the
+ Sentinel runtime.
+
+## 0.21.1 (May 8, 2023)
+
+BUG FIXES:
+
+- `config`: An issue with certain identifiers being treated as incorrectly invalid
+ has been resolved.
+
+## 0.21.0 (March 8, 2023)
+
+BREAKING CHANGES:
+
+- `lang/ast`: `is empty` and `is not empty` are now treated as `ast.UnaryExpr`
+ expressions, with `ast.IsEmptyExpr` being removed.
+- `lang/ast`: You can now assert if a value is defined or not using the `is defined`
+ and `is not defined` syntax.
+
+FEATURES:
+
+- `config`: Parameter values can now be provided for individual policies within
+ a policy block.
+
+## 0.20.0 (February 16, 2023)
+
+ENHANCEMENTS:
+
+- `cmd/testcmd`: Allows sentinel tests to run concurrently by default. Sequential style testing can
+ be enabled by running with the `-maxConcurrency=1` option.
+- `cmd/testcmd`: Allows sentinel test command to timeout after a certain duration. This can be provided
+ by the user or will default to 5 minutes.
+- `cmd/apply`: The policy enforcement level is now included in the JSON output.
+- `lang/ast`: Functions can now be declared as named statements, providing
+ a safer function declaration.
+
+BREAKING CHANGES:
+
+- `cmd/apply`: Policies provided directly to the apply command will now default their enforcement
+ level to `advisory`, aligning with the `policy` configuration block.
+- `sentinel`: JSON results will no longer return `allowed_failure` or `can_override` fields.
+- `sentinel/result`: A new package has been added which provides additional methods to return
+ supplemental data about the evaluation result.
+
+## 0.19.5 (February 9, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `SelectorExpr` will correctly rewrite `X`.
+
+## 0.19.4 (February 8, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `Compile` function will correctly set the `Original`
+ field when rewriting a `CallExpr` as an `ImportExpr`.
+
+## 0.19.3 (February 3, 2023)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.19.2 (January 17, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `Compile` function will no longer modify `ast.CallExpr`
+ arguments in place.
+
+## 0.19.1 (December 14, 2022)
+
+BUG FIXES:
+
+- `lang/ast`: The `Rewrite` function will no longer modify the provided Node
+ in place.
+
+ENHANCEMENTS:
+
+- `lang/semantic`: The Break semantic check uses the AST walker.
+
+## 0.19.0 (December 6, 2022)
+
+BREAKING CHANGES:
+
+- `config`: Attempts to override a standard import with a custom import will
+ now be met with an error.
+- `imports`: All plugin imports now return `sdk.Plugin` instead of `sdk.Import`,
+ as per the SDK update to v0.4.0.
+
+ENHANCEMENTS:
+
+- `runtime/importer`: An `Import` and `ImportInstance` interface have been
+ added to improve the import capabilities.
+- `runtime/importer`: The `Importer` interface now returns a `Import` interface
+ instead of a `sdk.Import`.
+- `lang/semantic`: The Import Assignment semantic check uses the AST walker.
+- `lang/ast`: Added an AST Walker so the AST rewriter does not need to be used for basic AST tasks.
+
+FEATURES:
+
+- `config`: Configuration syntax now allows for configuration of imports within
+ the standard library.
+- `config`: A new configuration syntax for defining modules and plugins is now
+ available.
+- Static data can now be added via `import` configuration.
+- `cmd/apply`: The `apply` command now supports multiple primary configuration
+ files by default, loading all configuration files within the working
+ directory.
+- `cmd/apply`: The `apply` command now accepts applying configuration overrides
+ to primary configuration.
+
+## 0.18.13 (October 31, 2022)
+
+BUG FIXES:
+
+- `cmd/apply`: `sentinel apply` no longer aborts on advisory policy failure.
+
+## 0.18.12 (September 15, 2022)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where nested `CallExpr` were not rewritten.
+
+## 0.18.11 (June 8, 2022)
+
+NOTES:
+
+- `config`: The `enforcement_level` key for policies is now optional, defaulting to `"advisory"` when not supplied.
+
+## 0.18.10 (May 20, 2022)
+
+NOTES:
+
+- `runtime/initwd`: Improvements to the installer, including timeouts and filesize limits, as well as disabling support
+ for symlinks within `git` or `hg` repositories.
+
+## 0.18.9 (March 23, 2022)
+
+NOTES:
+
+- This release fixes an issue with the Docker container build pipeline. There are no changes to the
+Sentinel runtime or CLI.
+
+## 0.18.8 (March 22, 2022)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue in the installer for Windows paths.
+
+## 0.18.7 (February 24, 2022)
+
+NOTES:
+
+- **Darwin arm64**. This release will be our first to include Darwin arm64 architecture.
+
+BUG FIXES:
+
+- `lang/ast`: Fixed issues where `IndexSpec` and `RuleLit` nodes where returning incorrect end positions.
+- `lang/ast`: Fixed an issue where `ParamSpec` nodes where returning incorrect end positions.
+
+## 0.18.6 (February 2, 2022)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.18.5 (January 14, 2022)
+
+IMPROVEMENTS:
+
+- `runtime/initwd`: Changed the sum algorithm used during remote installation from `md5` to `sha256` to reduce chance of collision.
+
+## 0.18.4 (July 20, 2021)
+
+FEATURES:
+
+- `imports/static`: Improved handling of struct tags when performing encoding, allowing `-` to mark as ignored.
+
+## 0.18.3 (June 1, 2021)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue for remote source installation where duplicate sub-directories resulted in incorrect installation.
+- `imports/static`: Fixed an issue where global configuration that contained an empty map could not be accessed without raising selector issues.
+
+## 0.18.2 (May 18, 2021)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where import expressions inside lists and maps were not being evaluated correctly.
+
+## 0.18.1 (May 11, 2021)
+
+BUG FIXES:
+
+- `imports/json`: Fixed an issue where empty maps where failing when passed at any level to `marshal`.
+
+## 0.18.0 (March 25, 2021)
+
+FEATURES:
+
+- `imports/http`: Added support for POST actions within the `http` import
+ through addition of the `post` function. Additionally, support for adding
+ a body to the `request` object has been added via `with_body`.
+
+## 0.17.4 (February 2, 2021)
+
+BUG FIXES:
+
+- `runtime/format`: Fixed a nesting issue with the rule printer where nesting
+ state leaks were leading to all collection values eventually being truncated,
+ regardless of their nesting level.
+
+## 0.17.3 (January 15, 2021)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue where local file installation failed on Windows
+ when using `sentinel test`.
+
+## 0.17.2 (January 13, 2021)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where duplicate log messages were being printed with
+ `sentinel test`.
+
+## 0.17.1 (January 7, 2021)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where hard-coded path separator caused `sentinel
+ test` issues in Windows.
+
+## 0.17.0 (January 5, 2021)
+
+LANGUAGE CHANGES:
+
+- **Map Expression** `map`. A new quantifier expression, `map` has been added.
+ `map` takes a collection and applies an operation to each element in the
+ collection, returning a list of results with the resulting values.
+- **Emptiness checks** `is empty` and `is not empty`. Two new expressions,
+ `is empty` and `is not empty` have been added. As they are aliases to the
+ `length` built-in, only `string`, `map`, `list` or `undefined` is
+ supported.
+- **Comparable maps** Maps (the data type) are now comparable. Maps are equal if
+ they are of equal length and both their corresponding keys and values are
+ comparable and equal.
+- **Rich Return Types** Rules can now process and return non-boolean data.
+ Scalar, list, and map types are supported. When non-boolean values are used,
+ the presence of a non-zero or non-zero-length `main` rule determines policy
+ failure. More details can be found on
+ https://docs.hashicorp.com/sentinel/language#main.
+
+FEATURES:
+
+- **CLI**: `sentinel apply` and `sentinel test` now have options for JSON
+ output. For more details, see the CLI documentation.
+- `imports/base64`: Added a new import, `base64`, to assist with encoding and
+ decoding base64 strings.
+
+## 0.16.1 (October 21, 2020)
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where `contains` to ensure any instance of `undefined` would correctly result in `undefined`.
+- `runtime/eval`: Fixed an issue in `any` and `all` expressions to ensure correct boolean logic when dealing with a collection containing `undefined`.
+- `imports`: Fixed an issue where import processes were not killed, which caused non-graceful closures for the clients.
+- `lang/scanner`: Fixed an issue where inline `#` style comments were not being consumed.
+
+## 0.16.0 (October 14, 2020)
+
+BREAKING CHANGES:
+
+- `cmd/doc`: Removal of the deprecated `sentinel doc` command.
+- `sentinel`: Replace `whitelist` and `blacklist` with `allowlist` and `denylist`, as per inclusive language changes.
+
+FEATURES:
+
+- `imports/version`: Added a new import, `version`, which supplies helpers for dealing with semantic versioning.
+- `cmd/apply`: Added an `HCL` based configuration file, which will eventually deprecate the legacy `JSON` configuration.
+- `cmd/apply`: Added support for download remote policies and modules.
+- `cmd/test`: Added support for downloading remote test modules.
+- `cmd/apply`: Added support for evaluating a policy based on it's key within the configuration.
+- `cmd/apply`: Added support for running all policies in a configuration by default.
+
+## 0.15.6 (July 7, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.15.5 (May 20, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel integrations. There are no user-facing changes.
+
+## 0.15.4 (May 13, 2020)
+
+BUG FIXES:
+
+- `imports`: Fixed an issue with the standard imports that was affecting the handling of null data within lists.
+
+## 0.15.3 (April 16, 2020)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where `IndexExpr` were not rewritten, as well as ensuring `SelectorExpr` uses any nested rewrites.
+- `lang/printer`: Fixed issue printing deeply nested structures and/or loops.
+
+IMPROVEMENTS:
+
+- `imports/decimal`: Added alias methods to improve consistency of method names within the `decimal` import. The alias methods are `is_nan` for `isnan`, as well as both `is_inf` and `is_infinite` for `isinfinite`.
+- `command/apply`: `sentinel apply` will now output the policy description when a trace is forced via `-trace`.
+- `sentinel`: Improved `String` output formatting of Policy docstring for `EvalPolicyResult`.
+
+## 0.15.2 (April 2, 2020)
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue with the `printer` that was causing a panic
+ when a `#` line comment was used with no text following it.
+- `imports/types`: Fixed an issue with the `types` import where calls to
+ `type_of` for undefined types were incorrectly returning as map types. These
+ will now be correctly be identified as undefined as expected.
+
+## 0.15.1 (March 6, 2020)
+
+BUG FIXES:
+
+- `sentinel`: Fixed how modules are managed internally in the runtime to correct
+ race conditions in concurrent scenarios and to allow for per-evaluation module
+ restrictions. This functionality is only of interest to embedded applications
+ with long-lived runtimes and is currently not implemented in any HashiCorp
+ integration.
+
+## 0.15.0 (March 5, 2020)
+
+NOTES:
+
+- **Windows Digital Signature**. Our windows releases for this version and up will be signed
+ and verified according to Microsoft's requirements.
+
+FEATURES:
+
+- `cmd/config`: Modules can now be loaded off of local disk using the Sentinel
+ CLI. For information on how to do so, see the Sentinel documentation.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: Changed modules so that they now have singleton scope when
+ loaded. Importing a module under two different aliases, or within a nested
+ module, will now share scopes - modification of state in one will alter the
+ other.
+- `lang/object`: Introduced an interface to allow the duplication of various
+ types, like collections.
+- `runtime/eval`: Changed how collection data is returned from modules. This
+ data is now duplicated to prevent accidental or deliberate circumvention of
+ the prohibition on assignment to import data, and brings behavior to parity
+ with binary imports.
+
+## 0.14.4 (February 6, 2020)
+
+IMPROVEMENTS:
+
+- `imports/time`: Changed the `add` timespace function in the `time` import to
+ allow it to take float types as durations. These values will be truncated to
+ the appropriate integer-value duration.
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where out-of-place right-hand braces were being
+ parsed as empty statements, instead of raising syntax errors.
+
+## 0.14.3 (January 22, 2020)
+
+IMPROVEMENTS:
+
+- `cmd/test`: Fail when an assertion is not found in a trace.
+- `cmd/test`: Added a message to notify when no rules were evaluated in a test.
+
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue where import aliases (the `as baz` in `import
+ "foo/bar" as baz`) were being removed when formatting with `sentinel fmt`.
+
+- `imports/http`: Fixed an issue with the http import where the client library's
+ debug logs were being printed to standard error.
+
+## 0.14.2 (January 15, 2020)
+
+NOTES:
+
+With this release, we are discontinuing support for MacOS 32-bit. 64-bit builds
+are now the only builds available for MacOS.
+
+IMPROVEMENTS:
+
+- `lang/printer`: A newline is now added to files formatted via `sentinel fmt`.
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue in printing where multi-line expressions would
+ indent infinitely. They will now only indent once at the most. This is mostly
+ seen when using `sentinel fmt`.
+- `runtime/encoding`: Fixed an issue where if a plugin returned a deeply
+ embedded undefined value, the value was instead decoded into a map.
+
+## 0.14.1 (January 6, 2020)
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where reserved words could not be used as
+ selectors when the selector expression was the last lexeme on a line.
+
+## 0.14.0 (December 18, 2019)
+
+LANGUAGE CHANGES:
+
+- **Filter Expression** `filter`. A new quantifier expression, `filter` has been added.
+ `filter` returns a subset of the provided collection based on a boolean condition that
+ is asserted for each element.
+
+NOTES:
+
+- **Apple Notarization**. Our darwin releases for this version and up will be signed
+ and notarized according to Apple's requirements.
+
+## 0.13.1 (November 25, 2019)
+
+BUG FIXES:
+
+- `runtime/eval`: Parameters are no longer allowed in mock files. Adding one to
+ a mock will result in a runtime error.
+- `runtime/eval`: Fixed an issue where import calls from within mocks were
+ failing under certain circumstances.
+- `imports/decimal`: `int` should now correctly provide the truncated integer
+ representation of a decimal number, not the rounded one.
+
+## 0.13.0 (November 15, 2019)
+
+LANGUAGE CHANGES:
+
+- **Policy Parameters**. The new [policy
+ parameters](https://docs.hashicorp.com/sentinel/language/parameters) feature
+ allows authors to use a `param` declaration at the top of a policy to supply
+ values that are expected to come from outside of a policy. See the linked
+ documentation for more details.
+
+FEATURES:
+
+- `imports/http`: The [`http`
+ import](https://docs.hashicorp.com/sentinel/imports/http) is a new addition
+ to the standard library enabling the use of HTTP-accessible data from outside
+ the runtime in policy rules.
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where loading other imports before a mocked
+ import would cause those imports to no longer be visible from within the
+ policy.
+
+## 0.12.0 (October 7, 2019)
+
+LANGUAGE CHANGES:
+
+- **New `case` statement**. This statement is a selection control mechanism to
+ conditionally execute a branch based on expression equality, allowing
+ simplification of complex conditional chains that may otherwise need to be
+ written with `else if`. See the [Case
+ Statements](https://docs.hashicorp.com/sentinel/language/spec#case-statements)
+ section of the Sentinel language specification for more details.
+
+IMPROVEMENTS:
+
+- `lang/semantic`: Added a semantic check to ensure usage of append is not using
+ a return value.
+
+BUG FIXES:
+
+- `runtime/eval`: Corrected an issue where calling a method on an import object
+ value that was the result of a method call on another import object value
+ would have erroneously tried to call an import of the name of the "parent"
+ import object value. Example: in `a = subject.new(); b = a.call(); c =
+ b.call()`, `b.call()` would attempt to call a method named `call` on the root
+ namespace for an import named `a`. This has now been corrected so that
+ `b.call()` will now correctly call the `call` method for the respective object
+ namespace residing in the import named `subject`.
+- `imports`: Some standard imports may have been returning null for some unknown
+ keys in objects when they should have been returning undefined. This was due
+ to an SDK issue which was corrected in SDK version 0.3.2, which has now been
+ corrected.
+- `cmd/test`: `sentinel test` will now correctly fail a policy if it encounters
+ an error.
+- `cmd/test`: `sentinel test` will now correctly display errors and other output
+ that were missing due to a formatting issue.
+- `runtime/eval`: The `append` builtin now correctly returns undefined for all
+ calls, as called for by the Specification. Note that in most cases, the
+ semantic check outlined above will trigger an error if the return value is
+ used.
+
+## 0.11.0 (September 5, 2019)
+
+LANGUAGE CHANGES:
+
+- **New builtin function:** `range()`. This function existed in the spec in
+ earlier versions but was removed as it lacked an implementation. This has now
+ been implemented and re-added to the spec. See the
+ [Range](https://docs.hashicorp.com/sentinel/language/spec#range) section of
+ the Sentinel Specification for more details.
+- Lists are now comparable. Lists are equal if their corresponding elements are
+ comparable and equal.
+- Method calls on values returned by imports are now supported. See the
+ [Imports](https://docs.hashicorp.com/sentinel/language/spec#imports) section
+ of the Sentinel Specification for more details.
+
+FEATURES:
+
+- `imports/decimal`: This is a new import designed to do exact precision
+ mathematical calculations.
+- `runtime/eval`: Compound call expressions that refer to imports (example:
+ foo.bar().baz() when `foo` is a loaded import) will now function as expected.
+ Previously, this was only supported up to the first call (example:
+ `foo.bar()`).
+- `imports/time`: Timespaces returned by calls such as `time.now` are now
+ callable. Example: `t = time.now; t.after(some_previous_time)` will now
+ function.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: The implementation of comparison of non-comparable types has
+ now changed. Rather than triggering a runtime error, non-comparable types will
+ now return false when attempting to compare.
+
+## 0.10.4 (August 15, 2019)
+
+BUG FIXES:
+
+- `runtime/encoding`: Fixed an issue with conversion of null values that could
+ lead to crashes in imports.
+
+## 0.10.3 (July 15, 2019)
+
+BUG FIXES:
+
+- `command/config`: Parsing a configuration file where malformed data is
+ encountered after apparently properly-formed data will now report an error. An
+ example would be a situation where misplaced braces would cause a JSON object
+ to only parse part of the configuration file.
+
+## 0.10.2 (June 25, 2019)
+
+BUG FIXES:
+
+- `lang/parser`: Corrected an issue where parsing certain compound binary
+ expressions where the negation predicate (`not`) was in use would cause the
+ negation to have no effect. Example: `foo else "bar" not in "baz"` would have
+ been parsed and evaluated as `foo else "bar" in "baz"`, effectively producing
+ the opposite result.
+
+## 0.10.1 (May 9, 2019)
+
+BUG FIXES:
+
+- `command/test`: `sentinel test` now displays policies without
+tests as an unknown result with [no test files], instead of the somewhat
+erroneous behavior of displaying it as a PASS. A full test run with a mixture
+of passing tests and no tests still results in an overall successful result.
+
+## 0.10.0 (April 18, 2019)
+
+LANGUAGE CHANGES:
+
+- Mixed-number arithmetic operations are now allowed. Addition (`+`),
+ subtraction (`-`), multiplication (`*`), division (`/`), and remainder
+ operations (`%`) are no longer restricted to number values of the same
+ type, and can mix integer and floating point. The result of these operations
+ is always a floating-point number.
+- Remainder (modulo, `%`) operations are now allowed on floating-point numbers.
+
+
+BUG FIXES:
+
+- `lang/parser`: Fixed a bug that affected the use of `not` with `contains`,
+ `matches`, or `in` in a compound expression.
+- `runtime/eval`: Fixed error messages on evaluation errors with `contains` and
+ `in` to indicate that strings are an allowed type along with lists and maps.
+
+
+## 0.9.2 (March 15, 2019)
+
+This is a dependency update related to the changes mentioned in 0.9.1. No other
+changes have been made.
+
+## 0.9.1 (March 14, 2019)
+
+This is a patch release that is required to integrate with the latest versions
+of the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). No other
+changes have been made.
+
+## 0.9.0 (January 28, 2019)
+
+FEATURES:
+
+- `imports/strings`: Added a `join` function to the `strings` import. This can
+ be used to join a list into a string with a specific separator.
+ Multi-dimensional lists and all Sentinel primitives are supported.
+
+## 0.8.1 (January 17, 2019)
+
+BUG FIXES:
+
+- `lang/eval`: Fixed a bug that prevents the effective use of `return`
+ statements in `for` loops.
+
+## 0.8.0 (January 14, 2019)
+
+FEATURES:
+
+- Mocks can now be represented by Sentinel code. This allows for the mocking of
+ functions and other complex data structures that cannot be represented in
+ JSON. For more information on using this feature via mocks, click
+ [here](https://docs.hashicorp.com/sentinel/commands/config#mocking-with-sentinel-code).
+
+
+IMPROVEMENTS:
+
+- Import validation has now been moved to the semantic checking phase. This
+ should result in better reporting of validation errors. In addition, import
+ validation will now enforce the use of an `as` identifier when an import path
+ is not a valid identifier on its own (example: `import "foo/bar"`).
+
+## 0.7.0 (December 12, 2018)
+
+BUG FIXES:
+
+- There have been changes to the runtime in how scope is handled over multiple
+ policy executions. Scope is now correctly unique per single policy execution,
+ and values set or builtins that are overridden in one policy will no longer
+ affect those values within another.
+
+## 0.6.0 (November 30, 2018)
+
+FEATURES:
+
+- `imports/runtime`: This new import allows for one to check various aspects of
+ the Sentinel runtime as it may be embedded in the simulator or a specific
+ implementation. For now, it allows the version to be checked.
+
+## 0.5.1 (November 28, 2018)
+
+IMPROVEMENTS:
+
+- `imports/time`: Added the `zone` and `zone_string` attributes to assist with
+ validation of a timespace's zone.
+- `command/fmt`: Added a new -check flag. This option does not commit changes,
+ but instead checks to see what files need formatting and outputs them on
+ stdout.
+
+BUG FIXES:
+
+- `command/test`: Ensure that passing test results are correctly output one per
+ line. Tests are also now run in a deterministic fashion based on
+ lexicographical (alphabetical) order.
+- `imports/time`: `month_name` and `weekday_name` will now show up correctly in
+ a returned timespace result.
+
+
+## 0.5.0 (November 5, 2018)
+
+IMPROVEMENTS:
+
+- `spec`: Selectors can now contain any reserved word (example: `rule`) or
+ keyword operator (example: `any`, `all`, `is`, `not`). This only works for the
+ _selector_ part of the expression (after the first period) - the first primary
+ expression (before the first period) still needs to be an identifier that does
+ not conflict with reserved words.
+
+BUG FIXES:
+
+- The simulator should now display import function call names correctly in
+ import errors.
+
+## 0.4.0 (October 1, 2018)
+
+FEATURES:
+
+- `builtin`: Added the `bool` built-in type conversion function. Booleans will
+ also now accepted as conversion into other values as well, with the full list
+ of behaviors available in the spec.
+
+## 0.3.2 (September 27, 2018)
+
+FEATURES:
+
+- `command/apply`: `sentinel apply` now prints out messages output by the
+ `print()` function when a trace is output on policy failure, or when a trace
+ is forced with `-trace`.
+- `imports/time`: Added the `month_name` and `weekday_name` keys to the
+ timespace, which return full-English names for the month and day of the week.
+
+
+BUG FIXES:
+
+- `command/fmt`: `sentinel fmt -` Will no longer print out the filter status
+ message on the output stream when `-write=false` Is not explicitly stated.
+ This brings the behavior of the command in line with the help text.
+- `runtime`: Index operations on the right-hand-side that have negative indexes
+ that go out of range (example: `length(list) * -1 - 1`) now correctly return
+ `undefined`. left-hand-side index assignments with a out-of-range negative
+ index still return runtime errors.
+
+## 0.3.1 (August 3, 2018)
+
+BUG FIXES:
+
+- `runtime`: Basic index assignment has been implemented as per the spec.
+- `runtime`: Index expressions for lists with negative indexes will no longer panic if the
+ list index is less than `length(list) * -1`.
+
+## 0.3.0 (July 20, 2018)
+
+FEATURES:
+
+- **New standard import: `types`.** This can be used to dynamicaly detect the
+ type of some value.
+
+## 0.2.0 (April 11, 2018)
+
+FEATURES:
+
+- **New standard import: `json`.** Marshal and unmarshal JSON documents and
+ access their contents as native Sentinel values.
+- **`break` and `continue`.** These are now both specified and implemented.
+ `break` allows loop exiting and `continue` allows immediate execution of the
+ next iteration.
+
+IMPROVEMENTS:
+
+- runtime: `print()` map values are now ordered alphabetically by keys.
+
+BUG FIXES:
+
+- command/test: If no `test` block exists, test behaves like it is asserting
+ `main: true`.
+- runtime: default maximum stack depth to 500
+- runtime: `print()` map values now appear like more typical maps.
+- runtime: division by zero is an error, not a crash
+- runtime: plugins that send map values with `null` values now decode properly
+ into native Sentinel values.
+
+## 0.1.0 (September 19, 2017)
+
+Initial release.
+
+
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/commands/apply.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/commands/apply.mdx
new file mode 100644
index 0000000000..c5e81d6d65
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/commands/apply.mdx
@@ -0,0 +1,63 @@
+---
+page_title: 'Command: apply'
+sidebar_current: docs-commands-apply
+description: >-
+ The `sentinel apply` command is used to execute a policy locally for
+ development purposes.
+layout: docs
+---
+
+# Command: `apply`
+
+The `sentinel apply` command is used to execute a policy locally for development
+purposes.
+
+## Usage
+
+Usage: `sentinel apply [options] POLICY`
+
+This command executes the policy file at the path specified by POLICY. In
+addition to a path to a policy, POLICY can reference a label of a
+[policy](/sentinel/configuration#policies) entry in the configuration.
+
+Use the exit code of this command to determine the exact status of the policy
+evaluation. `0` is pass, `1` is fail, `2` is undefined (fail, but because the
+result was undefined), and `3` is a runtime error. Errors unrelated to the
+policy status itself are returned with an exit status of `9`.
+
+To control the behavior of the `apply` command, create a [configuration
+file](/sentinel/configuration). With this, you can define available
+[import plugins](/sentinel/concepts/imports), mock data, and global values.
+This can help you simulate a policy embedded within an application.
+
+As of the 0.16 release, POLICY is optional. When it is not supplied,
+`sentinel apply` will run **all** policies in the supplied configuration.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-config=path` - Path to a configuration file specifying available imports,
+ mock data, globals, etc. The default is `sentinel.[hcl|json]`.
+
+- `-json` Enable JSON output. Using this mode, all other output will be
+ suppressed and the full detail of the apply will be output in a
+ machine-readable format. Enabling this implies `-trace`. See the section on
+ [tracing JSON output](/sentinel/writing/tracing#json-output) for more details.
+
+- `-json-rule=RULE` Enable JSON output, outputting only the value of the
+ specified rule. When running against a policy set, the policy must be supplied
+ to enable per-rule filtering. Implies `-json`.
+
+- `-global key=value` - Set global values. This is the same as setting `global`
+ in the configuration file, and will override any of these respective values
+ set in the configuration. The value is either a string, or a JSON number,
+ array, or object. To force strings, use quotes.
+
+- `-param key=value` - Set parameters, the same as setting `param` in the
+ configuration file. Values are handled in the same way they are with the
+ `-global` flag.
+
+- `-trace` - Always show the execution trace. This shows intermediate
+ boolean expression values. This always shows for failed policies.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/commands/fmt.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/commands/fmt.mdx
new file mode 100644
index 0000000000..8297316d8b
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/commands/fmt.mdx
@@ -0,0 +1,33 @@
+---
+page_title: 'Command: fmt'
+sidebar_current: docs-commands-fmt
+description: The `sentinel fmt` command formats a policy source to a canonical format.
+layout: docs
+---
+
+# Command: `fmt`
+
+The `sentinel fmt` command formats a policy source to a canonical format.
+
+## Usage
+
+Usage: `sentinel fmt [options] FILE ...`
+
+This command formats all the specified policy files to a canonical format.
+
+By default, policy files are overwritten in place. This behavior can be
+changed with the `-write` flag. If a specified FILE is `-` then stdin is
+read and the output is always written to stdout.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-write=true` - Write formatted policy to the named source file. If false,
+ output will go to stdout. If multiple files are specified, the output will
+ be concatenated directly.
+
+- `-check=false` - Don't format, only check if formatting is necessary. Files
+ that require formatting are printed, and a non-zero exit code is returned if
+ changes are required.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/commands/index.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/commands/index.mdx
new file mode 100644
index 0000000000..7177093cec
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/commands/index.mdx
@@ -0,0 +1,23 @@
+---
+page_title: Commands (CLI)
+sidebar_current: docs-commands
+description: >-
+ Sentinel provides a `sentinel` command-line interface (CLI) for developing and
+ testing policies.
+layout: docs
+---
+
+# Sentinel CLI Commands
+
+The Sentinel command-line interface (CLI) allows for the developing and testing
+of policies outside of a particular Sentinel implementation. Having a standard
+workflow to develop policies is critical for our mission of [policy as
+code](/sentinel/concepts/policy-as-code).
+
+The CLI takes a subcommand to execute. The complete list of subcommands is in
+the navigation to the left.
+
+The Sentinel CLI is a well-behaved command line application. In erroneous cases,
+a non-zero exit status will be returned. It also responds to -h and --help as
+you'd expect. To view a list of the available commands at any time, just run
+`sentinel` with no arguments.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/commands/test.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/commands/test.mdx
new file mode 100644
index 0000000000..ed50aacc22
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/commands/test.mdx
@@ -0,0 +1,53 @@
+---
+page_title: 'Command: test'
+sidebar_current: docs-commands-test
+description: The `sentinel test` command runs policies against the Sentinel test framework.
+layout: docs
+---
+
+# Command: `test`
+
+The `sentinel test` command runs policies against the Sentinel test framework.
+
+To learn more about testing, see the [testing
+policies](/sentinel/writing/testing) page.
+
+## Usage
+
+Usage: `sentinel test [options] [PATH] ...`
+
+This command runs the defined test cases against a set of policies.
+
+The test cases are expected to live at the path `test//*.[hcl|json]` relative
+to the policy being tested. `` should be the name of the policy
+excluding the file extension. The files should be in the [configuration
+file structure](/sentinel/configuration). The `test` key is used for
+assertions. Test cases ignore the root level configuration file and must have
+all required configuration provided in each test case.
+
+The PATH arguments specified may be a list of zero or more files or directories.
+When no arguments are given, it defaults to the current directory. When a
+directory is given, all policies ending with the extension `.sentinel` are
+tested.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-json` - Suppress normal output and output test results as a JSON document.
+ See the [testing JSON output](/sentinel/writing/testing#test-json-output)
+ section for more details.
+
+- `-run=regexp` - Run only tests matching the regular expression pattern.
+ A `/` splits policy name and test case name.
+
+- `-verbose` - Show tracing and log output for tests that succeed in addition
+ to tests that fail. By default, traces and logs are only shown for tests that
+ fail.
+
+- `-maxConcurrency` - Allows users to specify the number of tests to be run concurrently.
+ Defaults to the number of logical CPUs if not provided. To run tests sequentially, use `-maxConcurrency=1`
+
+- `-timeout` - Allows users to specify a timeout after which the test command will stop running.
+Defaults to 5 minutes if not provided. The timeout needs to be specified as a Duration type, eg `100ms, 5s etc`
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/concepts/enforcement-levels.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/concepts/enforcement-levels.mdx
new file mode 100644
index 0000000000..fc4b8062d0
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/concepts/enforcement-levels.mdx
@@ -0,0 +1,43 @@
+---
+page_title: Enforcement Levels
+sidebar_current: docs-concepts-levels
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Enforcement Levels
+
+Enforcement levels are a first class concept in Sentinel allowing pass/fail
+behavior to be associated separately from the policy logic. This enables
+any policy to be a warning, allow overrides, or be absolutely mandatory.
+Because this level is not part of the policy body itself, different uses of
+the same policy can have different enforcement levels.
+
+Sentinel has three enforcement levels:
+
+- **Advisory:** The policy is allowed to fail. However, a warning should be
+ shown to the user or logged. Advisory is the default enforcement level.
+
+- **Soft Mandatory:** The policy must pass unless an override is specified.
+ The semantics of "override" are specific to each Sentinel-enabled application.
+ The purpose of this level is to provide a level of privilege separation
+ for a behavior. Additionally, the override provides non-repudiation since
+ at least the primary actor was explicitly overriding a failed policy.
+
+- **Hard Mandatory:** The policy must pass no matter what. The only way to
+ override a hard mandatory policy is to explicitly remove the policy.
+ It should be used in situations where an override is not possible.
+
+## Configuring Enforcement Levels
+
+Enforcement levels are configured when a policy is deployed to a
+Sentinel-enabled application. The exact mechanism that the level is specified
+is determined by each application. Please reference the documentation for
+your Sentinel-enabled application for more information.
+
+Enforcement levels are not configured and are not known by the policy body
+itself. All policies should be written to describe exactly the behavior
+they're attempting to control. For example, a policy that restricts deploys
+to business hours should be written exactly like so. When that policy is
+configured on an application, the operator may specify that it is advisory,
+soft, or hard mandatory.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/concepts/imports.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/concepts/imports.mdx
new file mode 100644
index 0000000000..1306e1512e
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/concepts/imports.mdx
@@ -0,0 +1,32 @@
+---
+page_title: Imports
+sidebar_current: docs-concepts-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom imports](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only
+local context for making policy decisions.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that the
+import calendar allows access to engineer calendars. This import only exists
+for the purpose of this example and at the time of writing is not a real
+available import.
+
+```json sentinel
+{
+ "policy": "import \"calendar\"\n// Get the calendar for Bob for today\nbob_calendar = calendar.of(\"bob\").today\n\n// Allow this policy to pass if Bob is not on vacation.\nmain = rule { not bob_calendar.has_event(\"vacation\") }",
+ "mocks": {
+ "calendar": "has_event = func(event) {\n\treturn true\n}\nof = func(name) {\n\treturn { \"today\": { \"has_event\": has_event } }\n}"
+ }
+}
+```
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/concepts/index.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/concepts/index.mdx
new file mode 100644
index 0000000000..ce7ac51454
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/concepts/index.mdx
@@ -0,0 +1,15 @@
+---
+page_title: Basic Concepts
+sidebar_current: docs-concepts
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Basic Concepts
+
+This section covers some high level basic concepts that are important
+to understand for day to day Sentinel usage. Every page in
+this section is recommended reading for anyone consuming
+or extending Sentinel.
+
+Please use the navigation to the left to learn more about a topic.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/concepts/language.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/concepts/language.mdx
new file mode 100644
index 0000000000..ce91f3eb8f
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/concepts/language.mdx
@@ -0,0 +1,91 @@
+---
+page_title: Policy Language
+sidebar_current: docs-concepts-language
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Policy Language
+
+Sentinel defines and uses its own [policy language](/sentinel/language).
+
+The language was designed to be approachable by non-programmers, since
+there are many use cases where the individual defining policy may not
+be a developer. However, the language includes constructs that
+are familiar to developers to enable powerful policies.
+
+To learn more about the language, please see the
+[writing policy section](/sentinel/writing)
+or the [language reference](/sentinel/language).
+
+An example of the policy language is shown below:
+
+```sentinel playground
+import "time"
+
+# Validate time is between 8 AM and 4 PM
+valid_time = rule { time.now.hour >= 8 and time.now.hour < 16 }
+
+# Validate day is M - Th
+valid_day = rule {
+ time.now.weekday_name in ["Monday", "Tuesday", "Wednesday", "Thursday"]
+}
+
+main = rule { valid_time and valid_day }
+```
+
+## Why?
+
+To explain why Sentinel defines and uses its own language, the question
+can be more easily split into roughly two types of languages: _configuration_ languages
+and _programming_ languages.
+
+### Configuration Languages
+
+Configuration languages are formats such as JSON, YAML, XML, etc. Many applications
+use configuration languages as their ACL format. Configuration languages
+are good for static, declarative information. ACL systems are a good fit
+for this. ACL systems are focused, providing the limiting options necessary
+to restrict access to a system.
+
+Configuration languages are not good for dynamic or logical rules. They
+typically only contain limited conditions, loops, or functions. Further
+configuration is often declarative, which can introduce complexities to
+logical statements that depend on ordering.
+
+The use cases that Sentinel was built for require the ability to perform
+complex behavior that didn't model well into a configuration format or a
+declarative form.
+
+### Programming Languages
+
+The Sentinel language was designed with the following goals:
+
+- **Non-programmer friendly.** Sentinel was built to be used by non-programmers.
+ For the use cases we discovered, non-programmers needed the ability to
+ enforce certain rules within a system. For example, a person responsible for
+ compliance may need to insert rules into a system.
+
+- **Programmer friendly.** At the same time, the language needed to support
+ programmer-friendly constructs such as conditionals, loops, and functions
+ for complex policies that a programmer may be writing.
+
+- **Embeddable.** Sentinel is embedded in existing software. The language
+ itself needs to be easily embeddedable. Further, Sentinel was designed
+ to be embedded in [HashiCorp](https://www.hashicorp.com) software
+ written in Go, so it needed to be easily embeddable in Go.
+
+- **Safe.** The language is used in highly security-sensitive environments.
+ It must not be able to crash its host system or access system resources
+ without explicit approval.
+
+Prior to building our own language, Sentinel evaluated other languages.
+Some languages worked quite well. As we continued to develop Sentinel, we
+found that the flexibility and control over designing our own language
+outweighed the costs.
+
+Because the language itself doesn't need to be general purpose, building
+a language focused on policy proved to be the best route for Sentinel.
+It allows us to build powerful tracing capabilities for debugging, make
+interesting performance choices, and extend the language as needed in the
+future.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/concepts/policy-as-code.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/concepts/policy-as-code.mdx
new file mode 100644
index 0000000000..6dac593e18
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/concepts/policy-as-code.mdx
@@ -0,0 +1,72 @@
+---
+page_title: Policy as Code
+sidebar_current: docs-concepts-pac
+description: >-
+ Policy as code is the idea of writing code in a high-level language to manage
+ and automate policies. By representing policies as code in text files, proven
+ software development best practices can be adopted such as version control,
+ automated testing, and automated deployment.
+layout: docs
+---
+
+# Policy as Code
+
+Policy as code is the idea of writing code in a high-level language to
+manage and automate policies. By representing policies as code in text files,
+proven software development best practices can be adopted such as version
+control, automated testing, and automated deployment.
+
+Many existing policy or ACL systems do not practice policy as code. Many
+policies are set by clicking in a GUI, which isn't easily repeatable nor
+versionable. They usually don't provide any system for testing policies
+other than testing an action that would violate the policy. This makes it
+difficult for automated testing. And the policy language itself varies by
+product.
+
+Sentinel is built around the idea and provides all the benefits of policy as code.
+
+## Benefits
+
+Policy as code provides a number of benefits:
+
+- **Sandboxing.** Policies provide the guardrails for other automated systems.
+ As the number of automated systems grow, there is also a growing need to
+ protect those automated systems from performing dangerous actions. Manual
+ verification is too slow; policies need to be represented as code to
+ keep up with other automated systems.
+
+- **Codification.** By representing policy logic as code, the information
+ and logic about a policy is directly represented in code and can be augmented
+ with comments rather than relying on oral tradition to learn about the
+ reason for policies.
+
+- **Version Control.** Policies are encouraged to be stored as simple text
+ files managed by a version control system. This lets you gain all the
+ benefits of a modern VCS such as history, diffs, pull requests, and more.
+
+- **Testing.** Policies are just code. Their syntax and behavior can be
+ [easily validated with Sentinel](/sentinel/commands/test). This also encourages
+ automated testing such as through a CI. Paired with a VCS system, this
+ allows a pull request workflow to verify that a policy keeps the system
+ behavior as expected before merging.
+
+- **Automation.** With all policies as code in simple text files, various
+ automation tools can be used. For example, it is trivial to create tools
+ to automatically deploy the policies into a system.
+
+## Sentinel and Policy as Code
+
+Sentinel fully embraces policy as code in a number of ways:
+
+- **Language.** All Sentinel policies are written using the
+ [Sentinel language](/sentinel/concepts/language). This language is
+ made to be inputted directly to text files. As an additional benefit,
+ all Sentinel-enabled applications share the same policy language.
+
+- **Development.** Sentinel provides a [CLI](/sentinel/commands)
+ for development and testing. This local CLI can be used to verify policies
+ before deploying them to a system.
+
+- **Testing.** Sentinel provides a [test framework](/sentinel/commands/test)
+ designed specifically for automation. This allows developers and CI systems
+ to further verify policies.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/configuration/index.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/configuration/index.mdx
new file mode 100644
index 0000000000..9c4b0c942e
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/configuration/index.mdx
@@ -0,0 +1,422 @@
+---
+page_title: Sentinel CLI Configuration File Syntax
+sidebar_current: docs-configuration
+description: >-
+ The Sentinel CLI's configuration file can be used to control the
+ behavior of the simulator during apply and test operations.
+layout: docs
+---
+
+# Sentinel CLI Configuration File Syntax
+
+The Sentinel CLI's configuration file can be used to control the behavior
+of the simulator during [`apply`](/sentinel/commands/apply) and
+[`test`](/sentinel/commands/test) operations.
+
+## Usage
+
+The configuration file is used in different ways depending on what operation you
+are trying to execute.
+
+### Apply
+
+When using `sentinel apply`, configuration files that have the `.hcl`
+extension are loaded into a single configuration. This allows for configuration
+to be split across multiple files, which is useful for large policy sets. To
+supply a single configuration file, the `-config=FILE` flag can be used, where
+`FILE` is the path to the configuration file. This argument also allows for a
+`.json` configuration file to be supplied. The default is `sentinel.[hcl|json]`.
+
+See the [apply command](/sentinel/commands/apply) reference for more details.
+
+### Test
+
+When using `sentinel test`, each file matching `test//*.[hcl|json]` is a
+configuration file representing a single test case, where `` is the name
+of the policy being tested. Configure assertions for each [test
+case](#test-cases) using the test section.
+
+See the [test command](/sentinel/commands/test) reference for more details.
+
+#### Remote sources
+
+When declaring a `policy` or `module` block within the configuration, a
+`source` attribute must be supplied. This `source` should declare the location
+of the resource, which can be either a local file or a URL pointing to a remote
+file. Examples of local `source` attributes are detailed both in the
+[Policies](#policies) and [Modules](#modules) sections of this page. Below
+are a few examples of remote `source` attributes.
+
+Example:
+
+```hcl
+policy "foo" {
+ source = "git::https://github.com/hashicorp/sentinel-example.git//main.sentinel"
+ enforcement_level = "hard-mandatory"
+}
+```
+
+The above example will fetch the policy from the provided URL and place it into
+the cache ready for use. Be sure to read the
+[Remote Sources](/sentinel/configuration/remote-sources) page for an
+in-depth overview.
+
+## Configuration File Reference
+
+The format of the configuration file is either JSON or HCL. The available
+blocks are:
+
+- [`mock`](#mock-imports) - A mock import specification.
+- [`policy`](#policies) - Configuration for a [policy](/sentinel/writing).
+- [`import`](#imports) - Configuration for a [module](/sentinel/extending/modules), [standard import](/sentinel/imports), [plugin](/sentinel/extending/plugins) or
+ [static import](/sentinel/extending/static-imports).
+- [`global`](#globals) - Data that is inserted into the global scope.
+- [`param`](#parameters) - Values for [parameters](/sentinel/language/parameters) defined in the policy.
+- [`test`](#test-cases) - Test cases for the [test command](/sentinel/commands/test).
+- [`sentinel`](#sentinel) - Configuration to manage the Sentinel runtime
+
+### Mock Imports
+
+Mock imports allow running a policy with an
+[import](/sentinel/concepts/imports) that you may not have access to or is
+too difficult to run. For example, it can be easier to mock data for [Terraform
+Enterprise](https://www.terraform.io/docs/enterprise/sentinel/index.html)
+locally instead of having to run a workspace through a plan/policy check cycle.
+
+Mock imports are specified using the `mock` block, labelled by the import
+name that you want to mock. For example, if you wanted to mock the `time`
+import, you could create an entry with the `time` label pointing to the data
+you want to mock.
+
+The data can take one of two forms:
+
+#### Mocking static data
+
+Static data can be mocked directly via HCl using the `data` attribute on the
+`mock` block.
+
+Example:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ hour = 9
+ minute = 42
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"time\"\n\nmain = rule { time.now.hour is 9 }",
+ "mocks": {
+ "time": "now = { \"hour\": 9, \"minute\": 42 }"
+ }
+}
+```
+
+#### Mocking with Sentinel code
+
+There are some Sentinel types that raw data cannot mock, such as functions,
+and maps that don't have string keys. To mock this data, you can use
+a Sentinel file itself. In this case, you can set the `module` attribute to
+a file with the Sentinel code in it, relative to the current working directory in
+the event of `apply`, or relative to the configuration file location in the
+event of `test`.
+
+Note that `main` is not required in a mock data file.
+
+Example:
+
+```hcl
+mock "foo" {
+ module {
+ source = "mock-foo.sentinel"
+ }
+}
+```
+
+`mock-foo.sentinel` would contain:
+
+```sentinel
+bar = func() {
+ return "baz"
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"foo\"\n\nmain = rule { foo.bar() is \"baz\" }",
+ "mocks": {
+ "foo": "bar = func() { return \"baz\" }"
+ }
+}
+```
+
+### Policies
+
+The `policy` block allows for the the configuration of policies to assist
+with integrating into other commands.
+
+Each `policy` block has a label to identify the policy, with the block
+providing configuration of the policy. The available configuration options for
+policy are `source`, `enforcement_level` and an optional `params` attribute. The
+`source` key provides the location of the policy, while `enforcement_level` is
+currently used by integrations such as [Terraform Cloud](https://www.terraform.io/docs/cloud/sentinel/manage-policies.html#enforcement-levels).
+For more information on the `source` value, see [Policy and Module Sources](#policy-and-module-sources).
+
+The optional `params` attribute is used to provide values to [parameters](/sentinel/language/parameters)
+defined within the policy file.
+
+```hcl
+policy "foo" {
+ source = "foo.sentinel"
+ enforcement_level = "hard-mandatory"
+ params = {
+ "name" = "Sample"
+ }
+}
+```
+
+If `enforcement_level` is not supplied, it will fallback to the `advisory` level.
+
+### Imports
+
+Import configuration allows you to configure [modules](/sentinel/extending/modules),
+[standard imports](/sentinel/imports) and [import plugins](/sentinel/extending/plugins).
+
+The `import` is a multi-label block, with the first label determining the kind of import
+being configured. Currently the supported values for this label are:
+
+* [`"module"`](#import-modules) for configuring import modules
+* [`"plugin"`](#import-plugins) for configuring standard imports and import plugins
+* [`"static"`](#static-imports) for configuring static data files as imports
+
+#### Import Modules
+
+The `import "module"` block allows you to map a Sentinel import to a
+[module](/sentinel/extending/modules). The Sentinel CLI will parse the module
+source and make it available for evaluation with the policy.
+
+Each block has a label aligning to the name of the import, with
+the block set to the configuration object for the module. Currently, the only
+value within the configuration object is `source`, which points to the
+location of the module. For more information on the `source` value, see
+[Policy and Module Sources](#policy-and-module-sources).
+
+```hcl
+import "module" "foo" {
+ source = "modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "modules/bar.sentinel"
+}
+```
+
+Note when using [`sentinel test`](/sentinel/commands/test), module paths must be
+specified relative to the location of the configuration file.
+
+```hcl
+import "module" "foo" {
+ source = "../../modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "../../modules/bar/sentinel"
+}
+```
+
+See [Writing and Using a
+Module](/sentinel/extending/modules#writing-and-using-a-module) for more details
+on using a module with this configuration.
+
+### Import Plugins
+
+Import `"plugin"` configuration allows you to configure both [standard imports](/sentinel/imports)
+as well as [import plugins](/sentinel/extending/plugins) that can be used within
+a policy. If defining a custom import plugin, the Sentinel CLI will launch the
+plugin, connect to it, configure it, and execute it as needed by the policy.
+
+The available configuration attributes for an `import "plugin"` are:
+
+- `source` (string, required) - Path to the import plugin executable.
+- `args` (list of string, optional) - A list of arguments to pass to the
+ executable when starting it.
+- `env` (map of string to string, optional) - A set of environmental variables
+ to set when launching the plugin.
+- `config` (map, optional) - Configuration for the plugin itself. This is
+ specific to each individual plugin. Please reference the documentation for
+ the plugin for more information.
+
+Standard import example:
+
+```hcl
+import "plugin" "time" {
+ config = { "timezone": "Australia/Brisbane" }
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"time\"\n\nmain = time.now.zone_string is \"+10:00\"",
+ "mocks": {
+ "time": "now = { \"zone_string\": \"+10:00\" }"
+ }
+}
+```
+
+Custom plugin example:
+
+```hcl
+# flipper receives a boolean and returns the opposite
+import "plugin" "flipper" {
+ source = "/path/to/flipper"
+}
+```
+
+```json sentinel
+{
+ "policy": "import \"flipper\"\n\nmain = flipper.flip(false)",
+ "mocks": {
+ "flipper": "flip = func(input) { return not input }"
+ }
+}
+```
+
+#### Static Imports
+
+Static import configuration allows you to supply a data file and make
+it available to a policy via an import statement.
+
+The available configuration attributes for an `import "static"` are:
+
+- `source` (string, required) - Path to the static data file.
+- `format` (string, required) - The format of the static data. Currently, only
+ the "json" value is supported.
+
+Static import example:
+
+```hcl
+import "static" "people" {
+ source = "./data/people.json"
+ format = "json"
+}
+```
+
+```json sentinel
+{
+ "policy": "import \"people\"\n\nmain = length(people.names) > 0",
+ "mocks": {
+ "people": "names = [\"John Smith\", \"Jane Smith\"]"
+ }
+}
+```
+
+### Globals
+
+Global data is injected directly into the global scope of the policy.
+This can be used to simulate global data that may be injected by a
+Sentinel-enabled application.
+
+Global data is specified by the `global` key. The value is a map of
+variables to inject directly into the running policy.
+
+Example:
+
+```hcl
+global "time" {
+ value = {
+ now = {
+ day = 31
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass. Notice
+that we don't have to do any `import`. The values of `global` are
+injected directly into the global scope.
+
+```json sentinel
+{
+ "policy": "main = time.now.day == 31",
+ "globals": {
+ "time": {
+ "now": {
+ "day": 31
+ }
+ }
+ }
+}
+```
+
+### Parameters
+
+The `param` section allows you to supply values for the
+[parameters](/sentinel/language/parameters) found in a policy. Values
+entered here will satisfy required parameters in a policy, in addition to
+override any defaults. Note that any of the manual parameter value methods
+supported by `sentinel apply` will override the values set here.
+
+```hcl
+param "foo" {
+ value = "bar"
+}
+```
+
+### Test Cases
+
+Test cases specify the test cases for the [test command](/sentinel/commands/test).
+
+Tests are specified by the `test` key. The value of this is a map of
+string to boolean. The key is the name of a rule and the value is the
+expected value of that rule. If a rule is not specified, than any value is
+allowed.
+
+Example:
+
+```hcl
+test {
+ rules = {
+ main = true
+ days = []
+ }
+}
+```
+
+For the policy:
+
+```json sentinel
+{
+ "policy": "valid_days = rule {\n\tfilter days as d {\n\td is \"monday\"\n\t}\n}\n\nmain = rule { valid_days is empty }",
+ "globals": {
+ "days": []
+ }
+}
+```
+
+For more information, read about the [test command](/sentinel/commands/test).
+
+### Sentinel
+
+The `sentinel` block provides configuration specific to the Sentinel runtime.
+
+An example of how the `sentinel` block can be used to enable the [terraform](/sentinel/features/terraform)
+feature is as follows:
+
+```hcl
+sentinel {
+ features = {
+ terraform = true
+ }
+}
+```
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/configuration/overrides.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/configuration/overrides.mdx
new file mode 100644
index 0000000000..ca1a6842b1
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/configuration/overrides.mdx
@@ -0,0 +1,80 @@
+---
+page_title: Override Files
+sidebar_current: docs-configuration-overrides
+description: >-
+ The Sentinel CLI's configuration can be overriden using override files.
+ Override files merge additional settings into existing configuration.
+layout: docs
+---
+
+# Override Files
+
+As outlined in the configuration [overview](/sentinel/configuration#Apply),
+the normal behavior of Sentinel is to load all `.hcl` files into a single
+configuration object.
+
+In addition to appending multiple configuration files, Sentinel allows for the
+rare case of overriding configuration through override files. Override files
+are any files that match either `_override.{hcl,json}` or `override.{hcl,json}`.
+
+Sentinel will parse override files after it has loaded the primary
+configuration, and then process each override file in turn (in lexicographical
+order). All top level blocks other than [test](/sentinel/configuration#test-cases)
+require the block to already be defined in the primary configuration. It will
+then attempt to merge the override block into the existing configuration.
+
+## Example
+
+If you had a Sentinel configuration `sentinel.hcl` that contained the following:
+
+```hcl
+policy "main" {
+ source = "./main.sentinel"
+ enforcement_level = "advisory"
+}
+```
+
+And you created a file `override.hcl` that contained the following:
+
+```hcl
+policy "main" {
+ enforcement_level = "soft-mandatory"
+}
+```
+
+Sentinel will merge the contents of the override into the former, providing the
+following configuration:
+
+```hcl
+policy "main" {
+ source = "./main.sentinel"
+ enforcement_level = "soft-mandatory"
+}
+```
+
+## Merging Behavior
+
+The general rule, which applies in most cases, is:
+
+- A top-level block in an override file merges with a block in a normal
+ configuration file that has the same block header. The block header is the
+ block type and any quoted labels that follow it.
+- Within a top-level block, an attribute argument within an override block
+ replaces any argument of the same name in the original block.
+- Within a top-level block, any nested blocks within an override block replace
+ all blocks of the same type in the original block. Any block types that do not
+ appear in the override block remain from the original block.
+- The contents of nested configuration blocks are not merged.
+- The resulting merged block must still comply with any validation rules that
+ apply to the given block type.
+
+If more than one override file defines the same top-level block, the overriding
+effect is compounded, with later blocks taking precedence over earlier blocks.
+Overrides are processed in order first by filename (in lexicographical order)
+and then by position in each file.
+
+## Required Attributes
+
+It is important to note that all attributes inside an override file are
+considered to be optional, meaning that an override file can itself fail
+validation rules for block types.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/configuration/remote-sources.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/configuration/remote-sources.mdx
new file mode 100644
index 0000000000..b64e4d3abc
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/configuration/remote-sources.mdx
@@ -0,0 +1,165 @@
+---
+page_title: Remote Sources
+sidebar_current: docs-configuration-remote-sources
+description: >-
+ There are a number of options for formatting the URL provided to the
+ source attribute on policy and module blocks.
+layout: docs
+---
+
+# Remote Sources
+
+There are a number of options for formatting the URL provided to the
+`source` attribute on `policy` and `import "module"` blocks. This flexibility
+should solve most use cases and allow for reusable policies and modules.
+
+### Supported Protocols
+
+-> **NOTE:** All protocols are supported on the CLI, however each production
+Sentinel integration may not. Be sure to read the appropriate integration
+documentation to check the supported protocols.
+
+- Local filesystem
+- Git
+- Mercurial
+- HTTP
+- Amazon S3
+- Google GCP
+
+### Forced Protocols
+
+Due to the ambiguous nature of URLs, it is possible to force a particular
+protocol to be used during the fetch. To achieve this, simply prefix the url
+with the appropriate protocol as listed below:
+
+- `file` - Local filesystem
+- `git` - Git
+- `hg` - Mercurial
+- `http` or `https` - HTTP
+- `s3` - Amazon S3
+- `gcp` - Google GCP
+
+Example:
+
+```
+git::http://github.com/hashicorp/sentinel.git
+```
+
+The above would download the provided HTTP URL using the Git protocol.
+
+### Protocol Options
+
+In addition to some global options, there are options available to specific
+protocols to perform features such as authentication.
+
+#### Subdirectories / File Pathing
+
+When supplying URLs that point to a directory (eg. `git`), a subdirectory and
+file can be provided by suffixing the URL with `//` and the path. For example,
+if we have a git repository that has a policy, `main.sentinel` in the root, we
+could directly fetch the file by using the following:
+
+```
+git::https://github.com/hashicorp/sentinel-docs-example.git//main.sentinel
+```
+
+Or, if the file was nested in the `policies` folder:
+
+```
+git::https://github.com/hashicorp/sentinel-docs-example.git//policies/main.sentinel
+```
+
+#### Checksumming
+
+For downloads of any protocol, you can automatically verify a checksum. To
+checksum a file, append a `checksum` query parameter to the URL. The parameter
+value can be in the format of type:value or just value, where type is "md5",
+"sha1", "sha256", "sha512" or "file" . The "value" should be the actual
+checksum value or download URL for "file". When type part is omitted, type will
+be guessed based on the length of the checksum string.
+
+```
+./foo.sentinel?checksum=md5:b7d96c89d09d9e204f5fedc4d5d55b21
+
+./foo.sentinel?checksum=b7d96c89d09d9e204f5fedc4d5d55b21
+
+./foo.sentinel?checksum=file:./foo.txt.sha256sum
+```
+
+#### Unarchiving
+
+An `archive` query parameter can be supplied to explicitly state what format
+to attempt to extract, if required. The supported formats are:
+
+- `tar.gz` and `tgz`
+- `tar.bz2` and `tbz2`
+- `tar.xz` and `txz`
+- `zip`
+- `gz`
+- `bz2`
+- `xz`
+
+If a URL has an extension that matches one of the supported formats, it will
+use that format to unarchive.
+
+```
+./file.zip
+```
+
+The above would use the `zip` format to unarchive.
+
+```
+./path/to/file?archive=zip
+```
+
+Would force the `zip` format. If for some reason you required archiving to be
+disabled, this can also be achieved by using the `false` value.
+
+```
+./path/to/file?archive=false
+```
+
+#### Git (`git`)
+
+- `ref` - The Git ref to checkout. This is a ref, so it can point to a commit
+ SHA, a branch name, etc. If it is a named ref such as a branch name, it will
+ be updated to the latest on each get.
+- `sshkey` - An SSH private key to use during clones. The provided key must be
+ a base64-encoded string. For example, to generate a suitable sshkey from a
+ private key file on disk, you would run base64 -w0 \.
+ **Note:** Git 2.3+ is required to use this feature.
+- `depth` - The Git clone depth. The provided number specifies the last `n`
+ revisions to clone from the repository.
+
+The git getter accepts both URL-style SSH addresses like
+`git::ssh://git@example.com/foo/bar`, and "scp-style" addresses like
+`git::git@example.com/foo/bar`. In the latter case, omitting the `git::` forced
+prefix is allowed if the username prefix is exactly git@.
+
+The "scp-style" addresses cannot be used in conjunction with the `ssh://`
+scheme prefix, because in that case the colon is used to mark an optional port
+number to connect on, rather than to delimit the path from the host.
+
+#### Mercurial (`hg`)
+
+- `rev` - The Mercurial revision to checkout.
+
+#### HTTP (`http` or `https`)
+
+Basic Authentication
+
+To use HTTP basic authentication, simply prepend `username:password@` to the
+hostname in the URL such as `https://Aladdin:OpenSesame@www.example.com/index.html`.
+All special characters, including the username and password, must be URL
+encoded.
+
+#### S3 (`s3`)
+
+The following query parameters are available and will take priority when
+authenticating against an S3 bucket.
+
+- `aws_access_key_id` - AWS access key.
+- `aws_access_key_secret` - AWS access key secret.
+- `aws_access_token` - AWS access token if this is being used.
+- `aws_profile` - Use this profile from local ~/.aws/ config. Takes priority
+ over the other three.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/consul.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/consul.mdx
new file mode 100644
index 0000000000..7a3aaaa697
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/consul.mdx
@@ -0,0 +1,47 @@
+---
+page_title: Consul and Sentinel
+sidebar_current: docs-app-consul
+description: >-
+ Consul Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies are applied during writes to
+ the KV Store.
+layout: docs
+---
+
+# Consul
+
+[Consul Enterprise](https://www.hashicorp.com/products/consul/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies are applied during writes to the KV Store.
+
+Sentinel policies have access to the key/value being written. They can be used to
+allow or deny the modification. The information that Sentinel policies have access
+to will expand over time.
+
+The Consul integration with Sentinel is documented in depth in the
+[Consul Enterprise documentation](https://www.consul.io/docs/guides/sentinel.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Input validation depending on the name of the key.**
+
+```sentinel
+main = rule { valid_key() }
+
+required = [
+ ["port", "\\d+"], # ports must be integers
+ ["name", "\\w+"], # name must be a word
+]
+
+valid_key = func() {
+ for required as v {
+ if key is v[0] {
+ return value matches v[1]
+ }
+ }
+
+ return false
+}
+```
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/extending/index.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/extending/index.mdx
new file mode 100644
index 0000000000..a39cb0384d
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/extending/index.mdx
@@ -0,0 +1,52 @@
+---
+page_title: Extending Sentinel
+sidebar_current: docs-extending
+description: Learn how to create your own Sentinel imports to extend Sentinel's functionality.
+layout: docs
+---
+
+# Extending Sentinel
+
+-> **NOTE:** Sentinel's extensibility is currently undergoing large
+improvements - watch this space for future updates!
+
+Sentinel can be extended by writing additional
+[imports](/sentinel/language/imports). These imports can bring new functionality
+to Sentinel by allowing access to new data or by the addition of new functions.
+This section is designed to show you how to accomplish this extensibility and
+describe how it works.
+
+Currently, the only usable way to add additional imports is via
+[modules](#modules). While [plugins](#plugins) currently work under the Sentinel
+CLI, no Sentinel integration currently supports their use.
+
+-> You may also be interested in advanced details on [import
+internals](/sentinel/extending/internals).
+
+## Modules
+
+Modules allow you to re-use Sentinel code as an import. Modules are loaded
+external of policies and mapped to imports. This is usually configured via a
+file such as the [CLI configuration file](/sentinel/configuration).
+
+See the [modules](/sentinel/extending/modules) section for more details.
+
+## Plugins
+
+-> **NOTE:** Plugins are currently only supported on the CLI and not in any
+production Sentinel integration, with the exception of existing configuration
+in [Nomad](https://www.nomadproject.io/docs/configuration/sentinel).
+
+Imports can also be implemented as plugins when Sentinel code itself is too
+restrictive to represent the import, or if you need access to features not
+normally available to the Sentinel runtime.
+
+See the [plugins](/sentinel/extending/plugins) section for more details.
+
+## Static Imports
+
+Static imports provide support for loading data files into the Sentinel runtime,
+making them available as an import.
+
+See the [static imports](/sentinel/extending/static-imports) section for more
+details.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/extending/internals.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/extending/internals.mdx
new file mode 100644
index 0000000000..11be9d3d67
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/extending/internals.mdx
@@ -0,0 +1,252 @@
+---
+page_title: >-
+ Extending Sentinel: Internals
+sidebar_current: docs-extending-internals
+description: This Section covers some details about Sentinel's import internals.
+layout: docs
+---
+
+# Extending Sentinel: Internals
+
+-> **Advanced Topic!** This page covers low-level technical details of Sentinel.
+You don't need to understand these details to effectively use Sentinel. The
+details are documented here for those who wish to learn about them.
+
+This section covers some of the internal details of Sentinel's import system.
+The goal of this section is to help remove notion of "magic" from Sentinel, to
+allow you to trust and understand what Sentinel is doing with imports, and also
+to help practitioners and developers alike understand the differences between
+the ways that imports can be loaded into Sentinel.
+
+## Language and Runtime
+
+Understanding the import system in Sentinel generally requires understanding of
+two key concepts within Sentinel: the _language_, and the _runtime
+implementation_ of the language.
+
+The Sentinel language and the rules governing it are detailed in the [Sentinel
+Language Specification](/sentinel/language/spec). A runtime implementation must
+conform to the rules laid out in the specification at a minimum. However, to
+meet the design goals of a particular implementation, the runtime may implement
+features on top of the language. The subtle implementation details within the
+runtime may also vary while still being compliant with the specification.
+
+The core runtime included within the [Sentinel CLI](/sentinel/commands) is
+sometimes referred to as the _reference implementation_ of the Sentinel
+language. This runtime is also the main runtime embedded within each
+Sentinel-enabled HashiCorp product such as Terraform Cloud and Enterprise, Vault
+Enterprise, Nomad Enterprise, and Consul Enterprise. The runtime includes an API
+to allow these integrations implement Sentinel in as robust or as restrictive of
+a way as possible, so depending on the implementation, your experience may vary.
+
+## Sentinel's Import Model
+
+The concept of Imports within Sentinel is a _language_ feature. You can see the
+exact rules governing imports within the
+[Imports](/sentinel/language/spec#imports) section of the specification.
+
+Within the runtime, there are currently two major classifications of imports:
+
+- **Modules:** The mapping of Sentinel code to an import, essentially mapping a
+ scope of values to a particular package.
+- **Binary Imports:** A grouping of embedded and external plugins written using
+ the Sentinel SDK. These are comprised of internal or embedded imports (usually
+ the [standard imports](/sentinel/imports) and imports included by an
+ integration) and import plugins.
+
+Below is a diagram explaining in brief the relationship between the langauge
+and runtime features.
+
+
+
+This kind of topology ultimately minimizes the visibility of implementation
+internals to the actual policy language. This allows us to keep the Sentinel
+language itself simple and keep concerns such as module or plugin management,
+validation, and configuration out of the language. These kinds of things would
+impact the readability of a policy, possibly present security challenges, and
+would ultimately be of no use to more minimal embedded applications.
+
+### Implementation Differences Between Import Types
+
+As one would imagine, both modules and binary imports are loaded in much
+different ways, and the methods that they expose data to Sentinel differ as
+well. The effect is generally the same however across both, and we take care to
+ensure that both modules and binary imports behave as close as possible to each
+other, however there are some subtle differences:
+
+- Modules currently support the exporting of rules, but do not support function
+ calls with object receiver data (as seen in some standard imports such as
+ [`decimal`](/sentinel/imports/decimal), [`http`](/sentinel/imports/http), and
+ [`time`](/sentinel/imports/time)). This will be coming in a later release.
+- Binary imports cannot export rules. However, they do support object receiver
+ data (see [`framework.New`](/sentinel/extending/plugins#framework-new) in
+ [Extending Sentinel: Plugins](/sentinel/extending/plugins)).
+
+## Modules
+
+_Modules_ are essentially Sentinel code that is intended to be re-used in multiple
+policies as an import.
+
+The mechanism of module loading is fairly straightforward: during initialization
+of the Sentinel runtime, modules are parsed along with policies. The parsed
+_abstract syntax tree_ (AST) for each module is loaded into the runtime under
+the specified _import path_. These are then passed to the lower-level
+interpreter during the evaluation of each policy.
+
+As with policy code, during evaluation, a module's AST is walked to execute the
+code within that specific module. The module data is then stored within an
+specific scope mapped to the import name. The module data can then be accessed
+through the import via selector expressions and function calls (e.g.,
+`foo.some_map` or `foo.some_func()` for a module loaded via `import "foo"`).
+
+The AST for a module is only walked if a policy imports it, and it is _only
+walked once_. That means if a policy and a module import the same module, or a
+policy imports the same module twice (using [import
+aliases](/sentinel/language/imports#aliases)), those instances will share state.
+If you call a function that alters a singleton variable within the module, that
+singleton will be modified for all instances of the import.
+
+-> **Fun fact!** When you [mock an import with Sentinel
+code](/sentinel/configuration#mocking-with-sentinel-code), you are actually
+using a module. The module is loaded with a flag that allows it to override an
+existing import, which would normally give a runtime error.
+
+### Map and List Cloning for Module Calls
+
+It's also worthwhile noting that map and list values are cloned for modules.
+
+Consider the case where you have a module with a map singleton.
+
+```sentinel
+// modules/foo.sentinel
+a_map = {"a": "b"}
+```
+
+Normally, assignment to the singleton, even when using an index expression, is
+blocked, so `foo.a_map["c"] = "d"` would not pass semantic checking.
+
+However, even when you try to do assignment via an intermediary, you will still
+be only modifying the copy, and the original will not be affected:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+a_map_copy = foo.a_map
+a_map_copy["c"] = "d" // Only modifies copy, does not modify module singleton
+print(foo.a_map) // Still only prints {"a": "b"}
+```
+
+This is to ensure that modules are consistent with binary imports in how return
+data is handled, and the expectation that most non-object data within an import
+is read-only, with the exception of modification of singleton data via
+functions. As return of complex object and collection data in a binary import is
+always via copy, it's not possible to modify any value elements within the
+import in a similar fashion.
+
+There are also some other implications of this cloning that are of note:
+
+- Rules are not cloned, either within a list or map, or by themselves. This is
+ to ensure that [memoization](/sentinel/language/rules#lazy-and-memoized)
+ functions correctly. As only a module can export rules at this time, there is no
+ parity for this in binary imports.
+- Functions are _not_ cloned. This mainly has implications for scope - for
+ example, if you assign a function to another value, or assign an object to a
+ value that has a method that utilizes a global module variable, those calls will
+ reference and/or modify those values.
+
+Functions, while represented differently in binary imports, generally follow the
+same logic here - as they would be invoked in the same package within the binary
+import, any singleton values they accessed there would be affected in a similar
+fashion. As rules are pseudo-functions, this generally makes sense here too.
+
+## Binary Imports
+
+_Binary imports_ is a grouping referring to all imports that are designed with
+the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). These can be
+either internally embedded, or executed via a plugin.
+
+All imports found in the [standard library](/sentinel/imports) are binary
+imports, embedded internally.
+
+The main difference between internally embedded imports and external plugins is
+whether or not the runtime needs to execute a plugin binary as part of
+[lifecycle management](#lifecycle), and whether or not it needs to
+[communicate](#communication) over RPC. Internal binary imports do not require
+these steps, so their execution model is somewhat simpler; however, technically,
+they still are the same as external plugins in terms of design model and value
+conversion. Most standard imports are even tested using the [SDK test
+suite](/sentinel/extending/plugins#testing), which builds the import as an
+external plugin.
+
+The remainder of this section discusses topics mostly relevant to external
+plugins. A large amount of technical detail regarding binary import development
+(also applicable to embedded binary imports) can be seen on the
+[Plugins](/sentinel/extending/plugins) page.
+
+-> **NOTE:** At this time, plugins can only be written for the Sentinel CLI, and
+cannot be used with any of Sentinel's integrations.
+
+### Plugin Basics
+
+Sentinel plugins are built on top of the HashiCorp [go-plugin
+system](https://github.com/hashicorp/go-plugin). This is the same plugin system
+powering all pluggable HashiCorp tools such as
+[Terraform](https://www.terraform.io), [Vault](https://www.vaultproject.io), and
+more. go-plugin is a system that has been used in production for millions of
+users for over 5 years.
+
+Plugins are executable binaries. Sentinel is responsible for launching and
+managing the lifecycle of plugins. Once a plugin is running, Sentinel
+communicates with the plugin via [gRPC](https://grpc.io/). The [protocol is open
+source](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/import.proto)
+to allow anyone to write a Sentinel plugin.
+
+### Lifecycle
+
+Sentinel launches plugins and is responsible for plugin lifecycle.
+
+The runtime optimizes for latency by launching the plugin as soon as it is
+configured, rather than when a policy requires it. This ensures that the plugin
+is ready to be used immediately. A plugin is only closed when Sentinel is
+reconfigured to no longer allow that plugin or if the Sentinel-enabled
+application is closing.
+
+When a plugin is removed from the configuration, Sentinel may wait to shut down
+the plugin until all currently executing policies that are using that plugin
+complete. New policy executions will not be allowed to use the old plugins.
+
+Plugins are automatically restarted if they shut down unexpectedly. Policies
+that were executing while this happens may fail. The Sentinel system is
+improving to more gracefully understand locations where it is safe to retry an
+execution.
+
+### Communication
+
+An [initial
+handshake](https://github.com/hashicorp/go-plugin/blob/master/docs/internals.md)
+is done via stdout to determine the main communication location. Following the
+handshake, communication will either occur on a local Unix domain socket or via
+a local-only TCP socket. This communication is done mostly over the low-level
+[`Get`](https://github.com/hashicorp/sentinel-sdk/blob/2b4db07dd12b55142f0c016f301af80aa4422520/proto/import.proto#L12)
+RPC call.
+
+#### Value Conversion
+
+As can generally be seen in [Developing an Import
+Plugin](/sentinel/extending/plugins#developing-an-import-plugin), the Sentinel
+type system is not exposed to binary imports. While explicit values for null and
+undefined exist, values are mostly converted over the wire from their Go values
+to their Sentinel equivalents.
+
+This has a few implications:
+
+- As discussed in [Map and List Cloning for Module
+ Calls](#map-and-list-cloning-for-module-calls), Map and list data returned from
+ binary import value lookups or function calls is always cloned. These can be
+ freely modified without affecting anything within the binary import.
+- It's currently impossible to represent certain Sentinel types such as
+ [rules](/sentinel/language/rules) within a binary import. This is not a major
+ issue at this point in time as practitioners generally do not look to binary
+ imports for rules; we may examine this as a later time if this situation
+ changes.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/extending/modules.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/extending/modules.mdx
new file mode 100644
index 0000000000..9846d263e5
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/extending/modules.mdx
@@ -0,0 +1,135 @@
+---
+page_title: >-
+ Extending Sentinel: Modules
+sidebar_current: docs-extending-modules
+description: >-
+ Modules allow you to re-use Sentinel code as an import.
+layout: docs
+---
+
+# Extending Sentinel: Modules
+
+Modules allow you to re-use Sentinel code as an import. This allows for the
+export of any general construct that Sentinel supports, including values,
+functions, and even rules. As such, it's a great way to package code that is
+useful across multiple policies, reducing boilerplate and making final policy
+code simpler.
+
+You may want to use modules for:
+
+- **Writing a simple re-usable helper library.** If you mostly know Sentinel or
+ have helpers that you have written in Sentinel, moving these helpers to a module
+ will offer a low-friction way of facilitating their re-use.
+- **Writing re-usable rules.** If you have a set of
+ [rules](/sentinel/language/rules) you know you want to use across multiple
+ policies, bundling them into a module is a great way of abstracting the logic
+ away.
+- **Abstracting existing Sentinel imports.** Using a module is a great way to
+ extend existing Sentinel imports by abstracting the common functionality that
+ you use within an import to your own workflow.
+
+This page describes how you can use modules within the context of the Sentinel
+CLI. For instructions for a particular integration, see the documentation for
+that product.
+
+-> **Not all Sentinel-enabled applications support modules.** Please refer
+to the documentation of the specific Sentinel-enabled application. Some
+applications disable modules for security reasons.
+
+## Configuring a Module
+
+A module is configured within the Sentinel CLI by adding an entry for it in
+within the `imports` section of the [CLI configuration
+file](/sentinel/configuration). The key for each entry specifies the path that
+the module is imported as.
+
+```hcl
+import "module" "foo" {
+ source = "modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "modules/bar.sentinel"
+}
+```
+
+In this example, we have two modules:
+
+- Import path `foo`, being loaded from the code within `modules/foo.sentinel`.
+- Import path `bar`, being loaded from the code within `modules/bar.sentinel`.
+
+See the [Import Modules](/sentinel/configuration#import-modules) section within
+the CLI configuration file syntax page for more details.
+
+### Remote Modules
+
+In addition to loading from a local source, modules can be loaded from a remote
+location. This can increase reusability and allow for sharing modules across
+multiple configurations.
+
+### Testing Using Modules
+
+As with [mocks](/sentinel/configuration#mocking-with-sentinel-code), modules
+are loaded relative to the configuration file location when using `sentinel test`. As such, you need to account for this in your test configuration files:
+
+```hcl
+import "module" "foo" {
+ source = "../../modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "../../modules/bar.sentinel"
+}
+```
+
+This could be a test file for a policy (example: `policy.sentinel`), residing
+under the correct test file directory for this policy (example:
+`test/policy/pass.json`).
+
+## Writing and Using a Module
+
+Writing a module is nearly the same as writing a Sentinel policy, except for the
+following two exceptions:
+
+- Modules do not require [`main`](/sentinel/writing/basic#the-simplest-policy)
+ to be present. It can be, but it will not carry any extra significance as it
+ does within a policy.
+- [`param`](/sentinel/language/parameters) declarations cannot be present in a module. If they are encountered,
+ a runtime error is given.
+
+Once you have a complete module ready for use and configured, using the module
+is as simple as importing it.
+
+Building on the above [configuration example](#configuring-a-module), we can
+export a simple test function in the module `foo` by adding this code to
+`modules/foo.sentinel`:
+
+```sentinel
+// modules/foo.sentinel
+hello = func() {
+ print("hello world!")
+ return undefined
+}
+```
+
+We can then import it within our policy and use it:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+// prints "hello world" in the trace
+foo.hello()
+
+main = true
+```
+
+Note that modules are considered [imports](/sentinel/language/spec#imports) by
+the Sentinel runtime and as such conform to the specification. This means that
+you cannot directly assign values to anything behind a module scope. You can,
+however, use functions to change state.
+
+-> **NOTE:** At this time, modules do not support object receiver data in the
+way that some imports do, like [`time`](/sentinel/imports/time),
+[`decimal`](/sentinel/imports/decimal), and [`http`](/sentinel/imports/http).
+Support for this will be coming in later versions of Sentinel.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/extending/plugins.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/extending/plugins.mdx
new file mode 100644
index 0000000000..e80de5d4b1
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/extending/plugins.mdx
@@ -0,0 +1,521 @@
+---
+page_title: >-
+ Extending Sentinel: Plugins
+sidebar_current: docs-extending-plugins
+description: >-
+ Plugins allow you to write imports as standalone executables, allowing you to
+ extend Sentinel in ways not normally possible with policy code alone.
+layout: docs
+---
+
+# Extending Sentinel: Plugins
+
+-> **NOTE:** Plugins are currently only supported on the CLI and not in any
+production Sentinel integration, with the exception of existing configuration
+in [Nomad](https://www.nomadproject.io/docs/configuration/sentinel).
+
+Plugins allow you to write imports as standalone executables, allowing you to
+extend Sentinel in ways not normally possible with policy code alone.
+
+You might want to write or use a plugin when you need to:
+
+- **Use a provider or API integration for Sentinel.** In this case, you will
+ probably be working with a provider SDK, or other libraries that will be making
+ complex network requests to external services. Sentinel only provides limited
+ capabilities for these kinds of operations in the standard library, so writing
+ this kind of import as a module is not optimal.
+- **Introduce novel functionality not currently supported by Sentinel.**
+ Sentinel's policy language is limited by design to encourage simple policies,
+ reduce its runtime footprint, and to keep it efficient and safe. This will
+ mean that some functionality may be difficult or impossible to express as
+ Sentinel code, and would require a plugin to write.
+- **Extend a complex module.** Additionally, modules are restricted to a single
+ file of Sentinel code, so these will naturally become more and more unwieldy as
+ you continue to add to them. Eventually, there might be a time where a plugin is
+ better suited for the functionality, especially if it's a general-purpose
+ library.
+
+This section details how import plugins are currently configured in the Sentinel
+CLI, and some detail on how they are written. As we continue to build on the
+ability to use import plugins, we will extend this section with more details.
+
+-> **Not all Sentinel-enabled applications will support plugins.** Some
+applications will disable plugins for security reasons. For those that do enable
+plugins, the method to configure plugins will vary.
+
+## Installing Plugins
+
+Sentinel plugins are standalone executables. Sentinel-enabled applications such
+as the CLI launch these plugins and communicate with them via
+[RPC](https://en.wikipedia.org/wiki/Remote_procedure_call). To install a plugin,
+the first step is to download the plugin executable.
+
+After downloading the plugin, you must add it in the CLI configuration file,
+setting its name, path, and any arguments, environment variables, or
+configuration. An example is below:
+
+```hcl
+import "plugin" "custom_time" {
+ source = "/path/to/sentinel-import-time"
+ config = { "fixed_time": 1504155600 }
+}
+```
+
+-> **NOTE:** We are using "custom_time" as standard import paths cannot be
+overriden. See [Configuration File Syntax](/sentinel/configuration#imports) for
+more details.
+
+After configuring the plugin, it will be available on the next `sentinel apply`
+or `sentinel test`.
+
+For more details on configuration, see the
+[plugins](/sentinel/configuration#plugins) section of the [CLI configuration
+file syntax](/sentinel/configuration).
+
+## Debugging Plugins
+
+The Sentinel CLI logs when it launches, configures, and closes a plugin. The
+plugin may also log additional information when it is running.
+
+To debug issues with a plugin, you can usually refer to these logs. Depending on
+the plugin configuration, the logs you see may vary - refer to the plugin
+documentation on how to enable logs for a particular plugin.
+
+## Developing an Import Plugin
+
+Anyone can develop a Sentinel import plugin using the [Sentinel
+SDK](https://github.com/hashicorp/sentinel-sdk). The SDK contains a high-level
+framework for writing plugins in [Go](https://golang.org) including a test
+framework. Additionally, the SDK contains the low-level [gRPC protocol buffers
+definition](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/plugin.proto)
+for writing plugins in other languages.
+
+The rest of this page will document how to write a new Sentinel plugin in Go
+using the Sentinel SDK and the high-level framework provided. You are expected
+to already know the basics of Go and have a properly configured Go installation.
+
+Throughout this page, we'll be developing a simple plugin to access time values.
+
+-> **NOTE:** The example here is not reflective of the actual implementation of
+the [`time`](/sentinel/imports/time) standard import, so make sure to not
+get the two confused.
+
+### Plugin Framework
+
+The Sentinel SDK provides a high-level
+[framework](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework)
+for writing plugins. We recommend using this framework.
+
+#### Basic Concepts
+
+This framework works by implementing the correct Go interfaces.
+
+As a general overview:
+[`framework.Plugin`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Plugin)
+implements the
+[`sdk.Plugin`](https://godoc.org/github.com/hashicorp/sentinel-sdk#Plugin)
+interface and therefore is a valid import plugin. You must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface to configure the plugin. The root may then delegate nested access to
+various
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+implementations. Other interfaces are implemented to provide functionality past
+what is provided by the basic `framework.Namespace` implementation - these are
+documented below.
+
+This all may sound like a lot of interfaces, but each interface typically only
+requires a single function implementation and you're only required to implement
+a single namespace (`framework.Namespace`).
+
+As we move on, we'll use real examples to make this clear.
+
+#### Implementing framework.Root
+
+To begin, you must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface. This is the interface representing the root of your plugin. The root
+itself must be implement
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace),
+which allows values to be accessed. Note that the root can optionally implement
+other interfaces, but they're more advanced and omitted here for simplicity.
+
+For our custom time example, our root may look like this:
+
+```sentinel
+type root struct {
+ time time.Time
+}
+
+// framework.Root impl.
+func (m *root) Configure(raw map[string]interface{}) error {
+ if _, ok := raw["timestamp"]; !ok {
+ raw["timestamp"] = time.Now().Unix()
+ }
+
+ v := raw["timestamp"]
+ timestamp, ok := v.(int)
+ if !ok {
+ return fmt.Errorf("invalid timestamp type %T", v)
+ }
+
+ m.time = time.Unix(timestamp, 0).UTC()
+ return nil
+}
+
+// framework.Namespace impl.
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "minute":
+ return m.time.Minute(), nil
+ }
+
+ return nil, nil
+}
+```
+
+This example implements `framework.Root` and `framework.Namespace` and would
+enable the following within a Sentinel policy once the completed plugin is
+installed.
+
+```sentinel
+import "custom_time"
+
+print(custom_time.minute)
+main = true
+```
+
+#### framework.Namespace
+
+The
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+interface implements a namespace of values. You saw above that
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+must itself be a namespace. However, a namespace `Get` implementation may
+further return namespaces to access nested values.
+
+This enables behavior such as `custom_time.month.string` vs. `custom_time.month.index`.
+Notice each of these examples accesses a nested value within `month`. This can
+be modeled as namespaces within the framework.
+
+In the example below, we return a new namespace for `month` to do just this:
+
+```sentinel
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "month":
+ return &namespaceMonth{Month: m.time.Month()}, nil
+ }
+
+ // ...
+}
+
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil nil
+}
+```
+
+#### Primitive Types and Structs
+
+A namespace may also return any primitive Go type as well as structs. The
+framework automatically exposes primitive values as you would expect. For
+structs, exported fields are lowercased and exposed to the policies. The
+`sentinel` struct tag can be used to control how Sentinel can access the field.
+
+In the example below, we expose a struct directly from the root namespace:
+
+```sentinel
+type Location struct {
+ Name string
+ TimeZone string `sentinel:"time_zone"`
+}
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "location":
+ return &Location{Name: "somewhere", TimeZone: "some zone"}, nil
+ }
+
+ // ...
+}
+```
+
+This makes the following values work within a Sentinel policy:
+`time.location.name`, `time.location.time_zone`.
+
+If a field has an empty `sentinel` struct tag (example: `sentinel:""`),
+then that field will not be accessible from a policy.
+
+#### Optional Interfaces
+
+The following are optional interfaces that can be implemented on top of
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace).
+All of these interfaces can be implemented at any level, except for
+[`framework.New`](#framework-new), which only works at the root level.
+
+##### `framework.Call`
+
+The
+[`framework.Call`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Call)
+interface can be implemented to support function calls.
+
+Implementing this interface is very similar to attribute access, except instead
+of returning the attribute value, you return a function. The framework uses Go
+reflection to determine the argument and result types and calls it. If the
+argument types do not match or the signature is otherwise invalid, an error is
+returned.
+
+For example, let's implement a function to add months to the current month:
+
+```sentinel
+func (m *root) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *root) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.time.AddDate(0, n, 0).Month()}
+}
+```
+
+You can now call the function. If today was in the month of September,
+the policy below would pass:
+
+```sentinel
+import "custom_time"
+
+main = custom_time.add_month(4).string == "January"
+```
+
+##### `framework.Map`
+
+The optional
+[`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+interface allows an alternative method to to present a namespace back to a
+Sentinel policy as a map.
+
+`framework.Map` is best paired with
+[`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys),
+which allows you to quickly fetch the necessary data via `Get` calls on the
+namespace:
+
+```
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil, nil
+}
+
+func (m *namespaceMonth) Map() (map[string]interface{}, error) {
+ return framework.MapFromKeys(m, []string{"string", "index"})
+}
+```
+
+##### `framework.New`
+
+The optional
+[`framework.New`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#New)
+interface can be implemented on a root namespace to add _methods_ to your
+namespaces.
+
+Data returned from a namespace is memoized and returned as a map, and is
+normally not callable - to call a function to operate on the data, you would
+need to create a new namespace from the top-level of the plugin, and then make a
+function call on the result within the same expression.
+
+Let's consider the [root example](#implementing-framework-root). Let's say,
+instead of hard-coding the time value at configuration, we wanted to load it
+on-demand using a `time.now` key, and allow `add_month` to be callable on that
+value. Our root and de-coupled time namespace would now look like:
+
+```
+type root struct{}
+
+func (m *root) Configure(raw map[string]interface{}) error { return nil }
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "now":
+ return &namespaceTime{Time: time.Now()}
+ }
+
+ return nil
+}
+
+func (m *root) New(data map[string]interface{}) (framework.Namespace, error) {
+ if v, ok := data["unix"]; ok {
+ if t, ok := v.(int64); !ok {
+ return &namespaceTime{Time: time.Unix(t, 0)}
+ }
+
+ return nil, fmt.Errorf("expected timestamp to be int64, got %T", v)
+ }
+
+ return nil, nil
+}
+
+type namespaceTime struct {
+ Time time.Time
+}
+
+func (m *namespaceTime) Get(key string) interface{} {
+ switch key {
+ case "unix":
+ return m.Time.Unix()
+
+ // case ...
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) Get(key string) (interface{}, error) {
+ return framework.MapFromKeys(m, []string{"unix", "..."})
+}
+
+func (m *namespaceTime) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.Time.AddDate(0, n, 0).Month()}
+}
+```
+
+Via this example, we can now create and assign a `namespaceTime` using `t = custom_time.now`, and then call `t.add_month(months_to_add)` to return a
+`namespaceMonth` for the corresponding month.
+
+Methods on a namespace can also _modfiy_ the receiver data. So you can, for
+example, add a method named `increment_month` that takes no arguments, but
+increments the time stored in `namespaceTime.Time` by a month. Subsequent
+statements using the receiver would see the new value.
+
+Note that there are some restrictions and guidelines that you should take into
+account when using `framework.New`:
+
+- Only data that makes it back to a policy can be used as receiver data to
+ instantiate new namespaces. As such it's recommended to make use of
+ [`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+ and
+ [`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys)
+ to return all of the attribute data you need to construct new objects.
+- `framework.New` is only supported on the root namespace and has no effect on
+ namespaces below the root. Check all possible cases of receiver data within
+ the top-level `New` function and return the appropriate namespace based on the
+ input data.
+- Do not return general errors from your `New` method. When an unknown lookup is
+ made off of memoized data, it will hit your `New` method for possible
+ instantiation and key calls. This will ensure `undefined` is correctly
+ returned for these cases.
+- `framework.New` is designed to add utility to plugins where calling methods on
+ a value is more intuitive than just making a function call, or just returning
+ all of a data set as a memoized map. Use it sparingly and avoid using it on
+ recursively complex data sets.
+
+### Testing
+
+The SDK exposes a testing framework to verify your plugin works as
+expected. This test framework integrates directly into `go test` so it
+is part of a familiar workflow.
+
+The test framework works by dynamically building your plugin and
+running it against the `sentinel` CLI to verify it behaves as expected.
+This ensures that your plugin builds, your plugin communicates via RPC
+correctly, and that the behavior within a policy is also correct.
+
+The example below shows a complete ready table-driven test for our time plugin.
+You can run this with a normal `go test`.
+
+```sentinel
+package main
+
+import (
+ "os"
+ "testing"
+
+ "github.com/hashicorp/sentinel-sdk"
+ plugintesting "github.com/hashicorp/sentinel-sdk/testing"
+)
+
+func TestMain(m *testing.M) {
+ exitCode := m.Run()
+ plugintesting.Clean()
+ os.Exit(exitCode)
+}
+
+func TestPlugin(t *testing.T) {
+ cases := []struct {
+ Name string
+ Data map[string]interface{}
+ Source string
+ }{
+ {
+ "month",
+ map[string]interface{}{"timestamp": 1495483674},
+ `main = subject.month.string == "September"`,
+ },
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.Name, func(t *testing.T) {
+ plugintesting.TestPlugin(t, plugintesting.TestPluginCase{
+ Config: tc.Data,
+ Source: tc.Source,
+ })
+ })
+ }
+}
+```
+
+### Building Your Plugin
+
+To build your plugin, you must first implement the `main` function.
+The main function should just use the `rpc.Serve` method to serve the
+plugin over the Sentinel RPC layer:
+
+```sentinel
+package main
+
+import (
+ "github.com/hashicorp/sentinel-sdk"
+ "github.com/hashicorp/sentinel-sdk/rpc"
+)
+
+func main() {
+ rpc.Serve(&rpc.ServeOpts{
+ PluginFunc: func() sdk.Plugin {
+ return &framework.Plugin{Root: &root{}}
+ },
+ })
+}
+```
+
+You can then build this using `go build`. The output can be named
+anything. Once your plugin is built, [install it](#installing-plugins)
+and try it out!
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/extending/static-imports.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/extending/static-imports.mdx
new file mode 100644
index 0000000000..cb51beb5d8
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/extending/static-imports.mdx
@@ -0,0 +1,48 @@
+---
+page_title: >-
+ Extending Sentinel: Static Imports
+sidebar_current: docs-extending-static-imports
+description: >-
+ Static imports allow you to write imports that use static data files.
+layout: docs
+---
+
+# Extending Sentinel: Static Imports
+
+Static imports allow you to write imports that use static data files. This can
+provide methods of consuming data in ways that are not available via
+[modules](./modules) or [plugins](./plugins).
+
+### Configuring a Static Import
+
+The [configuration page](/sentinel/configuration#static-imports) details how to
+configure static imports.
+
+### Usage
+
+Using a static import is not that different to using a plugin or module. The
+import must first be introduced to the policy via an import statement.
+
+```sentinel
+import "people"
+```
+
+From here, the static data can be used throughout the policy as you would any
+normal Sentinel value.
+
+```sentinel
+admins = filter people as person {
+ person.role is "Admin"
+}
+```
+
+One differentiator for static imports is that you can directly reference the
+import without the use of selectors. For example, to print the value;
+
+```sentinel
+print(people)
+```
+
+This is due to the static data being directly loaded and converted into a
+Sentinel value, ready for use. Static imports, like modules and plugins, cannot
+be assigned a new value.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/features/index.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/features/index.mdx
new file mode 100644
index 0000000000..e035be978c
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/features/index.mdx
@@ -0,0 +1,28 @@
+---
+page_title: Features
+sidebar_current: docs-features
+description: Learn how features can enhance the Sentinel runtime by enabling further capabilities.
+layout: docs
+---
+
+# Features
+
+Features are a set of capabilities that enhance the runtime experience. They
+are enabled through the [`sentinel`](/sentinel/configuration#sentinel) block of the
+configuration, using the features attribute. An example of how to enable features
+is as follows:
+
+```hcl
+sentinel {
+ features = {
+ apply-all = true
+ terraform = true
+ }
+}
+```
+
+The current set of features are:
+
+- [terraform](/sentinel/features/terraform) - Allowing the Sentinel runtime
+ to interact with Terraform data.
+- apply-all - Force Sentinel to evaluate all policies within a policy set without prematurely terminating on failures.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/features/terraform/index.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/features/terraform/index.mdx
new file mode 100644
index 0000000000..a810dbe478
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/features/terraform/index.mdx
@@ -0,0 +1,24 @@
+---
+page_title: terraform
+sidebar_current: docs-features-terraform
+description: Learn about the terraform feature and its capabilities.
+layout: docs
+---
+
+# terraform feature
+
+The `terraform` feature enhances the Sentinel runtime to work with Terraform
+data. It will add the following imports to the standard library:
+
+- [tfplan/v1](/sentinel/features/terraform/tfplan-v1)
+- [tfplan/v2](/sentinel/features/terraform/tfplan-v2)
+- [tfconfig/v1](/sentinel/features/terraform/tfconfig-v1)
+- [tfconfig/v2](/sentinel/features/terraform/tfconfig-v2)
+- [tfstate/v1](/sentinel/features/terraform/tfstate-v1)
+- [tfstate/v2](/sentinel/features/terraform/tfstate-v2)
+
+-> **NOTE:** The above imports will only work with Terraform 0.12 and above,
+as they rely on the output from the `terraform show -json` command.
+
+It is recommended that the `/v2` suffixed imports are used, as they provide
+the best experience when interacting with the underlying data structures.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/features/terraform/tfconfig-v1.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/features/terraform/tfconfig-v1.mdx
new file mode 100644
index 0000000000..90179d7525
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/features/terraform/tfconfig-v1.mdx
@@ -0,0 +1,945 @@
+---
+page_title: tfconfig/v1 - terraform - Features
+sidebar_current: docs-features-terraform-config-v1
+description: The tfconfig/v1 import provides access to a Terraform configuration.
+layout: docs
+---
+
+# Import: tfconfig/v1
+
+The `tfconfig/v1` import provides access to a Terraform configuration.
+
+The Terraform configuration is the set of `*.tf` files that are used to
+describe the desired infrastructure state. Policies using the `tfconfig/v1`
+import can access all aspects of the configuration: providers, resources,
+data sources, modules, and variables.
+
+Some use cases for `tfconfig/v1` include:
+
+* **Organizational naming conventions**: requiring that configuration elements
+ are named in a way that conforms to some organization-wide standard.
+* **Required inputs and outputs**: organizations may require a particular set
+ of input variable names across all workspaces or may require a particular
+ set of outputs for asset management purposes.
+* **Enforcing particular modules**: organizations may provide a number of
+ "building block" modules and require that each workspace be built only from
+ combinations of these modules.
+* **Enforcing particular providers or resources**: an organization may wish to
+ require or prevent the use of providers and/or resources so that configuration
+ authors cannot use alternative approaches to work around policy
+ restrictions.
+
+Note with these use cases that this import is concerned with object _names_
+in the configuration. Since this is the configuration and not an invocation
+of Terraform, you can't see values for variables, the state, or the diff for
+a pending plan. If you want to write policy around expressions used
+within configuration blocks, you likely want to use the
+[`tfplan/v1`](/sentinel/features/terraform/tfplan-v1) import.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfconfig/v1" {
+ config = {
+ "plan": "./path/to/plan.json"
+ }
+}
+```
+
+## Namespace Overview
+
+The following is a tree view of the import namespace. For more detail on a
+particular part of the namespace, see below.
+
+-> **Note:** The root-level alias keys shown here (`data`, `modules`,
+`providers`, `resources`, and `variables`) are shortcuts to a [module
+namespace](#namespace-module) scoped to the root module. For more details, see
+the section on [root namespace aliases](#root-namespace-aliases).
+
+```
+tfconfig/v1
+├── module() (function)
+│ └── (module namespace)
+│ ├── data
+│ │ └── TYPE.NAME
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── provisioners
+│ │ └── NUMBER
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── type (string)
+│ ├── modules
+│ │ └── NAME
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ ├── source (string)
+│ │ └── version (string)
+│ ├──outputs
+│ │ └── NAME
+│ │ ├── depends_on (list of strings)
+│ │ ├── description (string)
+│ │ ├── sensitive (boolean)
+│ │ ├── references (list of strings)
+│ │ └── value (value)
+│ ├── providers
+│ │ └── TYPE
+│ │ ├── alias
+│ │ │ └── ALIAS
+│ │ │ ├── config (map of keys)
+│ │ | ├── references (map of keys)
+│ │ │ └── version (string)
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── version (string)
+│ ├── resources
+│ │ └── TYPE.NAME
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── provisioners
+│ │ └── NUMBER
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── type (string)
+│ └── variables
+│ └── NAME
+│ ├── default (value)
+│ └── description (string)
+├── module_paths ([][]string)
+│
+├── data (root module alias)
+├── modules (root module alias)
+├── outputs (root module alias)
+├── providers (root module alias)
+├── resources (root module alias)
+└── variables (root module alias)
+```
+
+### `references` Overview
+
+As an example, consider the following resource block:
+
+```hcl
+resource "local_file" "accounts" {
+ content = "some text"
+ filename = "${var.subdomain}.${var.domain}/accounts.txt"
+}
+```
+
+In this example, one might want to ensure `domain` and `subdomain` input
+variables are used within `filename` in this configuration.
+
+-> Any non-static values (such as interpolated strings) are not present within the
+configuration value and `references` should be used instead:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+# filename_references is a list of string values containing the references used in the expression
+filename_references = tfconfig.resources.local_file.accounts.references.filename
+
+main = rule {
+ filename_references contains "var.domain" and
+ filename_references contains "var.subdomain"
+}
+```
+
+The `references` value is present in any namespace where non-constant
+configuration values can be expressed. This is essentially every namespace
+which has a `config` value as well as the `outputs` namespace.
+
+-> **Note:** Remember, this import enforces policy around the literal Terraform
+configuration and not the final values as a result of invoking Terraform. If
+you want to write policy around the _result_ of expressions used within
+configuration blocks (for example, if you wanted to ensure the final value of
+`filename` above includes `accounts.txt`), you likely want to use the
+[`tfplan/v1`](/sentinel/features/terraform/tfplan-v1) import.
+
+## Namespace: Root
+
+The root-level namespace consists of the values and functions documented below.
+
+In addition to this, the root-level `data`, `modules`, `providers`, `resources`,
+and `variables` keys all alias to their corresponding namespaces within the
+[module namespace](#namespace-module).
+
+
+
+### Function: `module()`
+
+```
+module = func(ADDR)
+```
+
+* **Return Type:** A [module namespace](#namespace-module).
+
+The `module()` function in the [root namespace](#namespace-root) returns the
+[module namespace](#namespace-module) for a particular module address.
+
+The address must be a list and is the module address, split on the period (`.`),
+excluding the root module.
+
+Hence, a module with an address of simply `foo` (or `root.foo`) would be
+`["foo"]`, and a module within that (so address `foo.bar`) would be read as
+`["foo", "bar"]`.
+
+[`null`][ref-null] is returned if a module address is invalid, or if the module
+is not present in the configuration.
+
+[ref-null]: /sentinel/language/spec#null
+
+As an example, given the following module block:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+If the module contained the following content:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { subject.module(["foo"]).resources.null_resource.foo.config.triggers[0].foo is "bar" }
+```
+
+
+
+### Value: `module_paths`
+
+* **Value Type:** List of a list of strings.
+
+The `module_paths` value within the [root namespace](#namespace-root) is a list
+of all of the modules within the Terraform configuration.
+
+Modules not present in the configuration will not be present here, even if they
+are present in the diff or state.
+
+This data is represented as a list of a list of strings, with the inner list
+being the module address, split on the period (`.`).
+
+The root module is included in this list, represented as an empty inner list.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The value of `module_paths` would be:
+
+```
+[
+ [],
+ ["foo"],
+]
+```
+
+And the following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.module_paths contains ["foo"] }
+```
+
+#### Iterating Through Modules
+
+Iterating through all modules to find particular resources can be useful. This
+[example][iterate-over-modules] shows how to use `module_paths` with the
+[`module()` function](#function-module-) to find all resources of a
+particular type from all modules using the `tfplan` import. By changing `tfplan`
+in this function to `tfconfig`, you could make a similar function find all
+resources of a specific type in the Terraform configuration.
+
+[iterate-over-modules]: https://developer.hashicorp.com/terraform/cloud-docs/policy-enforcement/sentinel#sentinel-imports
+
+## Namespace: Module
+
+The **module namespace** can be loaded by calling [`module()`](#root-function-module)
+for a particular module.
+
+It can be used to load the following child namespaces:
+
+* `data` - Loads the [resource namespace](#namespace-resources-data-sources),
+ filtered against data sources.
+* `modules` - Loads the [module configuration
+ namespace](#namespace-module-configuration).
+* `outputs` - Loads the [output namespace](#namespace-outputs).
+* `providers` - Loads the [provider namespace](#namespace-providers).
+* `resources` - Loads the [resource
+ namespace](#namespace-resources-data-sources), filtered against resources.
+* `variables` - Loads the [variable namespace](#namespace-variables).
+
+### Root Namespace Aliases
+
+The root-level `data`, `modules`, `providers`, `resources`, and `variables` keys
+all alias to their corresponding namespaces within the module namespace, loaded
+for the root module. They are the equivalent of running `module([]).KEY`.
+
+
+
+## Namespace: Resources/Data Sources
+
+The **resource namespace** is a namespace _type_ that applies to both resources
+(accessed by using the `resources` namespace key) and data sources (accessed
+using the `data` namespace key).
+
+Accessing an individual resource or data source within each respective namespace
+can be accomplished by specifying the type and name, in the syntax
+`[resources|data].TYPE.NAME`.
+
+In addition, each of these namespace levels is a map, allowing you to filter
+based on type and name. Some examples of multi-level access are below:
+
+* To fetch all `aws_instance` resources within the root module, you can specify
+ `tfconfig.resources.aws_instance`. This would give you a map of resource
+ namespaces indexed from the names of each resource (`foo`, `bar`, and so
+ on).
+* To fetch all resources within the root module, irrespective of type, use
+ `tfconfig.resources`. This is indexed by type, as shown above with
+ `tfconfig.resources.aws_instance`, with names being the next level down.
+
+As an example, perhaps you wish to deny use of the `local_file` resource
+in your configuration. Consider the following resource block:
+
+```hcl
+resource "local_file" "foo" {
+ content = "foo!"
+ filename = "${path.module}/foo.bar"
+}
+```
+
+The following policy would fail:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.resources not contains "local_file" }
+```
+
+Further explanation of the namespace will be in the context of resources. As
+mentioned, when operating on data sources, use the same syntax, except with
+`data` in place of `resources`.
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+The `config` value within the [resource
+namespace](#namespace-resources-data-sources) is a map of key-value pairs that
+directly map to Terraform config keys and values.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#resources-value-references) should be used instead.
+
+As an example, consider the following resource block:
+
+```hcl
+resource "local_file" "accounts" {
+ content = "some text"
+ filename = "accounts.txt"
+}
+```
+
+In this example, one might want to access `filename` to validate that the correct
+file name is used. Given the above example, the following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule {
+ tfconfig.resources.local_file.accounts.config.filename is "accounts.txt"
+}
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [resource namespace](#namespace-resources-data-sources)
+contains the identifiers within non-constant expressions found in [`config`](#resources-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+
+
+### Value: `provisioners`
+
+* **Value Type:** List of [provisioner namespaces](#namespace-provisioners).
+
+The `provisioners` value within the [resource namespace](#namespace-resources)
+represents the [provisioners][ref-tf-provisioners] within a specific resource.
+
+Provisioners are listed in the order they were provided in the configuration
+file.
+
+While the `provisioners` value will be present within data sources, it will
+always be an empty map `null` (in Terraform 0.12) since data sources cannot
+actually have provisioners.
+
+The data within a provisioner can be inspected via the returned [provisioner
+namespace](#namespace-provisioners).
+
+[ref-tf-provisioners]: https://developer.hashicorp.com/terraform/language/resources/provisioners/syntax
+
+## Namespace: Provisioners
+
+The **provisioner namespace** represents the configuration for a particular
+[provisioner][ref-tf-provisioners] within a specific resource.
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+The `config` value within the [provisioner namespace](#namespace-provisioners)
+represents the values of the keys within the provisioner.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#provisioners-value-references) should be used instead.
+
+As an example, given the following resource block:
+
+```hcl
+resource "null_resource" "foo" {
+ # ...
+
+ provisioner "local-exec" {
+ command = "echo ${self.private_ip} > file.txt"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule {
+ tfconfig.resources.null_resource.foo.provisioners[0].config.command is "echo ${self.private_ip} > file.txt"
+}
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [provisioner namespace](#namespace-provisioners)
+contains the identifiers within non-constant expressions found in [`config`](#provisioners-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+
+
+### Value: `type`
+
+* **Value Type:** String.
+
+The `type` value within the [provisioner namespace](#namespace-provisioners)
+represents the type of the specific provisioner.
+
+As an example, in the following resource block:
+
+```hcl
+resource "null_resource" "foo" {
+ # ...
+
+ provisioner "local-exec" {
+ command = "echo ${self.private_ip} > file.txt"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.resources.null_resource.foo.provisioners[0].type is "local-exec" }
+```
+
+## Namespace: Module Configuration
+
+The **module configuration** namespace displays data on _module configuration_
+as it is given within a `module` block. This means that the namespace concerns
+itself with the contents of the declaration block (example: the `source`
+parameter and variable assignment keys), not the data within the module
+(example: any contained resources or data sources). For the latter, the module
+instance would need to be looked up with the [`module()`
+function](#root-function-module).
+
+
+
+### Value: `source`
+
+* **Value Type:** String.
+
+The `source` value within the [module configuration
+namespace](#namespace-module-configuration) represents the module source path as
+supplied to the module configuration.
+
+As an example, given the module declaration block:
+
+```hcl
+module "foo" {
+ source = "./foo"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.modules.foo.source is "./foo" }
+```
+
+
+
+### Value: `version`
+
+* **Value Type:** String.
+
+The `version` value within the [module configuration
+namespace](#namespace-module-configuration) represents the [version
+constraint][module-version-constraint] for modules that support it, such as
+modules within the [Terraform Module Registry][terraform-module-registry] or the
+[Terraform Cloud private module registry][tfe-private-registry].
+
+[module-version-constraint]: https://developer.hashicorp.com/terraform/language/modules#module-versions
+
+[terraform-module-registry]: https://registry.terraform.io/
+
+[tfe-private-registry]: https://developer.hashicorp.com/terraform/cloud-docs/registry
+
+As an example, given the module declaration block:
+
+```hcl
+module "foo" {
+ source = "foo/bar"
+ version = "~> 1.2"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.modules.foo.version is "~> 1.2" }
+```
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#modules-value-references) should be used instead.
+
+The `config` value within the [module configuration
+namespace](#namespace-module-configuration) represents the values of the keys
+within the module configuration. This is every key within a module declaration
+block except [`source`](#modules-value-source) and [`version`](#modules-value-version), which
+have their own values.
+
+As an example, given the module declaration block:
+
+```hcl
+module "foo" {
+ source = "./foo"
+
+ bar = "baz"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.modules.foo.config.bar is "baz" }
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [module configuration namespace](#namespace-module-configuration)
+contains the identifiers within non-constant expressions found in [`config`](#modules-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+## Namespace: Outputs
+
+The **output namespace** represents _declared_ output data within a
+configuration. As such, configuration for the [`value`](#outputs-value-value) attribute
+will be in its raw form, and not yet interpolated. For fully interpolated output
+values, see the [`tfstate` import][ref-tfe-sentinel-tfstate].
+
+[ref-tfe-sentinel-tfstate]: /sentinel/features/terraform/tfstate-v1
+
+This namespace is indexed by output name.
+
+
+
+### Value: `depends_on`
+
+* **Value Type:** A list of strings.
+
+The `depends_on` value within the [output namespace](#namespace-outputs)
+represents any _explicit_ dependencies for this output. For more information,
+see the [depends_on output setting][ref-depends_on] within the general Terraform
+documentation.
+
+[ref-depends_on]: https://developer.hashicorp.com/terraform/language/values/outputs#depends_on
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ depends_on = ["null_resource.bar"]
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.depends_on[0] is "null_resource.bar" }
+```
+
+
+
+### Value: `description`
+
+* **Value Type:** String.
+
+The `description` value within the [output namespace](#namespace-outputs)
+represents the defined description for this output.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ description = "foobar"
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.description is "foobar" }
+```
+
+
+
+### Value: `sensitive`
+
+* **Value Type:** Boolean.
+
+The `sensitive` value within the [output namespace](#namespace-outputs)
+represents if this value has been marked as sensitive or not.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ sensitive = true
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { subject.outputs.id.sensitive }
+```
+
+
+
+### Value: `value`
+
+* **Value Type:** Any primitive type, list or map.
+
+The `value` value within the [output namespace](#namespace-outputs) represents
+the defined value for the output as declared in the configuration. Primitives
+will bear the implicit type of their declaration (string, int, float, or bool),
+and maps and lists will be represented as such.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#outputs-value-references) should be used instead.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ value = "foo"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.value is "foo" }
+```
+
+
+
+### Value: `references`
+
+* **Value Type:**. List of strings.
+
+The `references` value within the [output namespace](#namespace-outputs)
+contains the names of any referenced identifiers when [`value`](#outputs-value-value)
+is a non-constant expression.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.references contains "null_resource.foo.id" }
+```
+
+## Namespace: Providers
+
+The **provider namespace** represents data on the declared providers within a
+namespace.
+
+This namespace is indexed by provider type and _only_ contains data about
+providers when actually declared. If you are using a completely implicit
+provider configuration, this namespace will be empty.
+
+This namespace is populated based on the following criteria:
+
+* The top-level namespace [`config`](#providers-value-config) and
+ [`version`](#providers-value-version) values are populated with the configuration and
+ version information from the default provider (the provider declaration that
+ lacks an alias).
+* Any aliased providers are added as namespaces within the
+ [`alias`](#providers-value-alias) value.
+* If a module lacks a default provider configuration, the top-level `config` and
+ `version` values will be empty.
+
+
+
+### Value: `alias`
+
+* **Value Type:** A map of [provider namespaces](#namespace-providers), indexed
+ by alias.
+
+The `alias` value within the [provider namespace](#namespace-providers)
+represents all declared [non-default provider
+instances][ref-tf-provider-instances] for a specific provider type, indexed by
+their specific alias.
+
+[ref-tf-provider-instances]: https://developer.hashicorp.com/terraform/language/providers/configuration#alias-multiple-provider-configurations
+
+The return type is a provider namespace with the data for the instance in
+question loaded. The `alias` key will not be available within this namespace.
+
+As an example, given the following provider declaration block:
+
+```hcl
+provider "aws" {
+ alias = "east"
+ region = "us-east-1"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.providers.aws.alias.east.config.region is "us-east-1" }
+```
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#providers-value-references) should be used instead.
+
+The `config` value within the [provider namespace](#namespace-providers)
+represents the values of the keys within the provider's configuration, with the
+exception of the provider version, which is represented by the
+[`version`](#providers-value-version) value. [`alias`](#providers-value-alias) is also not included
+when the provider is aliased.
+
+As an example, given the following provider declaration block:
+
+```hcl
+provider "aws" {
+ region = "us-east-1"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.providers.aws.config.region is "us-east-1" }
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [provider namespace](#namespace-providers)
+contains the identifiers within non-constant expressions found in [`config`](#providers-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+
+
+### Value: `version`
+
+* **Value Type:** String.
+
+The `version` value within the [provider namespace](#namespace-providers)
+represents the explicit expected version of the supplied provider. This includes
+the pessimistic operator.
+
+As an example, given the following provider declaration block:
+
+```hcl
+provider "aws" {
+ version = "~> 1.34"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.providers.aws.version is "~> 1.34" }
+```
+
+## Namespace: Variables
+
+The **variable namespace** represents _declared_ variable data within a
+configuration. As such, static data can be extracted, such as defaults, but not
+dynamic data, such as the current value of a variable within a plan (although
+this can be extracted within the [`tfplan` import][ref-tfe-sentinel-tfplan]).
+
+[ref-tfe-sentinel-tfplan]: /sentinel/features/terraform/tfplan-v1
+
+This namespace is indexed by variable name.
+
+
+
+### Value: `default`
+
+* **Value Type:** Any primitive type, list, map, or `null`.
+
+The `default` value within the [variable namespace](#namespace-variables)
+represents the default for the variable as declared in the configuration.
+
+The actual value will be as configured. Primitives will bear the implicit type
+of their declaration (string, int, float, or bool), and maps and lists will be
+represented as such.
+
+If no default is present, the value will be [`null`][ref-sentinel-null] (not to
+be confused with [`undefined`][ref-sentinel-undefined]).
+
+[ref-sentinel-null]: /sentinel/language/spec#null
+
+[ref-sentinel-undefined]: /sentinel/language/undefined
+
+As an example, given the following variable blocks:
+
+```hcl
+variable "foo" {
+ default = "bar"
+}
+
+variable "number" {
+ default = 42
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+default_foo = rule { tfconfig.variables.foo.default is "bar" }
+default_number = rule { tfconfig.variables.number.default is 42 }
+
+main = rule { default_foo and default_number }
+```
+
+
+
+### Value: `description`
+
+* **Value Type:** String.
+
+The `description` value within the [variable namespace](#namespace-variables)
+represents the description of the variable, as provided in configuration.
+
+As an example, given the following variable block:
+
+```hcl
+variable "foo" {
+ description = "foobar"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.variables.foo.description is "foobar" }
+```
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/features/terraform/tfconfig-v2.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/features/terraform/tfconfig-v2.mdx
new file mode 100644
index 0000000000..ddc282316b
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/features/terraform/tfconfig-v2.mdx
@@ -0,0 +1,441 @@
+---
+page_title: tfconfig/v2 - terraform - Features
+sidebar_current: docs-features-terraform-tfconfig-v2
+description: The tfconfig/v2 import provides access to a Terraform configuration.
+layout: docs
+---
+
+# Import: tfconfig/v2
+
+The `tfconfig/v2` import provides access to a Terraform configuration.
+
+The Terraform configuration is the set of `*.tf` files that are used to
+describe the desired infrastructure state. Policies using the `tfconfig`
+import can access all aspects of the configuration: providers, resources,
+data sources, modules, and variables.
+
+Some use cases for `tfconfig` include:
+
+* **Organizational naming conventions**: requiring that configuration elements
+ are named in a way that conforms to some organization-wide standard.
+* **Required inputs and outputs**: organizations may require a particular set
+ of input variable names across all workspaces or may require a particular
+ set of outputs for asset management purposes.
+* **Enforcing particular modules**: organizations may provide a number of
+ "building block" modules and require that each workspace be built only from
+ combinations of these modules.
+* **Enforcing particular providers or resources**: an organization may wish to
+ require or prevent the use of providers and/or resources so that configuration
+ authors cannot use alternative approaches to work around policy
+ restrictions.
+
+The data in the `tfconfig/v2` import is sourced from the JSON configuration file
+that is generated by the [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) command. For more information on
+the file format, see the [JSON Output Format](https://developer.hashicorp.com/terraform/internals/json-format)
+page.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfconfig/v2" {
+ config = {
+ "plan": "./path/to/plan.json"
+ }
+}
+```
+
+## Import Overview
+
+The `tfconfig/v2` import is structured as a series of _collections_, keyed as a
+specific format, such as resource address, module address, or a
+specifically-formatted provider key.
+
+```
+tfconfig/v2
+├── strip_index() (function)
+├── providers
+│ └── (indexed by [module_address:]provider[.alias])
+│ ├── provider_config_key (string)
+│ ├── name (string)
+│ ├── full_name (string)
+│ ├── alias (string)
+│ ├── module_address (string)
+│ ├── config (block expression representation)
+│ └── version_constraint (string)
+├── resources
+│ └── (indexed by address)
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── provider_config_key (string)
+│ ├── provisioners (list)
+│ │ └── (ordered provisioners for this resource only)
+│ ├── config (block expression representation)
+│ ├── count (expression representation)
+│ ├── for_each (expression representation)
+│ └── depends_on (list of strings)
+├── provisioners
+│ └── (indexed by resource_address:index)
+│ ├── resource_address (string)
+│ ├── type (string)
+│ ├── index (string)
+│ └── config (block expression representation)
+├── variables
+│ └── (indexed by module_address:name)
+│ ├── module_address (string)
+│ ├── name (string)
+│ ├── default (value)
+│ └── description (string)
+├── outputs
+│ └── (indexed by module_address:name)
+│ ├── module_address (string)
+│ ├── name (string)
+│ ├── sensitive (boolean)
+│ ├── value (expression representation)
+│ ├── description (string)
+│ └── depends_on (list of strings)
+└── module_calls
+ └── (indexed by module_address:name)
+ ├── module_address (string)
+ ├── name (string)
+ ├── source (string)
+ ├── config (block expression representation)
+ ├── count (expression representation)
+ ├── depends_on (expression representation)
+ ├── for_each (expression representation)
+ └── version_constraint (string)
+```
+
+The collections are:
+
+* [`providers`](#the-providers-collection) - The configuration for all provider
+ instances across all modules in the configuration.
+* [`resources`](#the-resources-collection) - The configuration of all resources
+ across all modules in the configuration.
+* [`variables`](#the-variables-collection) - The configuration of all variable
+ definitions across all modules in the configuration.
+* [`outputs`](#the-outputs-collection) - The configuration of all output
+ definitions across all modules in the configuration.
+* [`module_calls`](#the-module_calls-collection) - The configuration of all module
+ calls (individual [`module`](/terraform/language/modules) blocks) across
+ all modules in the configuration.
+
+These collections are specifically designed to be used with the
+[`filter`](/sentinel/language/collection-operations#filter-expression)
+quantifier expression in Sentinel, so that one can collect a list of resources
+to perform policy checks on without having to write complex module or
+configuration traversal. As an example, the following code will return all
+`aws_instance` resource types within the configuration, regardless of what
+module they are in:
+
+```
+all_aws_instances = filter tfconfig.resources as _, r {
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+You can add specific attributes to the filter to narrow the search, such as the
+module address. The following code would return resources in a module named
+`foo` only:
+
+```
+all_aws_instances = filter tfconfig.resources as _, r {
+ r.module_address is "module.foo" and
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+### Address Differences Between `tfconfig`, `tfplan`, and `tfstate`
+
+This import deals with configuration before it is expanded into a
+resource graph by Terraform. As such, it is not possible to compute an index as
+the import is building its collections and computing addresses for resources and
+modules.
+
+As such, addresses found here may not always match the expanded addresses found
+in the [`tfplan/v2`](/sentinel/features/terraform/tfplan-v2) and [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2)
+imports, specifically when
+[`count`](https://developer.hashicorp.com/terraform/language/resources#count-multiple-resource-instances-by-count)
+and
+[`for_each`](https://developer.hashicorp.com/terraform/language/resources#for_each-multiple-resource-instances-defined-by-a-map-or-set-of-strings),
+are used.
+
+As an example, consider a resource named `null_resource.foo` with a count of `2`
+located in a module named `bar`. While there will possibly be entries in the
+other imports for `module.bar.null_resource.foo[0]` and
+`module.bar.null_resource.foo[1]`, in `tfconfig/v2`, there will only be a
+`module.bar.null_resource.foo`. As mentioned in the start of this section, this
+is because configuration actually _defines_ this scaling, whereas _expansion_
+actually happens when the resource graph is built, which happens as a natural
+part of the refresh and planning process.
+
+The `strip_index` helper function, found in this import, can assist in
+removing the indexes from addresses found in the `tfplan/v2` and `tfstate/v2`
+imports so that data from those imports can be used to reference data in this
+one.
+
+## The `strip_index` Function
+
+The `strip_index` helper function can be used to remove indexes from addresses
+found in [`tfplan/v2`](/sentinel/features/terraform/tfplan-v2) and [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2),
+by removing the indexes from each resource.
+
+This can be used to help facilitate cross-import lookups for data between plan,
+state, and config.
+
+```
+import "tfconfig/v2" as tfconfig
+import "tfplan/v2" as tfplan
+
+main = rule {
+ all filter tfplan.resource_changes as _, rc {
+ rc.mode is "managed" and
+ rc.type is "aws_instance"
+ } as _, rc {
+ tfconfig.resources[tfconfig.strip_index(rc.address)].config.ami.constant_value is "ami-abcdefgh012345"
+ }
+}
+```
+
+## Expression Representations
+
+Most collections in this import will have one of two kinds of _expression
+representations_. This is a verbose format for expressing a (parsed)
+configuration value independent of the configuration source code, which is not
+100% available to a policy check in Terraform Cloud.
+
+```
+(expression representation)
+├── constant_value (value)
+└── references (list of strings)
+```
+
+There are two major parts to an expression representation:
+
+* Any _strictly constant value_ is expressed as an expression with a
+ `constant_value` field.
+* Any expression that requires some degree of evaluation to generate the final
+ value - even if that value is known at plan time - is not expressed in
+ configuration. Instead, any particular references that are made are added to
+ the `references` field. More details on this field can be found in the
+ [expression
+ representation](https://developer.hashicorp.com/terraform/internals/json-format#expression-representation)
+ section of the JSON output format documentation.
+
+For example, to determine if an output is based on a particular
+resource value, one could do:
+
+```
+import "tfconfig/v2" as tfconfig
+
+main = rule {
+ tfconfig.outputs["instance_id"].value.references is ["aws_instance.foo"]
+}
+```
+
+-> **Note:** The representation does not account for
+complex interpolations or other expressions that combine constants with other
+expression data. For example, the partially constant data in `"foo${var.bar}"` would be lost.
+
+### Block Expression Representation
+
+Expanding on the above, a multi-value expression representation (such as the
+kind found in a [`resources`](#the-resources-collection) collection element) is
+similar, but the root value is a keyed map of expression representations. This
+is repeated until a "scalar" expression value is encountered, ie: a field that
+is not a block in the resource's schema.
+
+```
+(block expression representation)
+└── (attribute key)
+ ├── (child block expression representation)
+ │ └── (...)
+ ├── constant_value (value)
+ └── references (list of strings)
+```
+
+As an example, one can validate expressions in an
+[`aws_instance`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance) resource using the
+following:
+
+```
+import "tfconfig/v2" as tfconfig
+
+main = rule {
+ tfconfig.resources["aws_instance.foo"].config.ami.constant_value is "ami-abcdefgh012345"
+}
+```
+
+Note that _nested blocks_, sometimes known as _sub-resources_, will be nested in
+configuration as as list of blocks (reflecting their ultimate nature as a list
+of objects). An example would be the `aws_instance` resource's
+[`ebs_block_device`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#ebs-ephemeral-and-root-block-devices) block:
+
+```
+import "tfconfig/v2" as tfconfig
+
+main = rule {
+ tfconfig.resources["aws_instance.foo"].config.ebs_block_device[0].volume_size < 10
+}
+```
+
+## The `providers` Collection
+
+The `providers` collection is a collection representing the configurations of
+all provider instances across all modules in the configuration.
+
+This collection is indexed by an opaque key. This is currently
+`module_address:provider.alias`, the same value as found in the
+`provider_config_key` field. `module_address` and the colon delimiter are
+omitted for the root module.
+
+The `provider_config_key` field is also found in the `resources` collection and
+can be used to locate a provider that belongs to a configured resource.
+
+The fields in this collection are as follows:
+
+* `provider_config_key` - The opaque configuration key, used as the index key.
+* `name` - The name of the provider, ie: `aws`.
+* `full_name` - The fully-qualified name of the provider, e.g. `registry.terraform.io/hashicorp/aws`.
+* `alias` - The alias of the provider, ie: `east`. Empty for a default provider.
+* `module_address` - The address of the module this provider appears in.
+* `config` - A [block expression
+ representation](#block-expression-representation) with provider configuration
+ values.
+* `version_constraint` - The defined version constraint for this provider.
+
+## The `resources` Collection
+
+The `resources` collection is a collection representing all of the resources
+found in all modules in the configuration.
+
+This collection is indexed by the resource address.
+
+The fields in this collection are as follows:
+
+* `address` - The resource address. This is the index of the collection.
+* `module_address` - The module address that this resource was found in.
+* `mode` - The resource mode, either `managed` (resources) or `data` (data
+ sources).
+* `type` - The type of resource, ie: `null_resource` in `null_resource.foo`.
+* `name` - The name of the resource, ie: `foo` in `null_resource.foo`.
+* `provider_config_key` - The opaque configuration key that serves as the index
+ of the [`providers`](#the-providers-collection) collection.
+* `provisioners` - The ordered list of provisioners for this resource. The
+ syntax of the provisioners matches those found in the
+ [`provisioners`](#the-provisioners-collection) collection, but is a list
+ indexed by the order the provisioners show up in the resource.
+* `config` - The [block expression
+ representation](#block-expression-representation) of the configuration values
+ found in the resource.
+* `count` - The [expression data](#expression-representations) for the `count`
+ value in the resource.
+* `for_each` - The [expression data](#expression-representations) for the
+ `for_each` value in the resource.
+* `depends_on` - The contents of the `depends_on` config directive, which
+ declares explicit dependencies for this resource.
+
+## The `provisioners` Collection
+
+The `provisioners` collection is a collection of all of the provisioners found
+across all resources in the configuration.
+
+While normally bound to a resource in an ordered fashion, this collection allows
+for the filtering of provisioners within a single expression.
+
+This collection is indexed with a key following the format
+`resource_address:index`, with each field matching their respective field in the
+particular element below:
+
+* `resource_address`: The address of the resource that the provisioner was found
+ in. This can be found in the [`resources`](#the-resources-collection)
+ collection.
+* `type`: The provisioner type, ie: `local_exec`.
+* `index`: The provisioner index as it shows up in the resource provisioner
+ order.
+* `config`: The [block expression
+ representation](#block-expression-representation) of the configuration values
+ in the provisioner.
+
+## The `variables` Collection
+
+The `variables` collection is a collection of all variables across all modules
+in the configuration.
+
+Note that this tracks variable definitions, not values. See the [`tfplan/v2`
+`variables` collection](/sentinel/features/terraform/tfplan-v2#the-variables-collection) for variable
+values set within a plan.
+
+This collection is indexed by the key format `module_address:name`, with each
+field matching their respective name below. `module_address` and the colon
+delimiter are omitted for the root module.
+
+* `module_address` - The address of the module the variable was found in.
+* `name` - The name of the variable.
+* `default` - The defined default value of the variable.
+* `description` - The description of the variable.
+
+## The `outputs` Collection
+
+The `outputs` collection is a collection of all outputs across all modules in
+the configuration.
+
+Note that this tracks variable definitions, not values. See the [`tfstate/v2`
+`outputs` collection](/sentinel/features/terraform/tfstate-v2#the-outputs-collection) for the final
+values of outputs set within a state. The [`tfplan/v2` `output_changes`
+collection](/sentinel/features/terraform/tfplan-v2#the-output_changes-collection) also contains a more
+complex collection of planned output changes.
+
+This collection is indexed by the key format `module_address:name`, with each
+field matching their respective name below. `module_address` and the colon
+delimiter are omitted for the root module.
+
+* `module_address` - The address of the module the output was found in.
+* `name` - The name of the output.
+* `sensitive` - Indicates whether or not the output was marked as
+ [`sensitive`](https://developer.hashicorp.com/terraform/language/values/outputs#sensitive-suppressing-values-in-cli-output).
+* `value` - An [expression representation](#expression-representations) for the output.
+* `description` - The description of the output.
+* `depends_on` - A list of resource names that the output depends on. These are
+ the hard-defined output dependencies as defined in the
+ [`depends_on`](https://developer.hashicorp.com/terraform/language/values/outputs#depends_on-explicit-output-dependencies)
+ field in an output declaration, not the dependencies that get derived from
+ natural evaluation of the output expression (these can be found in the
+ `references` field of the expression representation).
+
+## The `module_calls` Collection
+
+The `module_calls` collection is a collection of all module declarations at all
+levels within the configuration.
+
+Note that this is the
+[`module`](https://developer.hashicorp.com/terraform/language/modules#calling-a-child-module) stanza in
+any particular configuration, and not the module itself. Hence, a declaration
+for `module.foo` would actually be declared in the root module, which would be
+represented by a blank field in `module_address`.
+
+This collection is indexed by the key format `module_address:name`, with each
+field matching their respective name below. `module_address` and the colon
+delimiter are omitted for the root module.
+
+* `module_address` - The address of the module the declaration was found in.
+* `name` - The name of the module.
+* `source` - The contents of the `source` field.
+* `config` - A [block expression
+ representation](#block-expression-representation) for all parameter values
+ sent to the module.
+* `count` - An [expression representation](#expression-representations) for the
+ `count` field.
+* `depends_on`: An [expression representation](#expression-representations) for the
+ `depends_on` field.
+* `for_each` - An [expression representation](#expression-representations) for
+ the `for_each` field.
+* `version_constraint` - The string value found in the `version` field of the
+ module declaration.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/features/terraform/tfplan-v1.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/features/terraform/tfplan-v1.mdx
new file mode 100644
index 0000000000..0e0f9436a5
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/features/terraform/tfplan-v1.mdx
@@ -0,0 +1,610 @@
+---
+page_title: tfplan/v1 - terraform - Features
+sidebar_current: docs-features-terraform-tfplan-v1
+description: >-
+ The tfplan/v1 import provides access to a Terraform plan. A Terraform plan is the
+ file created as a result of `terraform plan` and is the input to `terraform
+ apply`. The plan represents the changes that Terraform needs to make to
+ infrastructure to reach the desired state represented by the configuration.
+layout: docs
+---
+
+# Import: tfplan/v1
+
+The `tfplan/v1` import provides access to a Terraform plan. A Terraform plan is the
+file created as a result of `terraform plan` and is the input to `terraform
+apply`. The plan represents the changes that Terraform needs to make to
+infrastructure to reach the desired state represented by the configuration.
+
+In addition to the diff data available in the plan, there is an
+[`applied`](#value-applied) state available that merges the plan with the state
+to create the planned state after apply.
+
+Finally, this import also allows you to access the configuration files and the
+Terraform state at the time the plan was run. See the section on [accessing a
+plan's state and configuration
+data](#accessing-a-plan-39-s-state-and-configuration-data) for more information.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan and schemas file.
+
+```hcl
+import "plugin" "tfplan/v1" {
+ config = {
+ "plan_path": "./path/to/plan.json",
+ "schemas_path": "./path/to/schemas.json"
+ }
+}
+```
+
+## Namespace Overview
+
+The following is a tree view of the import namespace. For more detail on a
+particular part of the namespace, see below.
+
+-> Note that the root-level alias keys shown here (`data`, `path`, and
+`resources`) are shortcuts to a [module namespace](#namespace-module) scoped to
+the root module. For more details, see the section on [root namespace
+aliases](#root-namespace-aliases).
+
+```
+tfplan/v1
+├── module() (function)
+│ └── (module namespace)
+│ ├── path ([]string)
+│ ├── data
+│ │ └── TYPE.NAME[NUMBER]
+│ │ ├── applied (map of keys)
+│ │ └── diff
+│ │ └── KEY
+│ │ ├── computed (bool)
+│ │ ├── new (string)
+│ │ └── old (string)
+│ └── resources
+│ └── TYPE.NAME[NUMBER]
+│ ├── applied (map of keys)
+│ ├── destroy (bool)
+│ ├── requires_new (bool)
+│ └── diff
+│ └── KEY
+│ ├── computed (bool)
+│ ├── new (string)
+│ └── old (string)
+├── module_paths ([][]string)
+├── terraform_version (string)
+├── variables (map of keys)
+│
+├── data (root module alias)
+├── path (root module alias)
+├── resources (root module alias)
+│
+├── config (tfconfig namespace alias)
+└── state (tfstate import alias)
+```
+
+## Namespace: Root
+
+The root-level namespace consists of the values and functions documented below.
+
+In addition to this, the root-level `data`, `path`, and `resources` keys alias
+to their corresponding namespaces or values within the [module
+namespace](#namespace-module).
+
+### Accessing a Plan's State and Configuration Data
+
+The `config` and `state` keys alias to the [`tfconfig`](/sentinel/features/terraform/tfconfig-v1) and
+[`tfstate`](/sentinel/features/terraform/tfstate-v1) namespaces, respectively, with the data sourced from
+the Terraform _plan_ (as opposed to actual configuration and state).
+
+-> Note that these aliases are not represented as maps. While they will appear
+empty when viewed as maps, the specific import namespace keys will still be
+accessible.
+
+### Function: `module()`
+
+```
+module = func(ADDR)
+```
+
+* **Return Type:** A [module namespace](#namespace-module).
+
+The `module()` function in the [root namespace](#namespace-root) returns the
+[module namespace](#namespace-module) for a particular module address.
+
+The address must be a list and is the module address, split on the period (`.`),
+excluding the root module.
+
+Hence, a module with an address of simply `foo` (or `root.foo`) would be
+`["foo"]`, and a module within that (so address `foo.bar`) would be read as
+`["foo", "bar"]`.
+
+[`null`](/sentinel/language/spec#null) is returned if a module address is
+invalid, or if the module is not present in the diff.
+
+As an example, given the following module block:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+If the module contained the following content:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.module(["foo"]).resources.null_resource.foo[0].applied.triggers.foo is "bar" }
+```
+
+### Value: `module_paths`
+
+* **Value Type:** List of a list of strings.
+
+The `module_paths` value within the [root namespace](#namespace-root) is a list
+of all of the modules within the Terraform diff for the current plan.
+
+Modules not present in the diff will not be present here, even if they are
+present in the configuration or state.
+
+This data is represented as a list of a list of strings, with the inner list
+being the module address, split on the period (`.`).
+
+The root module is included in this list, represented as an empty inner list, as
+long as there are changes.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The value of `module_paths` would be:
+
+```
+[
+ [],
+ ["foo"],
+]
+```
+
+And the following policy would evaluate to `true`:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.module_paths contains ["foo"] }
+```
+
+-> Note the above example only applies if the module is present in the diff.
+
+#### Iterating Through Modules
+
+Iterating through all modules to find particular resources can be useful. This
+[example][iterate-over-modules] shows how to use `module_paths` with the
+[`module()` function](#function-module-) to find all resources of a
+particular type from all modules that have pending changes using the `tfplan/v1`
+import.
+
+[iterate-over-modules]: https://developer.hashicorp.com/terraform/cloud-docs/policy-enforcement/sentinel#iterate-over-modules-and-find-resources
+
+### Value: `terraform_version`
+
+* **Value Type:** String.
+
+The `terraform_version` value within the [root namespace](#namespace-root)
+represents the version of Terraform used to create the plan. This can be used to
+enforce a specific version of Terraform in a policy check.
+
+As an example, the following policy would evaluate to `true`, as long as the
+plan was made with a version of Terraform in the 0.11.x series, excluding any
+pre-release versions (example: `-beta1` or `-rc1`):
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.terraform_version matches "^0\\.11\\.\\d+$" }
+```
+
+### Value: `variables`
+
+* **Value Type:** A string-keyed map of values.
+
+The `variables` value within the [root namespace](#namespace-root) represents
+all of the variables that were set when creating the plan. This will only
+contain variables set for the root module.
+
+Note that unlike the [`default`][import-tfconfig-variables-default] value in the
+[`tfconfig` variables namespace][import-tfconfig-variables], primitive values
+here are stringified, and type conversion will need to be performed to perform
+comparison for int, float, or boolean values. This only applies to variables
+that are primitives themselves and not primitives within maps and lists, which
+will be their original types.
+
+[import-tfconfig-variables-default]: /sentinel/features/terraform/tfconfig-v1#value-default
+
+[import-tfconfig-variables]: /sentinel/features/terraform/tfconfig-v1#namespace-variables
+
+If a default was accepted for the particular variable, the default value will be
+populated here.
+
+As an example, given the following variable blocks:
+
+```hcl
+variable "foo" {
+ default = "bar"
+}
+
+variable "number" {
+ default = 42
+}
+
+variable "map" {
+ default = {
+ foo = "bar"
+ number = 42
+ }
+}
+```
+
+The following policy would evaluate to `true`, if no values were entered to
+change these variables:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+default_foo = rule { tfplan.variables.foo is "bar" }
+default_number = rule { tfplan.variables.number is "42" }
+default_map_string = rule { tfplan.variables.map["foo"] is "bar" }
+default_map_int = rule { tfplan.variables.map["number"] is 42 }
+
+main = rule { default_foo and default_number and default_map_string and default_map_int }
+```
+
+## Namespace: Module
+
+The **module namespace** can be loaded by calling
+[`module()`](#function-module-) for a particular module.
+
+It can be used to load the following child namespaces, in addition to the values
+documented below:
+
+* `data` - Loads the [resource namespace](#namespace-resources-data-sources),
+ filtered against data sources.
+* `resources` - Loads the [resource
+ namespace](#namespace-resources-data-sources), filtered against resources.
+
+### Root Namespace Aliases
+
+The root-level `data` and `resources` keys both alias to their corresponding
+namespaces within the module namespace, loaded for the root module. They are the
+equivalent of running `module([]).KEY`.
+
+### Value: `path`
+
+* **Value Type:** List of strings.
+
+The `path` value within the [module namespace](#namespace-module) contains the
+path of the module that the namespace represents. This is represented as a list
+of strings.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The following policy would evaluate to `true` _only_ if the diff had changes for
+that module:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.module(["foo"]).path contains "foo" }
+```
+
+## Namespace: Resources/Data Sources
+
+The **resource namespace** is a namespace _type_ that applies to both resources
+(accessed by using the `resources` namespace key) and data sources (accessed
+using the `data` namespace key).
+
+Accessing an individual resource or data source within each respective namespace
+can be accomplished by specifying the type, name, and resource number (as if the
+resource or data source had a `count` value in it) in the syntax
+`[resources|data].TYPE.NAME[NUMBER]`. Note that NUMBER is always needed, even if
+you did not use `count` in the resource.
+
+In addition, each of these namespace levels is a map, allowing you to filter
+based on type and name.
+
+-> The (somewhat strange) notation here of `TYPE.NAME[NUMBER]` may imply that
+the inner resource index map is actually a list, but it's not - using the square
+bracket notation over the dotted notation (`TYPE.NAME.NUMBER`) is required here
+as an identifier cannot start with a number.
+
+Some examples of multi-level access are below:
+
+* To fetch all `aws_instance.foo` resource instances within the root module, you
+ can specify `tfplan.resources.aws_instance.foo`. This would then be indexed by
+ resource count index (`0`, `1`, `2`, and so on). Note that as mentioned above,
+ these elements must be accessed using square-bracket map notation (so `[0]`,
+ `[1]`, `[2]`, and so on) instead of dotted notation.
+* To fetch all `aws_instance` resources within the root module, you can specify
+ `tfplan.resources.aws_instance`. This would be indexed from the names of
+ each resource (`foo`, `bar`, and so on), with each of those maps containing
+ instances indexed by resource count index as per above.
+* To fetch all resources within the root module, irrespective of type, use
+ `tfplan.resources`. This is indexed by type, as shown above with
+ `tfplan.resources.aws_instance`, with names being the next level down, and so
+ on.
+
+~> When [resource targeting](https://developer.hashicorp.com/terraform/cli/commands/plan#resource-targeting) is
+ in effect, `tfplan.resources` will only include the resources specified as
+ targets for the run. This may lead to unexpected outcomes if a policy expects
+ a resource to be present in the plan.
+
+Further explanation of the namespace will be in the context of resources. As
+mentioned, when operating on data sources, use the same syntax, except with
+`data` in place of `resources`.
+
+### Value: `applied`
+
+* **Value Type:** A string-keyed map of values.
+
+The `applied` value within the [resource
+namespace](#namespace-resources-data-sources) contains a "predicted"
+representation of the resource's state post-apply. It's created by merging the
+pending resource's diff on top of the existing data from the resource's state
+(if any). The map is a complex representation of these values with data going
+as far down as needed to represent any state values such as maps, lists, and
+sets.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if the resource was in the diff:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].applied.triggers.foo is "bar" }
+```
+
+-> Note that some values will not be available in the `applied` state because
+they cannot be known until the plan is actually applied. In Terraform 0.11 or
+earlier, these values are represented by a placeholder (the UUID value
+`74D93920-ED26-11E3-AC10-0800200C9A66`) and in Terraform 0.12 or later they
+are `undefined`. **In either case**, you should instead use the
+[`computed`](#value-computed) key within the [diff
+namespace](#namespace-resource-diff) to determine that a computed value will
+exist.
+
+-> If a resource is being destroyed, its `applied` value is omitted from the
+namespace and trying to fetch it will return undefined.
+
+### Value: `diff`
+
+* **Value Type:** A map of [diff namespaces](#namespace-resource-diff).
+
+The `diff` value within the [resource
+namespace](#namespace-resources-data-sources) contains the diff for a particular
+resource. Each key within the map links to a [diff
+namespace](#namespace-resource-diff) for that particular key.
+
+Note that unlike the [`applied`](#value-applied) value, this map is not complex;
+the map is only 1 level deep with each key possibly representing a diff for a
+particular complex value within the resource.
+
+See the below section for more details on the diff namespace, in addition to
+usage examples.
+
+### Value: `destroy`
+
+* **Value Type:** Boolean.
+
+The `destroy` value within the [resource
+namespace](#namespace-resources-data-sources) is `true` if a resource is being
+destroyed for _any_ reason, including cases where it's being deleted as part of
+a resource re-creation, in which case [`requires_new`](#value-requires_new) will
+also be set.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` when `null_resource.foo` is being
+destroyed:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].destroy }
+```
+
+### Value: `requires_new`
+
+* **Value Type:** Boolean.
+
+The `requires_new` value within the [resource
+namespace](#namespace-resources-data-sources) is `true` if the resource is still
+present in the configuration, but must be replaced to satisfy its current diff.
+Whenever `requires_new` is `true`, [`destroy`](#value-destroy) is also `true`.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if one of the `triggers` in
+`null_resource.foo` was being changed:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].requires_new }
+```
+
+## Namespace: Resource Diff
+
+The **diff namespace** is a namespace that represents the diff for a specific
+attribute within a resource. For details on reading a particular attribute,
+see the [`diff`](#value-diff) value in the [resource
+namespace](#namespace-resources-data-sources).
+
+### Value: `computed`
+
+* **Value Type:** Boolean.
+
+The `computed` value within the [diff namespace](#namespace-resource-diff) is
+`true` if the resource key in question depends on another value that isn't yet
+known. Typically, that means the value it depends on belongs to a resource that
+either doesn't exist yet, or is changing state in such a way as to affect the
+dependent value so that it can't be known until the apply is complete.
+
+-> Keep in mind that when using `computed` with complex structures such as maps,
+lists, and sets, it's sometimes necessary to test the count attribute for the
+structure, versus a key within it, depending on whether or not the diff has
+marked the whole structure as computed. This is demonstrated in the example
+below. Count keys are `%` for maps, and `#` for lists and sets. If you are
+having trouble determining the type of specific field within a resource, contact
+the support team.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+
+resource "null_resource" "bar" {
+ triggers = {
+ foo_id = "${null_resource.foo.id}"
+ }
+}
+```
+
+The following policy would evaluate to `true`, if the `id` of
+`null_resource.foo` was currently not known, such as when the resource is
+pending creation, or is being deleted and re-created:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.bar[0].diff["triggers.%"].computed }
+```
+
+### Value: `new`
+
+* **Value Type:** String.
+
+The `new` value within the [diff namespace](#namespace-resource-diff) contains
+the new value of a changing attribute, _if_ the value is known at plan time.
+
+-> `new` will be an empty string if the attribute's value is currently unknown.
+For more details on detecting unknown values, see [`computed`](#value-computed).
+
+Note that this value is _always_ a string, regardless of the actual type of the
+value changing. [Type conversion][ref-sentinel-type-conversion] within policy
+may be necessary to achieve the comparison needed.
+
+[ref-sentinel-type-conversion]: https://docs.hashicorp.com/sentinel/language/values#type-conversion
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`, if the resource was in the diff
+and each of the concerned keys were changing to new values:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].diff["triggers.foo"].new is "bar" }
+```
+
+### Value: `old`
+
+* **Value Type:** String.
+
+The `old` value within the [diff namespace](#namespace-resource-diff) contains
+the old value of a changing attribute.
+
+Note that this value is _always_ a string, regardless of the actual type of the
+value changing. [Type conversion][ref-sentinel-type-conversion] within policy
+may be necessary to achieve the comparison needed.
+
+If the value did not exist in the previous state, `old` will always be an empty
+string.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "baz"
+ }
+}
+```
+
+If that resource was previously in config as:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].diff["triggers.foo"].old is "bar" }
+```
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/features/terraform/tfplan-v2.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/features/terraform/tfplan-v2.mdx
new file mode 100644
index 0000000000..61fb7b2bf2
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/features/terraform/tfplan-v2.mdx
@@ -0,0 +1,402 @@
+---
+page_title: tfplan/v2 - terraform - Features
+sidebar_current: docs-features-terraform-tfplan-v2
+description: >-
+ The tfplan import provides access to a Terraform plan. A Terraform plan is the
+ file created as a result of `terraform plan` and is the input to `terraform
+ apply`. The plan represents the changes that Terraform needs to make to
+ infrastructure to reach the desired state represented by the configuration.
+layout: docs
+---
+
+# Import: tfplan/v2
+
+The `tfplan/v2` import provides access to a Terraform plan.
+
+A Terraform plan is the file created as a result of `terraform plan` and is the
+input to `terraform apply`. The plan represents the changes that Terraform needs
+to make to infrastructure to reach the desired state represented by the
+configuration.
+
+In addition to the diff data available in the plan, there is a "planned state"
+that is available through this import, via the
+[`planned_values`](#the-planned_values-collection) collection. This collection
+presents the Terraform state as how it might look after the plan data is
+applied, but is not guaranteed to be the final state.
+
+The data in the `tfplan/v2` import is sourced from the JSON configuration file
+that is generated by the [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) command. For more information on
+the file format, see the [JSON Output Format](https://developer.hashicorp.com/terraform/internals/json-format)
+page.
+
+The entirety of the JSON output file is exposed as a Sentinel map via the
+[`raw`](#the-raw-collection) collection. This allows direct, low-level access to
+the JSON data, but should only be used in complex situations where the
+higher-level collections do not serve the purpose.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfplan/v2" {
+ config = {
+ "plan_path": "./path/to/plan.json"
+ }
+}
+```
+
+## Import Overview
+
+The `tfplan/v2` import is structured as a series of _collections_, keyed as a
+specific format depending on the collection.
+
+```
+tfplan/v2
+├── terraform_version (string)
+├── variables
+│ └── (indexed by name)
+│ ├── name (string)
+│ └── value (value)
+├── planned_values
+│ ├── outputs (tfstate/v2 outputs representation)
+│ └── resources (tfstate/v2 resources representation)
+├── resource_changes
+│ └── (indexed by address[:deposed])
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── index (float (number) or string)
+│ ├── provider_name (string)
+│ ├── deposed (string)
+│ └── change (change representation)
+├── resource_drift
+│ └── (indexed by address[:deposed])
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── index (float (number) or string)
+│ ├── provider_name (string)
+│ ├── deposed (string)
+│ └── change (change representation)
+├── output_changes
+│ └── (indexed by name)
+│ ├── name (string)
+│ └── change (change representation)
+└── raw (map)
+```
+
+The collections are:
+
+* [`variables`](#the-variables-collection) - The values of variables that have
+ been set in the plan itself. This collection only contains variables set in
+ the root module.
+* [`planned_values`](#the-planned_values-collection) - The state representation
+ of _planned values_, or an estimation of what the state will look like after
+ the plan is applied.
+* [`resource_changes`](#the-resource_changes-and-resource_drift-collections) - The set of change
+ operations for resources and data sources within this plan.
+* [`resource_drift`](#the-resource_changes-and-resource_drift-collections) - A description of the
+ changes Terraform detected when it compared the most recent state to the prior saved state.
+* [`output_changes`](#the-output_changes-collection) - The changes to outputs
+ within this plan. This collection only contains outputs set in the root
+ module.
+* [`raw`](#the-raw-collection) - Access to the raw plan data.
+
+These collections are specifically designed to be used with the
+[`filter`](/sentinel/language/collection-operations#filter-expression)
+quantifier expression in Sentinel, so that one can collect a list of resources
+to perform policy checks on without having to write complex discovery code. As
+an example, the following code will return all `aws_instance` resource changes,
+across all modules in the plan:
+
+```
+all_aws_instances = filter tfplan.resource_changes as _, rc {
+ rc.mode is "managed" and
+ rc.type is "aws_instance"
+}
+```
+
+You can add specific attributes to the filter to narrow the search, such as the
+module address, or the operation being performed. The following code would
+return resources in a module named `foo` only, and further narrow the search
+down to only resources that were being created:
+
+```
+all_aws_instances = filter tfplan.resource_changes as _, rc {
+ rc.module_address is "module.foo" and
+ rc.mode is "managed" and
+ rc.type is "aws_instance" and
+ rc.change.actions is ["create"]
+}
+```
+
+### Change Representation
+
+Certain collections in this import contain a _change representation_, an object
+with details about changes to a particular entity, such as a resource (within
+the [`resource_changes`](#the-resource_changes-collection) collection), or
+output (within the [`output_changes`](#the-output_changes-collection)
+collection).
+
+```
+(change representation)
+├── actions (list)
+├── before (value, or map)
+├── after (value, or map)
+└── after_unknown (boolean, or map of booleans)
+```
+
+This change representation contains the following fields:
+
+* `actions` - A list of actions being carried out for this change. The order is
+ important, for example a regular replace operation is denoted by `["delete",
+ "create"]`, but a
+ [`create_before_destroy`](https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle#create_before_destroy)
+ resource will have an operation order of `["create", "delete"]`.
+* `before` - The representation of the resource data object value before the
+ action. For create-only actions, this is unset. For no-op actions, this value
+ will be identical with `after`.
+* `after` - The representation of the resource data object value after the
+ action. For delete-only actions, this is unset. For no-op actions, this value
+ will be identical with `before`. Note that unknown values will not show up in
+ this field.
+* `after_unknown` - A deep object of booleans that denotes any values that are
+ unknown in a resource. These values were previously referred to as "computed"
+ values. If the value cannot be found in this map, then its value should be
+ available within `after`, so long as the operation supports it.
+
+#### Actions
+
+As mentioned above, actions show up within the `actions` field of a change
+representation and indicate the type of actions being performed as part of the
+change, and the order that they are being performed in.
+
+The current list of actions are as follows:
+
+* `create` - The action will create the associated entity. Depending on the
+ order this appears in, the entity may be created alongside a copy of the
+ entity before replacing it.
+* `read` - The action will read the associated entity. In practice, seeing this
+ change type should be rare, as reads generally happen before a plan is
+ executed (usually during a refresh).
+* `update` - The action will update the associated entity in a way that alters its state
+ in some way.
+* `delete` - The action will remove the associated entity, deleting any
+ applicable state and associated real resources or infrastructure.
+* `no-op` - No action will be performed on the associated entity.
+
+The `actions` field is a list, as some real-world actions are actually a
+composite of more than one primitive action. At this point in time, this
+is generally only applicable to resource replacement, in which the following
+action orders apply:
+
+* **Normal replacement:** `["delete", "create"]` - Applies to default lifecycle
+ configurations.
+* **Create-before-destroy:** `["create", "delete"]` - Applies when
+ [`create_before_destroy`](https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle#create_before_destroy)
+ is used in a lifecycle configuration.
+
+Note that, in most situations, the plan will list all "changes", including no-op
+changes. This makes filtering on change type crucial to the accurate selection
+of data if you are concerned with the state change of a particular resource.
+
+To filter on a change type, use exact list comparison. For example, the
+following example from the [Import Overview](#import-overview) filters on
+exactly the resources being created _only_:
+
+```
+all_aws_instances = filter tfplan.resource_changes as _, rc {
+ rc.module_address is "module.foo" and
+ rc.mode is "managed" and
+ rc.type is "aws_instance" and
+ rc.change.actions is ["create"]
+}
+```
+
+#### `before`, `after`, and `after_unknown`
+
+The exact attribute changes for a particular operation are outlined in the
+`before` and `after` attributes. Depending on the entity being operated on, this
+will either be a map (as with
+[`resource_changes`](#the-resource_changes-collection)) or a singular value (as
+with [`output_changes`](#the-output_changes-collection)).
+
+What you can expect in these fields varies depending on the operation:
+
+* For fresh create operations, `before` will generally be `null`, and `after`
+ will contain the data you can expect to see after the change.
+* For full delete operations, this will be reversed - `before` will contain
+ data, and `after` will be `null`.
+* Update or replace operations will have data in both fields relevant to their
+ states before and after the operation.
+* No-op operations should have identical data in `before` and `after`.
+
+For resources, if a field cannot be found in `after`, it generally means one of
+two things:
+
+* The attribute does not exist in the resource schema. Generally, known
+ attributes that do not have a value will show up as `null` or otherwise empty
+ in `after`.
+* The attribute is _unknown_, that is, it was unable to be determined at plan
+ time and will only be available after apply-time values have been able to be
+ calculated.
+
+In the latter case, there should be a value for the particular attribute in
+`after_unknown`, which can be checked to assert that the value is indeed
+unknown, versus invalid:
+
+```
+import "tfplan/v2" as tfplan
+
+no_unknown_amis = rule {
+ all filter tfplan.resource_changes as _, rc {
+ rc.module_address is "module.foo" and
+ rc.mode is "managed" and
+ rc.type is "aws_instance" and
+ rc.change.actions is ["create"]
+ } as _, rc {
+ rc.change.after_unknown.ami else false is false
+ }
+}
+```
+
+For output changes, `after_unknown` will simply be `true` if the value won't be
+known until the plan is applied.
+
+## The `terraform_version` Value
+
+The top-level `terraform_version` value in this import gives the Terraform
+version that made the plan. This can be used to do version validation.
+
+```
+import "tfplan/v2" as tfplan
+import "strings"
+
+v = strings.split(tfplan.terraform_version, ".")
+version_major = int(v[1])
+version_minor = int(v[2])
+
+main = rule {
+ version_major is 12 and version_minor >= 19
+}
+```
+
+## The `variables` Collection
+
+The `variables` collection is a collection of the variables set in the root
+module when creating the plan.
+
+This collection is indexed on the name of the variable.
+
+The valid values are:
+
+* `name` - The name of the variable, also used as the collection key.
+* `value` - The value of the variable assigned during the plan.
+
+## The `planned_values` Collection
+
+The `planned_values` collection is a special collection in that it contains two
+fields that alias to state collections with the _planned_ state set. This is the
+best prediction of what the state will look like after the plan is executed.
+
+The two fields are:
+
+* `outputs` - The prediction of what output values will look like after the
+ state is applied. For more details on the structure of this collection, see
+ the [`outputs`](/sentinel/features/terraform/tfstate-v2#the-outputs-collection) collection in the
+ [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2) documentation.
+* `resources` - The prediction of what resource values will look like after the
+ state is applied. For more details on the structure of this collection, see
+ the [`resources`](/sentinel/features/terraform/tfstate-v2#the-resources-collection) collection in the
+ [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2) documentation.
+
+-> **NOTE:** Unknown values are omitted from the `planned_values` state
+representations, regardless of whether or not they existed before. Use
+[`resource_changes`](#the-resource_changes-collection) if awareness of unknown
+data is important.
+
+## The `resource_changes` and `resource_drift` Collections
+
+The `resource_changes` and `resource_drift` collections are a set of change operations for resources
+and data sources within this plan.
+
+The `resource_drift` collection provides a description of the changes Terraform detected
+when it compared the most recent state to the prior saved state.
+
+The `resource_changes` collection includes all resources that have been found in the configuration and state,
+regardless of whether or not they are changing.
+
+~> When [resource targeting](/terraform/cli/commands/plan#resource-targeting) is in effect, the `resource_changes` collection will only include the resources specified as targets for the run. This may lead to unexpected outcomes if a policy expects a resource to be present in the plan. To prohibit targeted runs altogether, ensure [`tfrun.target_addrs`](/terraform/cloud-docs/policy-enforcement/sentinel/import/tfrun#value-target_addrs) is undefined or empty.
+
+This collection is indexed on the complete resource address as the key. If
+`deposed` is non-empty, it is appended to the end, and may look something like
+`aws_instance.foo:deposed-abc123`.
+
+An element contains the following fields:
+
+* `address` - The absolute resource address - also the key for the collection's
+ index, if `deposed` is empty.
+
+* `module_address` - The module portion of the absolute resource address.
+
+* `mode` - The resource mode, either `managed` (resources) or `data` (data
+ sources).
+
+* `type` - The resource type, example: `aws_instance` for `aws_instance.foo`.
+
+* `name` - The resource name, example: `foo` for `aws_instance.foo`.
+
+* `index` - The resource index. Can be either a number or a string.
+
+* `provider_name` - The name of the provider this resource belongs to. This
+ allows the provider to be interpreted unambiguously in the unusual situation
+ where a provider offers a resource type whose name does not start with its own
+ name, such as the `googlebeta` provider offering `google_compute_instance`.
+
+ -> **Note:** Starting with Terraform 0.13, the `provider_name` field contains the
+ _full_ source address to the provider in the Terraform Registry. Example:
+ `registry.terraform.io/hashicorp/null` for the null provider.
+
+* `deposed` - An identifier used during replacement operations, and can be used
+ to identify the exact resource being replaced in state.
+
+* `change` - The data describing the change that will be made to this resource.
+ For more details, see [Change Representation](#change-representation).
+
+## The `output_changes` Collection
+
+The `output_changes` collection is a collection of the change operations for
+outputs within this plan.
+
+Only outputs for the root module are included.
+
+This collection is indexed by the name of the output. The fields in a collection
+value are below:
+
+* `name` - The name of the output, also the index key.
+* `change` - The data describing the change that will be made to this output.
+ For more details, see [Change Representation](#change-representation).
+
+## The `raw` Collection
+
+The `raw` collection exposes the raw, unprocessed plan data.
+
+This is the same data that is produced by [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) on the plan file for the run this
+policy check is attached to.
+
+Use of this data is only recommended in expert situations where the data the
+collections present may not exactly serve the needs of the policy. For more
+information on the file format, see the [JSON Output
+Format](https://developer.hashicorp.com/terraform/internals/json-format) page.
+
+-> **NOTE:** Although designed to be relatively stable, the actual makeup for
+the JSON output format is a Terraform CLI concern and as such not managed by
+Sentinel. Use at your own risk, follow the [Terraform CLI
+project](https://github.com/hashicorp/terraform), and watch the file format
+documentation for any changes.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/features/terraform/tfstate-v1.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/features/terraform/tfstate-v1.mdx
new file mode 100644
index 0000000000..9f3487ad08
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/features/terraform/tfstate-v1.mdx
@@ -0,0 +1,552 @@
+---
+page_title: tfstate/v1 - terraform - Features
+sidebar_current: docs-features-terraform-tfstate-v1
+description: The tfstate/v1 import provides access to a Terraform state.
+layout: docs
+---
+
+# Import: tfstate/v1
+
+The `tfstate/v1` import provides access to the Terraform state.
+
+The _state_ is the data that Terraform has recorded about a workspace at a
+particular point in its lifecycle, usually after an apply. You can read more
+general information about how Terraform uses state [here][ref-tf-state].
+
+[ref-tf-state]: https://developer.hashicorp.com/terraform/language/state
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfstate/v1" {
+ config = {
+ "path": "./path/to/plan.json"
+ }
+}
+```
+
+## Namespace Overview
+
+The following is a tree view of the import namespace. For more detail on a
+particular part of the namespace, see below.
+
+-> Note that the root-level alias keys shown here (`data`, `outputs`, `path`,
+and `resources`) are shortcuts to a [module namespace](#namespace-module) scoped
+to the root module. For more details, see the section on [root namespace
+aliases](#root-namespace-aliases).
+
+```
+tfstate/v1
+├── module() (function)
+│ └── (module namespace)
+│ ├── path ([]string)
+│ ├── data
+│ │ └── TYPE.NAME[NUMBER]
+│ │ ├── attr (map of keys)
+│ │ ├── depends_on ([]string)
+│ │ ├── id (string)
+│ │ └── tainted (boolean)
+│ ├── outputs (root module only in TF 0.12 or later)
+│ │ └── NAME
+│ │ ├── sensitive (bool)
+│ │ ├── type (string)
+│ │ └── value (value)
+│ └── resources
+│ └── TYPE.NAME[NUMBER]
+│ ├── attr (map of keys)
+│ ├── depends_on ([]string)
+│ ├── id (string)
+│ └── tainted (boolean)
+│
+├── module_paths ([][]string)
+├── terraform_version (string)
+│
+├── data (root module alias)
+├── outputs (root module alias)
+├── path (root module alias)
+└── resources (root module alias)
+```
+
+## Namespace: Root
+
+The root-level namespace consists of the values and functions documented below.
+
+In addition to this, the root-level `data`, `outputs`, `path`, and `resources`
+keys alias to their corresponding namespaces or values within the [module
+namespace](#namespace-module).
+
+### Function: `module()`
+
+```
+module = func(ADDR)
+```
+
+* **Return Type:** A [module namespace](#namespace-module).
+
+The `module()` function in the [root namespace](#namespace-root) returns the
+[module namespace](#namespace-module) for a particular module address.
+
+The address must be a list and is the module address, split on the period (`.`),
+excluding the root module.
+
+Hence, a module with an address of simply `foo` (or `root.foo`) would be
+`["foo"]`, and a module within that (so address `foo.bar`) would be read as
+`["foo", "bar"]`.
+
+[`null`][ref-null] is returned if a module address is invalid, or if the module
+is not present in the state.
+
+[ref-null]: /sentinel/language/spec#null
+
+As an example, given the following module block:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+If the module contained the following content:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if the resource was present in
+the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.module(["foo"]).resources.null_resource.foo[0].attr.triggers.foo is "bar" }
+```
+
+### Value: `module_paths`
+
+* **Value Type:** List of a list of strings.
+
+The `module_paths` value within the [root namespace](#namespace-root) is a list
+of all of the modules within the Terraform state at plan-time.
+
+Modules not present in the state will not be present here, even if they are
+present in the configuration or the diff.
+
+This data is represented as a list of a list of strings, with the inner list
+being the module address, split on the period (`.`).
+
+The root module is included in this list, represented as an empty inner list, as
+long as it is present in state.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The value of `module_paths` would be:
+
+```
+[
+ [],
+ ["foo"],
+]
+```
+
+And the following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.module_paths contains ["foo"] }
+```
+
+-> Note the above example only applies if the module is present in the state.
+
+#### Iterating Through Modules
+
+Iterating through all modules to find particular resources can be useful. This
+[example][iterate-over-modules] shows how to use `module_paths` with the
+[`module()` function](#function-module-) to find all resources of a
+particular type from all modules using the `tfplan` import. By changing `tfplan`
+in this function to `tfstate`, you could make a similar function find all
+resources of a specific type in the current state.
+
+[iterate-over-modules]: https://developer.hashicorp.com/terraform/cloud-docs/policy-enforcement/sentinel#sentinel-imports
+
+### Value: `terraform_version`
+
+* **Value Type:** String.
+
+The `terraform_version` value within the [root namespace](#namespace-root)
+represents the version of Terraform in use when the state was saved. This can be
+used to enforce a specific version of Terraform in a policy check.
+
+As an example, the following policy would evaluate to `true` as long as the
+state was made with a version of Terraform in the 0.11.x series, excluding any
+pre-release versions (example: `-beta1` or `-rc1`):
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.terraform_version matches "^0\\.11\\.\\d+$" }
+```
+
+-> **NOTE:** This value is also available via the [`tfplan`](/sentinel/features/terraform/tfplan-v1)
+import, which will be more current when a policy check is run against a plan.
+It's recommended you use the value in `tfplan` until Terraform Cloud
+supports policy checks in other stages of the workspace lifecycle. See the
+[`terraform_version`][import-tfplan-terraform-version] reference within the
+`tfplan` import for more details.
+
+[import-tfplan-terraform-version]: /sentinel/features/terraform/tfplan-v1#value-terraform_version
+
+## Namespace: Module
+
+The **module namespace** can be loaded by calling
+[`module()`](#function-module-) for a particular module.
+
+It can be used to load the following child namespaces, in addition to the values
+documented below:
+
+* `data` - Loads the [resource namespace](#namespace-resources-data-sources),
+ filtered against data sources.
+* `outputs` - Loads the [output namespace](#namespace-outputs), which supply the
+ outputs present in this module's state. Note that with Terraform 0.12 or
+ later, this value is only available for the root namespace.
+* `resources` - Loads the [resource
+ namespace](#namespace-resources-data-sources), filtered against resources.
+
+### Root Namespace Aliases
+
+The root-level `data`, `outputs`, and `resources` keys both alias to their
+corresponding namespaces within the module namespace, loaded for the root
+module. They are the equivalent of running `module([]).KEY`.
+
+### Value: `path`
+
+* **Value Type:** List of strings.
+
+The `path` value within the [module namespace](#namespace-module) contains the
+path of the module that the namespace represents. This is represented as a list
+of strings.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The following policy would evaluate to `true`, _only_ if the module was present
+in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.module(["foo"]).path contains "foo" }
+```
+
+## Namespace: Resources/Data Sources
+
+The **resource namespace** is a namespace _type_ that applies to both resources
+(accessed by using the `resources` namespace key) and data sources (accessed
+using the `data` namespace key).
+
+Accessing an individual resource or data source within each respective namespace
+can be accomplished by specifying the type, name, and resource number (as if the
+resource or data source had a `count` value in it) in the syntax
+`[resources|data].TYPE.NAME[NUMBER]`. Note that NUMBER is always needed, even if
+you did not use `count` in the resource.
+
+In addition, each of these namespace levels is a map, allowing you to filter
+based on type and name.
+
+-> The (somewhat strange) notation here of `TYPE.NAME[NUMBER]` may imply that
+the inner resource index map is actually a list, but it's not - using the square
+bracket notation over the dotted notation (`TYPE.NAME.NUMBER`) is required here
+as an identifier cannot start with number.
+
+Some examples of multi-level access are below:
+
+* To fetch all `aws_instance.foo` resource instances within the root module, you
+ can specify `tfstate.resources.aws_instance.foo`. This would then be indexed
+ by resource count index (`0`, `1`, `2`, and so on). Note that as mentioned
+ above, these elements must be accessed using square-bracket map notation (so
+ `[0]`, `[1]`, `[2]`, and so on) instead of dotted notation.
+* To fetch all `aws_instance` resources within the root module, you can specify
+ `tfstate.resources.aws_instance`. This would be indexed from the names of
+ each resource (`foo`, `bar`, and so on), with each of those maps containing
+ instances indexed by resource count index as per above.
+* To fetch all resources within the root module, irrespective of type, use
+ `tfstate.resources`. This is indexed by type, as shown above with
+ `tfstate.resources.aws_instance`, with names being the next level down, and so
+ on.
+
+Further explanation of the namespace will be in the context of resources. As
+mentioned, when operating on data sources, use the same syntax, except with
+`data` in place of `resources`.
+
+### Value: `attr`
+
+* **Value Type:** A string-keyed map of values.
+
+The `attr` value within the [resource
+namespace](#namespace-resources-data-sources) is a direct mapping to the state
+of the resource.
+
+The map is a complex representation of these values with data going as far down
+as needed to represent any state values such as maps, lists, and sets.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if the resource was in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.resources.null_resource.foo[0].attr.triggers.foo is "bar" }
+```
+
+### Value: `depends_on`
+
+* **Value Type:** A list of strings.
+
+The `depends_on` value within the [resource
+namespace](#namespace-resources-data-sources) contains the dependencies for the
+resource.
+
+This is a list of full resource addresses, relative to the module (example:
+`null_resource.foo`).
+
+As an example, given the following resources:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+
+resource "null_resource" "bar" {
+ # ...
+
+ depends_on = [
+ "null_resource.foo",
+ ]
+}
+```
+
+The following policy would evaluate to `true` if the resource was in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.resources.null_resource.bar[0].depends_on contains "null_resource.foo" }
+```
+
+### Value: `id`
+
+* **Value Type:** String.
+
+The `id` value within the [resource
+namespace](#namespace-resources-data-sources) contains the id of the resource.
+
+-> **NOTE:** The example below uses a _data source_ here because the
+[`null_data_source`][ref-tf-null-data-source] data source gives a static ID,
+which makes documenting the example easier. As previously mentioned, data
+sources share the same namespace as resources, but need to be loaded with the
+`data` key. For more information, see the
+[synopsis](#namespace-resources-data-sources) for the namespace itself.
+
+[ref-tf-null-data-source]: https://registry.terraform.io/providers/hashicorp/null/latest/docs/data-sources/data_source
+
+As an example, given the following data source:
+
+```hcl
+data "null_data_source" "foo" {
+ # ...
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.data.null_data_source.foo[0].id is "static" }
+```
+
+### Value: `tainted`
+
+* **Value Type:** Boolean.
+
+The `tainted` value within the [resource
+namespace](#namespace-resources-data-sources) is `true` if the resource is
+marked as tainted in Terraform state.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`, if the resource was marked as
+tainted in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.resources.null_resource.foo[0].tainted }
+```
+
+## Namespace: Outputs
+
+The **output namespace** represents all of the outputs present within a
+[module](#namespace-module). Outputs are present in a state if they were saved
+during a previous apply, or if they were updated with known values during the
+pre-plan refresh.
+
+**With Terraform 0.11 or earlier** this can be used to fetch both the outputs
+of the root module, and the outputs of any module in the state below the root.
+This makes it possible to see outputs that have not been threaded to the root
+module.
+
+**With Terraform 0.12 or later** outputs are available in the top-level (root
+module) namespace only and not accessible within submodules.
+
+This namespace is indexed by output name.
+
+### Value: `sensitive`
+
+* **Value Type:** Boolean.
+
+The `sensitive` value within the [output namespace](#namespace-outputs) is
+`true` when the output has been [marked as sensitive][ref-tf-sensitive-outputs].
+
+[ref-tf-sensitive-outputs]: https://developer.hashicorp.com/terraform/language/values/outputs#sensitive-suppressing-values-in-cli-output
+
+As an example, given the following output:
+
+```hcl
+output "foo" {
+ sensitive = true
+ value = "bar"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.outputs.foo.sensitive }
+```
+
+### Value: `type`
+
+* **Value Type:** String.
+
+The `type` value within the [output namespace](#namespace-outputs) gives the
+output's type. This will be one of `string`, `list`, or `map`. These are
+currently the only types available for outputs in Terraform.
+
+As an example, given the following output:
+
+```hcl
+output "string" {
+ value = "foo"
+}
+
+output "list" {
+ value = [
+ "foo",
+ "bar",
+ ]
+}
+
+output "map" {
+ value = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+type_string = rule { tfstate.outputs.string.type is "string" }
+type_list = rule { tfstate.outputs.list.type is "list" }
+type_map = rule { tfstate.outputs.map.type is "map" }
+
+main = rule { type_string and type_list and type_map }
+```
+
+### Value: `value`
+
+* **Value Type:** String, list, or map.
+
+The `value` value within the [output namespace](#namespace-outputs) is the value
+of the output in question.
+
+Note that the only valid primitive output type in Terraform is currently a
+string, which means that any int, float, or boolean value will need to be
+converted before it can be used in comparison. This does not apply to primitives
+within maps and lists, which will be their original types.
+
+As an example, given the following output blocks:
+
+```hcl
+output "foo" {
+ value = "bar"
+}
+
+output "number" {
+ value = "42"
+}
+
+output "map" {
+ value = {
+ foo = "bar"
+ number = 42
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+value_foo = rule { tfstate.outputs.foo.value is "bar" }
+value_number = rule { int(tfstate.outputs.number.value) is 42 }
+value_map_string = rule { tfstate.outputs.map.value["foo"] is "bar" }
+value_map_int = rule { tfstate.outputs.map.value["number"] is 42 }
+
+main = rule { value_foo and value_number and value_map_string and value_map_int }
+```
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/features/terraform/tfstate-v2.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/features/terraform/tfstate-v2.mdx
new file mode 100644
index 0000000000..9b29aa2c51
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/features/terraform/tfstate-v2.mdx
@@ -0,0 +1,176 @@
+---
+page_title: tfstate/v2 - terraform - Features
+sidebar_current: docs-features-terraform-tfstate-v2
+description: The tfstate/v2 import provides access to a Terraform state.
+layout: docs
+---
+
+# Import: tfstate/v2
+
+The `tfstate/v2` import provides access to a Terraform state.
+
+The _state_ is the data that Terraform has recorded about a workspace at a
+particular point in its lifecycle, usually after an apply. You can read more
+general information about how Terraform uses state
+[here](https://developer.hashicorp.com/terraform/language/state).
+
+The data in the `tfstate/v2` import is sourced from the JSON configuration file
+that is generated by the [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) command. For more information on
+the file format, see the [JSON Output Format](https://developer.hashicorp.com/terraform/internals/json-format)
+page.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfstate/v2" {
+ config = {
+ "path": "./path/to/plan.json"
+ }
+}
+```
+
+## Import Overview
+
+The `tfstate/v2` import is structured as currently two _collections_, keyed in
+resource address and output name, respectively.
+
+```
+(tfstate/v2)
+├── terraform_version (string)
+├── resources
+│ └── (indexed by address)
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── index (float (number) or string)
+│ ├── provider_name (string)
+│ ├── values (map)
+│ ├── depends_on (list of strings)
+│ ├── tainted (boolean)
+│ └── deposed_key (string)
+└── outputs
+ └── (indexed by name)
+ ├── name (string)
+ ├── sensitive (boolean)
+ └── value (value)
+```
+
+The collections are:
+
+* [`resources`](#the-resources-collection) - The state of all resources across
+ all modules in the state.
+* [`outputs`](#the-outputs-collection) - The state of all outputs from the root module in the state.
+
+These collections are specifically designed to be used with the
+[`filter`](/sentinel/language/collection-operations#filter-expression)
+quantifier expression in Sentinel, so that one can collect a list of resources
+to perform policy checks on without having to write complex module traversal. As
+an example, the following code will return all `aws_instance` resource types
+within the state, regardless of what module they are in:
+
+```
+all_aws_instances = filter tfstate.resources as _, r {
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+You can add specific attributes to the filter to narrow the search, such as the
+module address. The following code would return resources in a module named
+`foo` only:
+
+```
+all_aws_instances = filter tfstate.resources as _, r {
+ r.module_address is "module.foo" and
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+## The `terraform_version` Value
+
+The top-level `terraform_version` value in this import gives the Terraform
+version that recorded the state. This can be used to do version validation.
+
+```
+import "tfstate/v2" as tfstate
+import "strings"
+
+v = strings.split(tfstate.terraform_version, ".")
+version_major = int(v[1])
+version_minor = int(v[2])
+
+main = rule {
+ version_major is 12 and version_minor >= 19
+}
+```
+
+## The `resources` Collection
+
+The `resources` collection is a collection representing all of the resources in
+the state, across all modules.
+
+This collection is indexed on the complete resource address as the key.
+
+An element in the collection has the following values:
+
+* `address` - The absolute resource address - also the key for the collection's
+ index.
+
+* `module_address` - The address portion of the absolute resource address.
+
+* `mode` - The resource mode, either `managed` (resources) or `data` (data
+ sources).
+
+* `type` - The resource type, example: `aws_instance` for `aws_instance.foo`.
+
+* `name` - The resource name, example: `foo` for `aws_instance.foo`.
+
+* `index` - The resource index. Can be either a number or a string.
+
+* `provider_name` - The name of the provider this resource belongs to. This
+ allows the provider to be interpreted unambiguously in the unusual situation
+ where a provider offers a resource type whose name does not start with its own
+ name, such as the `googlebeta` provider offering `google_compute_instance`.
+
+ -> **Note:** Starting with Terraform 0.13, the `provider_name` field contains the
+ _full_ source address to the provider in the Terraform Registry. Example:
+ `registry.terraform.io/hashicorp/null` for the null provider.
+
+* `values` - An object (map) representation of the attribute values of the
+ resource, whose structure depends on the resource type schema. When accessing
+ proposed state through the [`planned_values`](/sentinel/features/terraform/tfplan-v2#the-planned_values-collection)
+ collection of the tfplan/v2 import, unknown values will be omitted.
+
+* `depends_on` - The addresses of the resources that this resource depends on.
+
+* `tainted` - `true` if the resource has been explicitly marked as
+ [tainted](https://developer.hashicorp.com/terraform/cli/commands/taint) in the state.
+
+* `deposed_key` - Set if the resource has been marked deposed and will be
+ destroyed on the next apply. This matches the deposed field in the
+ [`resource_changes`](/sentinel/features/terraform/tfplan-v2#the-resource_changes-collection)
+ collection in the [`tfplan/v2`](/sentinel/features/terraform/tfplan-v2) import.
+
+## The `outputs` Collection
+
+The `outputs` collection is a collection of outputs from the root module of the
+state.
+
+Note that no child modules are included in this output set, and there is no way
+to fetch child module output values. This is to encourage the correct flow of
+outputs to the recommended root consumption level.
+
+The collection is indexed on the output name, with the following fields:
+
+* `name`: The name of the output, also the collection key.
+* `sensitive`: Whether or not the value was marked as
+ [sensitive](https://developer.hashicorp.com/terraform/language/values/outputs#sensitive-suppressing-values-in-cli-output)
+ in
+ configuration.
+* `value`: The value of the output.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/functions/append.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/functions/append.mdx
new file mode 100644
index 0000000000..81395154f6
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/functions/append.mdx
@@ -0,0 +1,46 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Append'
+sidebar_current: docs-funcs-append
+description: The built-in function `append` appends a value to the end of a list.
+layout: docs
+---
+
+# Builtin Function: append
+
+The built-in function `append` appends a value to the end of a list. The list
+is modified in-place. The return value will always be [undefined](/sentinel/language/undefined).
+
+`append` called on any value other than a list or undefined will result
+in an immediate error.
+
+Examples:
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+### Why does this function return undefined?
+
+This function returns `undefined` to avoid unintended side effects of the function in the context
+of a [rule](/sentinel/language/rules).
+
+For example, if `append` returned the list value as a result, the following policy would depend
+on the order in which rules are referenced:
+
+```sentinel
+list = []
+a = rule { append(list, 1) contains 1 }
+b = rule { append(list, 2) contains 2 }
+
+// Scenario 1: list becomes [1, 2]
+main = rule { a and b }
+
+// Scenario 2: list becomes [2, 1]
+main = rule { b and a }
+```
+
+This sort of side effect behavior introduces complex and difficult to track logical errors in programs.
+As a result, functions like this return `undefined` and modify values in-place.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/functions/delete.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/functions/delete.mdx
new file mode 100644
index 0000000000..3b60803997
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/functions/delete.mdx
@@ -0,0 +1,25 @@
+---
+page_title: 'Sentinel Language - Builtin Function: delete'
+sidebar_current: docs-funcs-delete
+description: The built-in function `delete` deletes an element from a map.
+layout: docs
+---
+
+# Builtin Function: delete
+
+The built-in function `delete` deletes an element from a map.
+
+If the element to delete does not exist, the map is left unchanged.
+Calling `delete` on a value that is not a map or
+[undefined](/sentinel/language/undefined)
+is an immediate error. The result of `delete` is always `undefined`.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/functions/error.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/functions/error.mdx
new file mode 100644
index 0000000000..96d253b662
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/functions/error.mdx
@@ -0,0 +1,15 @@
+---
+page_title: 'Sentinel Language - Builtin Function: error'
+sidebar_current: docs-funcs-error
+description: >-
+ The built-in function `error` is used to exit immediately with an error
+ message.
+layout: docs
+---
+
+# Builtin Function: error
+
+The built-in function `error` is used to exit immediately with an error message.
+
+Please see the [page on errors](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/functions/index.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/functions/index.mdx
new file mode 100644
index 0000000000..4e12143ad0
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/functions/index.mdx
@@ -0,0 +1,13 @@
+---
+page_title: Sentinel Language - Builtin Functions
+sidebar_current: docs-funcs
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Language: Builtin Functions
+
+Builtin functions are
+[functions](/sentinel/language/functions)
+that are available in all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in function.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/functions/keys.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/functions/keys.mdx
new file mode 100644
index 0000000000..a96cafe897
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/functions/keys.mdx
@@ -0,0 +1,22 @@
+---
+page_title: 'Sentinel Language - Builtin Function: keys'
+sidebar_current: docs-funcs-keys
+description: The built-in function `keys` returns the keys of a map.
+layout: docs
+---
+
+# Builtin Function: keys
+
+The built-in function `keys` returns the keys of a map.
+
+`keys` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned keys are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+```
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/functions/length.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/functions/length.mdx
new file mode 100644
index 0000000000..8b874b14c4
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/functions/length.mdx
@@ -0,0 +1,23 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Length'
+sidebar_current: docs-funcs-length
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Builtin Function: length
+
+The built-in function `length` returns the length of a collection or string.
+
+The length of a string is the number of bytes in the string. It is not
+necessarilly the number of characters. The length of a collection is the
+number of elements in that collection. The length of
+[undefined](/sentinel/language/undefined) is `undefined`.
+
+Examples:
+
+```sentinel
+length("foo") // 3
+length([1, 2, 3, 4]) // 4
+length({ "key": "value" }) // 1
+```
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/functions/print.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/functions/print.mdx
new file mode 100644
index 0000000000..bcf441d09d
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/functions/print.mdx
@@ -0,0 +1,13 @@
+---
+page_title: 'Sentinel Language - Builtin Function: print'
+sidebar_current: docs-funcs-print
+description: The built-in function `print` is used for logging and debugging.
+layout: docs
+---
+
+# Builtin Function: print
+
+The built-in function `print` is used for logging and debugging.
+
+Please see the [page on logging](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/functions/range.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/functions/range.mdx
new file mode 100644
index 0000000000..0f86bfc402
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/functions/range.mdx
@@ -0,0 +1,49 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Range'
+sidebar_current: docs-funcs-range
+description: The built-in function `range` returns a list of numbers in a range.
+layout: docs
+---
+
+# Builtin Function: range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it
+defaults to `1`.
+
+Negative steps are permitted and count down from `start` towards `end`, instead
+of up.
+
+The range ends when the next value given by the sum of the last value and `step`
+would exceed `end`, regardless of if it has been reached.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4] - starts at 1 instead of 0
+range(1, 5, 2) // [1,3] - 3+2 does not satisfy r[-1] < end
+range(0, -3, -1) // [0,-1,-2] - example of negative range
+```
+
+Impossible ranges, or ranges where `start` will never reach `end` given `step`,
+yield an empty list.
+
+```
+range(1, 1) // [] - already starting at 1
+range(0, 1, -1) // [] - negative step will never reach 1
+```
+
+Supplying an undefined value to any argument of `range` yields an undefined
+value back.
+
+A range with a zero step is a runtime error.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/functions/values.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/functions/values.mdx
new file mode 100644
index 0000000000..8f98d3ab56
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/functions/values.mdx
@@ -0,0 +1,22 @@
+---
+page_title: 'Sentinel Language - Builtin Function: values'
+sidebar_current: docs-funcs-values
+description: The built-in function `values` returns the values of a map.
+layout: docs
+---
+
+# Builtin Function: values
+
+The built-in function `values` returns the values of a map.
+
+`values` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned values are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+values(data) // [3, 2]
+```
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/imports/base64.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/imports/base64.mdx
new file mode 100644
index 0000000000..4e58429268
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/imports/base64.mdx
@@ -0,0 +1,46 @@
+---
+page_title: 'Import: base64'
+sidebar_current: docs-imports-base64
+description: The base64 import enables a Sentinel policy to encode and decode Base64 values.
+layout: docs
+---
+
+# Import: base64
+
+The base64 import enables a Sentinel policy to encode and decode Base64 values.
+
+### base64.encode(str)
+
+Encodes the string `str` into a Base64 encoded string, using the standard
+encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-4).
+
+```sentinel
+enc = base64.encode("foo") // "Zm9v"
+```
+
+### base64.decode(str)
+
+Decodes the Base64 encoded string `str`, returning the string result,
+using the standard encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-4).
+
+```sentinel
+dec = base64.decode("Zm9v") // "foo"
+```
+
+### base64.urlencode(str)
+
+Encodes the string `str` into a Base64 encoded string, using the alternate
+encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-5).
+
+```sentinel
+enc = base64.urlencode("foo") // "Zm9v"
+```
+
+### base64.urldecode(str)
+
+Decodes the Base64 encoded string `str`, returning the string result, using the
+alternate encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-5).
+
+```sentinel
+dec = base64.urldecode("Zm9v") // "foo"
+```
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/imports/decimal.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/imports/decimal.mdx
new file mode 100644
index 0000000000..a6633a38e0
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/imports/decimal.mdx
@@ -0,0 +1,318 @@
+---
+page_title: 'Import: decimal'
+sidebar_current: docs-imports-decimal
+description: |-
+ The decimal import provides functions for operating on numbers represented
+ as decimals.
+layout: docs
+---
+
+# Import: decimal
+
+The decimal import provides functions for operating on numbers represented
+as decimals.
+
+Binary floating-point numbers provided by the `float` type are unable to
+fully represent numbers like `1.1` and `2.2`. The decimal import can
+represent these numbers exactly, and so should be used when exactness is
+required.
+
+Decimals are created through the `new` function, which takes any type that
+can represent a number. Arguments to the decimal operators can also take
+a value of any of these types. The full list is:
+
+- float
+- int
+- string
+- existing decimal value
+
+### decimal.infinity(sign)
+
+Creates an infinite-value decimal. Infinite-value decimals are always larger
+or smaller, depending on sign, than any non-infinite decimals.
+
+```sentinel
+decimal.infinity(1) // +Infinity
+decimal.infinity(-1) // -Infinity
+```
+
+Also available as `decimal.inf`.
+
+### decimal.nan
+
+A decimal representing a "not-a-number" value. NaNs are not equal to any
+other number, even themselves.
+
+```sentinel
+decimal.new(-1).sqrt() // NaN
+decimal.new(0).mul(decimal.inf(1)) // NaN
+decimal.nan.is(decimal.nan) // false
+```
+
+### decimal.is_infinite(d)
+
+Evaluates to true if the decimal is infinite.
+
+```sentinel
+decimal.is_infinite(100) // false
+decimal.is_infinite("Infinity") // true
+```
+
+Also available as `decimal.is_inf`, `decimal.isinf` and `decimal.isinfinite`.
+
+### decimal.is_nan(d)
+
+Evaluates to true if the decimal is not-a-number.
+
+```sentinel
+decimal.is_nan("NaN") // true
+```
+
+Also available as `decimal.isnan`.
+
+### decimal.new(v)
+
+Constructs a decimal from another value.
+
+```sentinel
+decimal.new("1.1") // Constructs a decimal from a string.
+decimal.new(2.2) // Constructs a decimal from a float.
+decimal.new(3) // Constructs a decimal from an integer.
+decimal.new("1.1234E+400") // Constructs a decimal from a string using E-notation.
+```
+
+## Type: number
+
+Numbers are represented internally by a sign, coefficient, and exponent.
+The value is calculated with the formula `sign * coefficient * 10^exponent`.
+Exponent is limited to 32 bits, and the coefficient is limited by the total
+precision.
+
+The maximum precision a number can represent is 100 digits. This includes
+both before and after the decimal place.
+
+### number.string
+
+A string representation of the decimal.
+
+```sentinel
+decimal.new(1.5).string // 1.5
+```
+
+### number.sign
+
+A number between `-1` and `+1` representing the sign of the decimal.
+
+```sentinel
+decimal.new(1.5).sign // 1
+```
+
+### number.coefficient
+
+The coefficient component of the decimal.
+
+```sentinel
+decimal.new(1.5).coefficient // 15
+```
+
+### number.exponent
+
+The exponent component of the decimal.
+
+```sentinel
+decimal.new(1.5).exponent // -1
+```
+
+### number.float
+
+A floating-point representation of the decimal.
+
+```sentinel
+decimal.new(1.5).float // 1.500000
+```
+
+### number.int
+
+An integer representation of the decimal. This always rounds down to the nearest integer.
+
+```sentinel
+decimal.new(1.5).int // 1
+```
+
+### number.is(v)
+
+Test for equality with another value.
+
+```sentinel
+decimal.new(1.5).is(1) // false
+```
+
+### number.is_not(v)
+
+Test for inequality with another value.
+
+```sentinel
+decimal.new(1.5).is_not(1) // true
+```
+
+### number.less_than(v)
+
+Test that the decimal is less than another value.
+Can also be called as `number.lt(v)`
+
+```sentinel
+decimal.new(1.5).less_than(1) // false
+```
+
+### number.less_than_or_equals(v)
+
+Test that the decimal is less than or equals to another value.
+Can also be called as `number.lte(v)`
+
+```sentinel
+decimal.new(1.5).less_than_or_equals(1) // false
+```
+
+### number.greater_than(v)
+
+Test that the decimal is greater than another value.
+Can also be called as `number.gt(v)`
+
+```sentinel
+decimal.new(1.5).greater_than(1) // true
+```
+
+### number.greater_than_or_equals(v)
+
+Test that the decimal is greater than or equals to another value.
+Can also be called as `number.gte(v)`
+
+```sentinel
+decimal.new(1.5).greater_than_or_equals(1) // true
+```
+
+### number.add(v)
+
+Add another number to the decimal.
+
+```sentinel
+decimal.new(1.5).add(1) // 2.5
+```
+
+### number.subtract(v)
+
+Subtract another number from the decimal.
+Can also be called as `number.sub(v)`
+
+```sentinel
+decimal.new(1.5).subtract(1) // 0.5
+```
+
+### number.multiply(v)
+
+Multiply the decimal by a number.
+Can also be called as `number.mul(v)`
+
+```sentinel
+decimal.new(1.5).multiply(2) // 3.0
+```
+
+### number.divide(v)
+
+Divide the decimal by a number.
+Can also be called as `number.div(v)`
+
+```sentinel
+decimal.new(3.0).divide(1.5) // 2
+```
+
+### number.modulo(v)
+
+Find the remainder after dividing the decimal by a number.
+Can also be called as `mod`, `remainder`, or `rem`.
+
+```sentinel
+decimal.new(1.5).modulo(1) // 0.5
+```
+
+### number.power(v)
+
+Raise the decimal to a power.
+Can also be called as `number.pow(v)`
+
+```sentinel
+decimal.new(1.5).power(2) // 2.25
+```
+
+### number.exp()
+
+The natural exponent of the decimal. This calculates `e^number`.
+
+```sentinel
+decimal.new(1.5).exp() // 4.4816...
+```
+
+### number.loge()
+
+The natural logarithm of the decimal.
+Can also be called as `number.ln()`
+
+```sentinel
+decimal.new(1.5).loge() // 0.4054...
+```
+
+### number.log10()
+
+The base-10 logarithm of the decimal.
+Can also be called as `number.log()`
+
+```sentinel
+decimal.new(1.5).log10() // 0.1760...
+```
+
+### number.square_root()
+
+The square root of the decimal.
+Can also be called as `number.sqrt()`
+
+```sentinel
+decimal.new(1.5).square_root() // 1.2247...
+```
+
+### number.ceiling()
+
+Round the decimal to the smallest integer greater or equal to itself.
+Can also be called as `number.ceil()`
+
+```sentinel
+decimal.new(1.5).ceiling() // 2
+```
+
+### number.floor()
+
+Round the decimal to the largest integer less than or equal to itself.
+
+```sentinel
+decimal.new(1.5).floor() // 1
+```
+
+### number.absolute()
+
+The absolute value of the decimal.
+Can also be called as `number.abs()`
+
+```sentinel
+decimal.new(1.5).absolute() // 1.5
+decimal.new(-1.5).absolute() // 1.5
+```
+
+### number.negate()
+
+The negated value of the decimal. A positive decimal will become negative,
+and a negative decimal will become positive.
+Can also be called as `number.neg()`
+
+```sentinel
+decimal.new(1.5).negate() // -1.5
+decimal.new(-1.5).negate() // 1.5
+```
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/imports/http.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/imports/http.mdx
new file mode 100644
index 0000000000..f94ae98c27
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/imports/http.mdx
@@ -0,0 +1,370 @@
+---
+page_title: 'Import: http'
+sidebar_current: docs-imports-http
+description: The http import enables the use of HTTP-accessible data from outside the runtime in Sentinel policy rules.
+layout: docs
+---
+
+# Import: http
+
+The http import enables the use of HTTP-accessible data from outside
+the runtime in Sentinel policy rules.
+
+The simplest example of the import in use would be:
+
+```sentinel
+import "http"
+
+resp = http.get("https://example.hashicorp.com")
+main = rule { resp.body contains "something" }
+```
+
+By default, any request response that is not a successful "200 OK" HTTP
+response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information and
+how to customize this behavior.
+
+For more advanced requests which require setting request headers, use a
+[`request` type](#type-request):
+
+```sentinel
+import "http"
+import "json"
+
+param token
+
+req = http.request("https://example.hashicorp.com").with_header("Authorization", "token "+token)
+resp = json.unmarshal(http.get(req).body)
+main = rule { resp["some_key"] is true }
+```
+
+The `with_header` function above returns the `request` object with the
+`Authorization` HTTP header set to value of the variable `some_value`. Many
+of the methods within the http import API are designed around this concept
+of method chaining, allowing for complex building of HTTP requests
+encapsulated in functions. The following is an idiomatic example further
+demonstrating this pattern:
+
+```sentinel
+import "http"
+import "json"
+
+param github_user
+param github_token
+
+client = func(req) {
+ return http.with_timeout(5).with_retries(3)
+}
+
+github_request = func(path) {
+ full_url = "https://api.github.com" + path
+ return http.request(full_url).
+ with_basic_auth(github_user, github_token).
+ with_header("Accept", "application/vnd.github.v3+json").
+ with_header("Custom", "foo"),
+}
+
+default_response = client.get(github_request("/example"))
+
+# Override the Custom header for this single request
+custom_header_response = json.unmarshal(
+ client.get(
+ github_request("/example").with_header("Custom", "bar"),
+ ),
+)
+
+# Override the timeout value for a known slower request
+slow_response = json.unmarshal(
+ client.with_timeout(15).get(github_request("/slow-endpoint")),
+)
+
+some_key_is_true = rule { json.unmarshal(default_response.body)["some_key"] is true }
+body_contains_text = rule { custom_header_response.body contains "something" }
+response_is_ok = rule { slow_response.status_code is 200 }
+
+main = rule { some_key_is_true and body_contains_text and response_is_ok }
+```
+
+### http.client
+
+Retrieve the base HTTP client with default settings. The returned client may be
+configured by calling configuration functions on it; all of these configuration
+functions on are also aliased as top-level functions in this namespace. See
+[`client`](#type-client) below.
+
+### http.request(url)
+
+Create a new [`request`](#type-request) to the specified `url` (string). A
+`request` type enables customization of headers and other concerns before then
+providing the constructed `request` to [`get()`](#client-get-url_or_request),
+[`post()`](#client-post-url_or_request) or [`send()`](#client-send-request).
+
+```sentinel
+req = http.request("example.hashicorp.com/foo").with_header("Foo", "bar")
+resp = http.get(req)
+```
+
+### http.methodPost
+
+Returns a string containing the accepted verb for POST requests, `"POST"`.
+
+### http.methodGet
+
+Returns a string containing the accepted verb for GET requests, `"GET"`.
+
+## Type: client
+
+All of the following configuration functions on the default client are also
+aliased as top-level functions in the import. This allows a short-hand (and
+idiomatic) way of configuring a new client without having to directly access
+`http.client` each time:
+
+```sentinel
+# The following two lines are semantically the same:
+resp = http.client.with_timeout(5).get(url)
+resp = http.with_timeout(5).get(url)
+```
+
+### url_or_request
+
+The [`get()`](#client-get-url_or_request) and [`post()`](#client-post-url_or_request)
+functions accept either a URL string or a request object, noted as
+`url_or_request` throughout the documentation.
+
+When `url_or_request` is a string, a request is made with the URL
+specified by the string. To customize HTTP headers and other settings, pass
+a request. By default, a request has a timeout value of 10 seconds and 1
+retry. These settings can be adjusted with [`with_timeout()`](#clientwith_timeoutinteger) and
+[`with_retries()`](#clientwith_retriesinteger), respectively.
+
+### Redirects
+
+If the response is one of the following redirect codes, the client follows the
+redirect, up to a maximum of 10 redirects:
+
+- 301 (Moved Permanently)
+- 302 (Found)
+- 303 (See Other)
+- 307 (Temporary Redirect)
+- 308 (Permanent Redirect)
+
+If the redirect limit is exceeded, the result is a policy error.
+
+By default, after any redirects are followed (up to the maximum), any request
+response that is not a successful "200 OK" response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information
+and how to customize this behavior.
+
+### client.get(url_or_request)
+
+Issue a GET request with a URL string or a `request` type. Returns a `response`
+type.
+
+```sentinel
+import "http"
+
+resp = http.get("https://example.hashicorp.com")
+main = rule { resp.body contains "something" }
+```
+
+### client.post(url_or_request)
+
+Issue a POST request with a URL string or a `request` type. Returns a `response`
+type.
+
+```sentinel
+import "http"
+
+req = http.request("https://example.hashicorp.com")
+ .with_body(json.marshal({"foo": "bar"}))
+resp = http.post(req)
+main = rule { resp.body contains "something" }
+```
+
+### client.send(request)
+
+Make a request using the supplied `request` type. Returns a `response`.
+
+```sentinel
+import "http"
+
+req = http.request("https://example.hashicorp.com")
+resp = http.send(req)
+main = rule { resp.body contains "something" }
+```
+
+### client.accept_status_codes(list)
+
+Configures a client to not automatically cause a policy failure if
+the resulting response's status code is in `list`. Any status code received
+that is not present in this list will trigger a runtime error.
+
+By default, any request response that is not a successful "200 OK" response
+causes a policy error. This is for convenience, as the most common scenario
+by far is to receive a response and immediately signal a policy error if the
+status code is not 200. Use this scoping to specify a list of non-redirect
+HTTP codes that you wish to accept without error and evaluate the response
+yourself.
+
+Redirects are not considered final status codes in this context. That is,
+redirects are always followed up to the maximum allowed value (see [`Redirects`](#redirects))
+and the final response code in the redirect chain is validated against
+`list`.
+
+```sentinel
+resp = http.accept_status_codes([200, 404]).get(url)
+main = rule { resp.status_code is 404 }
+```
+
+### client.accepted_status_codes
+
+Returns a list of the client's accepted status codes.
+
+```sentinel
+client = http.accept_status_codes([200, 404])
+main = rule { client.accepted_status_codes contains 404 }
+```
+
+### client.with_retries(integer)
+
+Sets the maximum number of retries, specified by `integer`, that should be
+attempted on an unsuccessful request. An unsuccessful request is considered
+to be any 5xx response except `501 (Not Implemented)` or a request timeout.
+By default, one retry is attempted.
+
+The maximum number of retries is 10, for a total of 11 request attempts.
+When a request exceeds the maximum number of retries without a response, the
+result is a policy error. Specifying a number larger than this will result
+in a runtime error.
+
+Between each retry, an exponentially increasing delay is observed, allowing
+time for the server to recover. This delay starts at 1 second for the first
+retry and increases each attempt thereafter. The maximum delay for a single
+attempt is 30 seconds.
+
+Retries can be disabled by specifying 0.
+
+### client.retries
+
+Returns the client's configured number of retries as an integer.
+
+### client.with_timeout(integer)
+
+Sets the request timeout to the number of seconds indicated by `integer`.
+
+Each individual request performed by the HTTP client will be limited by the
+given request timeout. This timeout includes connection time, any redirects,
+and reading the response body.
+
+A maximum of 30 seconds is allowed. Specifying a number larger than this
+will result in a runtime error.
+
+By default, requests will time out after 10 seconds.
+
+### client.timeout
+
+Returns the client's configured timeout in whole seconds as an integer.
+
+### client.without_certificate_verification()
+
+Skips verification of TLS certificates during secure HTTP operations,
+allowing for requests to `https://` endpoints which are secured with a TLS
+certificate signed by an untrusted certificate authority. Takes no arguments.
+
+By default, the HTTP client will trigger a runtime error if a TLS certificate
+presented by a server is from an untrusted certificate authority.
+
+Do not change this setting unless you are aware of the risks involved.
+Without verification, TLS accepts any certificate presented by the server
+and any host name in that certificate. In this mode, TLS is susceptible to
+man-in-the-middle attacks. This option should only be used for testing or in
+trusted networks with self-signed certificates.
+
+```sentinel
+http.without_certificate_verification().get(url)
+```
+
+### client.certificate_verification
+
+Returns true if the client requires TLS certificates to be verifiable.
+
+## Type: request
+
+### request.url
+
+Returns the request URL as a string.
+
+### request.headers
+
+Returns the configured request headers as a string-keyed map of strings.
+
+### request.body
+
+Returns the configured body as a string.
+
+### request.with_header(key, value)
+
+Returns a `request` with the header `key` (string) set to `value` (string).
+
+Values are overridden on subsequent calls to the same `key`. To specify
+multiple values, use the existing value when setting the new one.
+
+```sentinel
+original = http.request("http://example.hashicorp.com").with_header("Foo", "bar")
+overridden = original.with_header("Foo", "baz")
+multi_value = original.with_header("Foo", original.headers["Foo"] + ",baz")
+
+main = rule {
+ original.headers["Foo"] is "bar" and
+ overridden.headers["Foo"] is "baz" and
+ multi_value.headers["Foo"] is "bar,baz"
+}
+```
+
+### request.with_basic_auth(username, password)
+
+Returns a `request` with the `Authorization` header set to use HTTP Basic
+Authentication with the provided username and password.
+
+Note that with HTTP Basic Authentication the provided username and password
+are encoded, but not encrypted.
+
+### request.with_body(body)
+
+Returns a `request` with the `body` set. This value will then be sent as a body
+as part of the request. Commonly used along with the [`post()`](#client-post-url_or_request) function.
+
+```sentinel
+req = http.request("http://example.hashicorp.com")
+ .with_header("Content-Type", "application/json")
+ .with_body(json.marshal({ "foo": "bar" }))
+
+resp = http.post(req)
+```
+
+## Type: response
+
+### response.status_code
+
+The response status code as an integer, e.g. `200`.
+
+### response.headers
+
+The response headers as a map.
+
+### response.body
+
+The response body as a string. Callers should unmarshal the content to a useful
+representation as necessary based on the content type. For example, if the
+response is in JSON:
+
+```sentinel
+import "http"
+import "json"
+
+# This example endpoint returns {"some_key": true}
+req = http.request("https://example.hashicorp.com/some-json")
+
+resp = json.unmarshal(http.get(req).body)
+main = rule { keys(resp) contains "some_key" and resp["some_key"] is true }
+```
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/imports/index.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/imports/index.mdx
new file mode 100644
index 0000000000..e45bf81b81
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/imports/index.mdx
@@ -0,0 +1,18 @@
+---
+page_title: Sentinel Language - Standard Imports
+sidebar_current: docs-imports
+description: >-
+ Standard Imports are the built-in imports that are available to all Sentinel
+ policies.
+layout: docs
+---
+
+# Standard Imports
+
+Standard Imports are the built-in [imports](/sentinel/language/imports)
+that are available to all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in import.
+
+Sentinel-embedded applications can choose to allow or deny certain
+standard imports. Please reference the documentation for the Sentinel-enabled
+application you're using to determine if all standard imports are available.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/imports/json.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/imports/json.mdx
new file mode 100644
index 0000000000..3355458f14
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/imports/json.mdx
@@ -0,0 +1,31 @@
+---
+page_title: 'Import: json'
+sidebar_current: docs-imports-json
+description: The json import enables a Sentinel policy to parse and access a JSON document.
+layout: docs
+---
+
+# Import: json
+
+The json import enables a Sentinel policy to parse and access a JSON
+document.
+
+### json.unmarshal(obj)
+
+Unmarshals the JSON object `obj` into a native Sentinel structure.
+
+All native JSON types can be represented perfectly as Sentinel native types.
+
+```sentinel
+// Typically the input for this would come from an external source.
+config = json.unmarshal("{ \"foo\": 42 }")
+config.foo // 42
+config.bar // undefined (as usual for accessing a non-existent map key)
+```
+
+### json.marshal(obj)
+
+Marshals the Sentinel object `obj` into a JSON object encoded as a string.
+
+Functions and `undefined` cannot be natively encoded as JSON. If values of
+either type are found in the structure, this will return undefined.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/imports/runtime.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/imports/runtime.mdx
new file mode 100644
index 0000000000..6493bbbb22
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/imports/runtime.mdx
@@ -0,0 +1,16 @@
+---
+page_title: 'Import: runtime'
+sidebar_current: docs-imports-runtime
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: runtime
+
+The runtime import contains various information about the Sentinel runtime. It
+can be used to get information about the runtime as it's embedded in the CLI or
+a specific integration, such as validating a specific runtime version.
+
+### runtime.version
+
+Returns the version of Sentinel within this implementation.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/imports/sockaddr.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/imports/sockaddr.mdx
new file mode 100644
index 0000000000..a6842ba5f0
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/imports/sockaddr.mdx
@@ -0,0 +1,41 @@
+---
+page_title: 'Import: sockaddr'
+sidebar_current: docs-imports-sockaddr
+description: The sockaddr import makes working with IP addresses easy.
+layout: docs
+---
+
+# Import: sockaddr
+
+The sockaddr import makes working with IP addresses easy.
+
+### sockaddr.is_contained(outer, inner)
+
+The is_contained function returns true if `inner` is an address that
+is contained within `outer`.
+
+```sentinel
+sockaddr.is_contained("192.168.0.0/24", "192.168.0.32") // true
+sockaddr.is_contained("192.168.0.0/24", "192.174.1.1") // false
+```
+
+### sockaddr.is_equal(addr1, addr2)
+
+The is_equal function returns true if addr1 is equal to addr2.
+
+```sentinel
+sockaddr.is_equal("192.168.12.24", "192.168.12.24") // true
+```
+
+### sockaddr.is_ipv4(addr)
+
+The is_ipv4 function returns true if addr is an IPv4 address.
+
+```sentinel
+sockaddr.is_ipv4("192.168.12.24") // true
+sockaddr.is_ipv4("2001:0db8:85a3:0000:0000:8a2e:0370:7334") // false
+```
+
+### sockaddr.is_ipv6(addr)
+
+The is_ipv6 function returns true if addr is an IPv6 address.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/imports/strings.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/imports/strings.mdx
new file mode 100644
index 0000000000..823c5d8f07
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/imports/strings.mdx
@@ -0,0 +1,91 @@
+---
+page_title: 'Import: strings'
+sidebar_current: docs-imports-strings
+description: The strings import exposes common string operations.
+layout: docs
+---
+
+# Import: strings
+
+The strings import exposes common string operations.
+
+### strings.has_prefix(s, prefix)
+
+Returns true if `s` starts with `prefix`.
+
+```sentinel
+strings.has_prefix("billing-id", "billing-") // true
+strings.has_prefix("bill-id", "billing-") // false
+```
+
+### strings.has_suffix(s, suffix)
+
+Returns true if `s` ends with `suffix`.
+
+```sentinel
+strings.has_suffix("billing-id", "id") // true
+strings.has_suffix("billing-name", "id") // false
+```
+
+### strings.join(a, sep)
+
+Joins a list with the string supplied by `sep`.
+
+Multi-dimensional lists are supported, and non-string primitive
+types will be converted to their string equivalents. Maps and
+other complex non-list types are not supported.
+
+```sentinel
+strings.join(["foo", "bar", "baz"], ".") // "foo.bar.baz"
+strings.join([["foo", "bar"], "baz"], ".") // "foo.bar.baz"
+strings.join(["a", 1, 1.01, true], ",") // "a,1,1.01,true"
+```
+
+### strings.trim_prefix(s, prefix)
+
+Trim the prefix from the string `s`. If the string doesn't have the
+prefix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_prefix("billing-id", "billing-") // "id"
+strings.trim_prefix("bill-id", "billing-") // "bill-id"
+```
+
+### strings.trim_suffix(s, suffix)
+
+Trim the suffix from the string `s`. If the string doesn't have the
+suffix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_suffix("billing-id", "-id") // "billing"
+strings.trim_suffix("bill-id", "-foo") // "bill-id"
+```
+
+### strings.to_lower(s)
+
+Lowercase the string s.
+
+```sentinel
+strings.to_lower("FoO") // "foo"
+```
+
+### strings.to_upper(s)
+
+Uppercase the string s.
+
+```sentinel
+strings.to_upper("FoO") // "FOO"
+```
+
+### strings.split(s, sep)
+
+Split the string 's' via a substring 'sep'. If 'sep' is not contained in
+'s', the return value will be a single-element list containing only 's'.
+As a special-case, if the input is already a list, it will be returned
+as-is. This allows calling the split function when not knowing if the input
+is already a list or not.
+
+```sentinel
+strings.split("foo,bar,baz", ",") // ["foo", "bar", "baz"]
+strings.split(["foo", "bar", "baz"], ",") // ["foo", "bar", "baz"]
+```
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/imports/time.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/imports/time.mdx
new file mode 100644
index 0000000000..3a675e2bbd
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/imports/time.mdx
@@ -0,0 +1,257 @@
+---
+page_title: 'Import: time'
+sidebar_current: docs-imports-time
+description: The time import provides access to the execution time and time functions.
+layout: docs
+---
+
+# Import: time
+
+The time import provides access to the execution time and time functions.
+
+During the execution of a policy the time is fixed to the moment of
+execution. This gives the illusion that a policy instantly executes at a
+single moment of time.
+
+The import uses Coordinated Universal Time (UTC).
+
+As an example of this, if a policy accessed `time.now.second` multiple times,
+for each access the same value would be returned even if they are several seconds apart.
+This is the execution `timespace`, which is mapped to `time.now`.
+
+It is also possible to run functions on a custom time by using the
+`time.load()` function to create a new timespace from the specified value.
+See the documentation for available actions on timespaces.
+
+Times specified as the reference time or via the `time.load()` function can
+be specified in a `timeish` fashion. This is either one of:
+
+- The number of seconds since the Unix epoch (Thursday, 1 January 1970,
+ 00:00:00 UTC),
+- A timestamp matching the format outlined in RFC3339, either in the
+ format `2006-01-02T15:04:05+07:00`, which includes a timezone offset, or
+ `2006-01-02T15:04:05Z`, which indicates UTC.
+
+### time.now
+
+Create a `timespace` locked either to execution time or, if set, the
+reference time. In a given execution, this will always produce the same
+value.
+
+```sentinel
+time.now // {"day": 17, "hour": 23, "minute": 19, "month": 11 ... }
+```
+
+### time.load(timeish)
+
+Create a timespace using the given value. Please see the import
+documentation for valid values for "timeish".
+
+```sentinel
+time.load("2019-11-16T01:20:30Z") // {"day": 16, "hour": 1, "minute": 20, "month": 11 ... }
+```
+
+### time.nanosecond
+
+A nanosecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.nanosecond * 1) // 2019-11-16T01:20:30.000000001Z
+```
+
+### time.microsecond
+
+A microsecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.microsecond * 1) // 2019-11-16T01:20:30.000001Z
+```
+
+### time.millisecond
+
+A millisecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.millisecond * 1) // 2019-11-16T01:20:30.001Z
+```
+
+### time.second
+
+A second duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.second * 1) // 2019-11-16T01:20:31Z
+```
+
+### time.minute
+
+A minute for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.minute * 1) // 2019-11-16T01:21:30Z
+```
+
+### time.hour
+
+An hour duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+## Type: timespace
+
+### timespace.hour
+
+The hour from 0 to 23.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").hour // 1
+```
+
+### timespace.minute
+
+The minute from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").minute // 20
+```
+
+### timespace.second
+
+The second from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").second // 30
+```
+
+### timespace.day
+
+The day of the month, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").day // 16
+```
+
+### timespace.month
+
+The month of the year as an integer, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month // 11
+```
+
+### timespace.month_name
+
+The name of the month of the year, in English. Example: `January`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month_name // November
+```
+
+### timespace.weekday
+
+The weekday as an integer, where 0 is Sunday and 6 is Saturday.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday // 6
+```
+
+### timespace.weekday_name
+
+The name of the day of the week, in English. Example: `Sunday`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday_name // Saturday
+```
+
+### timespace.year
+
+The year as an integer.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").year // 2019
+```
+
+### timespace.unix
+
+The number of seconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix // 1573867230
+```
+
+### timespace.unix_nano
+
+The number of nanoseconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix_nano // 1573867230000000000
+```
+
+### timespace.zone
+
+The timezone offset, in seconds. This can be a negative number to indicate
+time west of UTC.
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone // -28800
+```
+
+### timespace.zone_string
+
+The timezone offset, in the format `+HH:MM` (example: `-08:00` for PST).
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone_string // -08:00
+```
+
+### timespace.before(timeish_or_timespace)
+
+Check if the time in the timespace is before the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").before("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.after(timeish_or_timespace)
+
+Check if the time in the timespace is after the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").after("2019-11-15T01:20:30Z") // true
+```
+
+### timespace.equal(timeish_or_timespace)
+
+Check if two times are equivalent
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").equal("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.add(duration)
+
+Add a duration in nanoseconds to the time in the timespace and return a new timespace. The
+duration can be negative. The duration should be specified as an integer
+multiplied with the correct unit.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+### timespace.sub(timeish_or_timespace)
+
+Subtract a time from the time in the timespace and return a duration in nanoseconds.
+
+```sentinel
+// Gives a duration of 3 hours.
+duration = time.load(
+ "2019-11-16T01:20:30Z",
+).sub("2019-11-16T04:20:30Z")
+
+// Returns 2019-11-16T01:20:30Z
+time.load(
+ "2019-11-16T04:20:30Z",
+).add(duration)
+```
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/imports/types.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/imports/types.mdx
new file mode 100644
index 0000000000..fcfdbd2ae5
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/imports/types.mdx
@@ -0,0 +1,35 @@
+---
+page_title: 'Import: types'
+sidebar_current: docs-imports-types
+description: The types import enables a Sentinel policy to parse an object's type.
+layout: docs
+---
+
+# Import: types
+
+The types import enables a Sentinel policy to parse an object's type.
+
+### types.type_of(obj)
+
+Returns the object's type as a string
+
+```sentinel playground
+import "types"
+
+// Typically the inputs would come from an external source.
+is_boolean = rule { types.type_of(true) is "bool" }
+is_string = rule { types.type_of("Hello!") is "string" }
+is_integer = rule { types.type_of(42) is "int" }
+is_float = rule { types.type_of(42.123) is "float" }
+is_null = rule { types.type_of(null) is "null" }
+is_undefined = rule { types.type_of(undefined) is "undefined" }
+
+main = rule {
+ is_boolean and
+ is_string and
+ is_integer and
+ is_float and
+ is_null and
+ is_undefined
+}
+```
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/imports/units.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/imports/units.mdx
new file mode 100644
index 0000000000..e2ad3442f9
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/imports/units.mdx
@@ -0,0 +1,39 @@
+---
+page_title: 'Import: units'
+sidebar_current: docs-imports-units
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: units
+
+The units import provides access to quick calculations for various byte
+units.
+
+Note that this import uses base-2 terminology:
+
+- One kilobyte is 1024 bytes
+- One megabyte is 1024^2 = 1048576 bytes
+- One gigabyte is 1024^3 = 1073741824 bytes
+- One terabyte is 1024^4 = 1099511627776 bytes
+- One petabyte is 1024^5 = 1125899906842624 bytes
+
+### units.kilobyte
+
+The base-2 representation of a kilobyte (1024 bytes).
+
+### units.megabyte
+
+The base-2 representation of a megabyte (1048576 bytes).
+
+### units.gigabyte
+
+The base-2 representation of a gigabyte (1073741824 bytes).
+
+### units.terabyte
+
+The base-2 representation of a terabyte (1099511627776 bytes).
+
+### units.petabyte
+
+The base-2 representation of a petabyte (1125899906842624 bytes).
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/imports/version.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/imports/version.mdx
new file mode 100644
index 0000000000..59366c1087
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/imports/version.mdx
@@ -0,0 +1,151 @@
+---
+page_title: 'Import: version'
+sidebar_current: docs-imports-version
+description: |-
+ The version import provides functions for parsing versions and version constraints,
+ and verifying versions against a set of constraints.
+layout: docs
+---
+
+# Import: version
+
+The version import provides functions for parsing versions and version constraints,
+and verifying versions against a set of constraints.
+
+Versions are created through the `new` function, which accepts a string type in dotted-tri format (i.e. 1.0.0-alpha.1+001) that can represent a version.
+
+### version.new(v)
+
+Constructs a version from string value.
+
+```sentinel
+version.new("1.0.0-alpha.1+001")
+```
+
+### version.major
+
+An integer representation of the Major number for a given version .
+
+```sentinel
+version.new("1.0.0-alpha.1+001").major // 1
+```
+
+### version.minor
+
+An integer representation of the Minor number for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").minor // 0
+```
+
+### version.patch
+
+An integer representation of the Patch number for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").patch // 0
+```
+
+### version.prerelease
+
+A string representation of the Prerelease for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").prerelease // "alpha.1"
+```
+
+### version.metadata
+
+A string representation of the Metadata for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").metadata // "001"
+```
+
+### version.version
+
+A string representation of the version including pre-release and metadata information.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").version // "1.0.0-alpha.1+001"
+```
+
+### version.greater_than(v)
+
+Tests that the version is greater than a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").greater_than("0.12.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").gt("1.0.1-alpha.1+001") // False
+```
+
+### version.greater_than_or_equals(v)
+
+Tests that the version is greater than or equal to a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").greater_than_or_equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").gte("1.0.1-alpha.1+001") // False
+```
+
+### version.less_than(v)
+
+Tests that the version is less than a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").less_than("1.0.1-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").lt("0.12.0-alpha.1+001") // False
+```
+
+### version.less_than_or_equals(v)
+
+Tests that the version is less than or equal to a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").less_than_or_equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").lte("0.12.0-alpha.1+001") // False
+```
+
+### version.less_than_or_equals(v)
+
+Tests that the version equals a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").eq("0.12.0-alpha.1+001") // False
+```
+
+### version.compare(v)
+
+Compares one version with a given version. Compare returns -1, 0, or 1 if the version is less, equal, or greater than the other version, respectively.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").compare("0.12.0-alpha.1+001") // -1
+version.new("1.0.0-alpha.1+001").compare("1.0.0-alpha.1+001") // 0
+version.new("0.12.0-alpha.1+001").cmp("1.0.0-alpha.1+001") // 1
+```
+
+### version.satisfies(v, x)
+
+Tests that a version adheres to one or more version constraints. Satisfies supports the following restriction operators:
+
+- =
+- !=
+- \>
+- <
+- \>=
+- <=
+- ~>
+
+Satisfies supports the following usage scenarios:
+
+- A constraint with a pre-release can only match a pre-release version that contains the same major, minor and patch identifiers.
+- A constraint without a pre-release can only match a version without a pre-release.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").satisfies("> 1.0.0-alpha.1+001") // false
+version.new("1.0.0-alpha.1+001").satisfies(">= 1.0.0-alpha.1+001") // true
+version.new("1.0.0-alpha.1+001").satisfies(">= 1.0.0-alpha.1+001, < 1.0.0-beta+001") // true
+version.new("1.0.0").sat("= 1.0.0") // true
+version.new("0.12.7").sat("~> 0.12.0") // true
+```
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/index.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/index.mdx
new file mode 100644
index 0000000000..4497b40b83
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/index.mdx
@@ -0,0 +1,31 @@
+---
+layout: 'docs'
+page_title: 'Documentation'
+sidebar_current: 'docs-home'
+description: |-
+ Sentinel is a language and framework for policy built to be embedded in existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+[introduction](/sentinel/intro) to understand what Sentinel is, how it
+compares to other software, and more.
+
+
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/language/arithmetic.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/language/arithmetic.mdx
new file mode 100644
index 0000000000..06b1256a1b
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/language/arithmetic.mdx
@@ -0,0 +1,67 @@
+---
+page_title: Sentinel Language - Arithmetic
+sidebar_current: docs-language-arith
+description: >-
+ Sentinel supports arithmetic operators for integers and floats. Sentinel
+ supports sum, difference, product, quotient, and remainder.
+layout: docs
+---
+
+# Language: Arithmetic
+
+Sentinel supports arithmetic operators for integers and floats. Sentinel
+supports sum, difference, product, quotient, and remainder.
+
+```plaintext
++ sum
+* difference
+* product
+/ quotient
+% remainder
+```
+
+These operators work in a typical infix style:
+
+```sentinel
+4 + 8 // 12
+8 * 2 // 16
+8 / 4 // 2
+8 / 5 // 1
+8 % 5 // 3
+```
+
+## Order of Operations
+
+Arithmetic follows a standard mathematical order of operations.
+Grouping with parentheses can be used to affect ordering.
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+(4 + 5) * 2 // 18
+```
+
+A full table of operator precendence can be found on the
+[boolean expressions page](/sentinel/language/boolexpr). This
+shows how arithmetic operators relate to other operators.
+
+## Integer Division
+
+Integer division with a remainder rounds down to the nearest integer.
+Example: `8 / 3 is 2`.
+
+If the divisor is zero, an error occurs.
+
+## Mixed Numeric Operations
+
+Mixed numeric operations between integer and floating-point values are
+permitted. The result is a floating-point operation with the integer converted
+to a floating-point value for purposes of calculation.
+
+```sentinel
+import "types"
+
+a = 1.1 + 1 // 2.1
+types.type_of(a) // "float"
+```
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/language/boolexpr.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/language/boolexpr.mdx
new file mode 100644
index 0000000000..f1c518c3bf
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/language/boolexpr.mdx
@@ -0,0 +1,225 @@
+---
+page_title: Sentinel Language - Boolean Expressions
+sidebar_current: docs-language-boolexpr
+description: >-
+ Boolean expressions are expressions that evaluate to a boolean value from the
+ result of a combination of one or more comparisons and logical operators.
+layout: docs
+---
+
+# Language: Boolean Expressions
+
+Boolean expressions are expressions that evaluate to a boolean value
+from the result of a combination of one or more comparisons and logical
+operators. Boolean expressions form the basis of policy since policy can be broken
+down to a set of logical decisions that turn into true or false.
+
+A single boolean expression is the body of a [rule](/sentinel/language/rules).
+Additionally, boolean expressions are used with [conditionals](/sentinel/language/conditionals),
+and more.
+
+## Order of Operations
+
+The precedence of operators is shown below. Operators with higher
+precedence values (larger numbers) are evaluated first. Operators with
+the same precedence value are evaluated left-to-right.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Examples of this precedence are shown below:
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+```
+
+## Logical Operators
+
+Logical operators are used to change or combine logical expressions.
+There are three binary operators `and`, `or`, and `xor`. There is
+a single unary operator `not` (which can also be specified as `!`).
+
+The binary operators require two boolean operands and the unary
+operator requires a single boolean operand. In both case, the operands
+are values or expressions that are boolean types.
+
+Below is a basic explanation of the logical operators directly from
+the specification:
+
+```sentinel
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+Using parentheses to group boolean expressions, you can combine
+boolean expressions to become more complex or affect ordering:
+
+```sentinel
+(p and q) or r
+p and (q or r)
+```
+
+## Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+For any comparison, the two operands must be equivalent types. The one
+exception are integers and floats. When an integer is compared with
+a float, the integer is promoted to a floating point value.
+
+The available comparison operators are:
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+The behavior of `is` with `==` and `is not` with `!=` is identical.
+
+Example comparisons:
+
+```sentinel
+name is "Mitchell"
+idnumber > 42
+idnumber <= 12
+```
+
+Using the logical operators, these can be combined:
+
+```sentinel
+name is "Mitchell" and idnumber > 42
+```
+
+The language specification provides more detail on exactly
+[how comparison behaves](/sentinel/language/spec#comparison-operators).
+
+## Set Operators
+
+The set operators `contains` and `in` test for inclusion in a collection
+(a list or map).
+
+Set operators may 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.
+
+`contains` tests if the left-hand collection contains the right-hand value.
+`in` tests if the right-hand collection contains the left-hand value. For
+maps, "contains" looks at the keys, not the values.
+
+Examples:
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+```
+
+## Matches Operator
+
+The `matches` operator tests if a string matches a regular expression.
+The `matches` operator can be negated by prefixing the operator with
+`not`, such as `not matches`.
+
+The left-hand value is the string to test. The right-hand value is
+a string value representing a regular expression. The syntax of the regular
+expression is the same general syntax used by Python, Ruby, and other languages.
+The precise syntax accepted is the syntax accepted by RE2.
+
+Regular expressions are not anchored by default; any anchoring must be
+explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+## Any, All Expressions
+
+`any` and `all` expressions allow testing that any or all elements of a
+collection match some boolean expression. The result of an `any` and `all`
+expression is a boolean.
+
+`any` returns the boolean value `true` if any value in the collection expression
+results in the body expression evaluating to `true`. If the body expression
+evalutes to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression
+result in the body expression evaluating to `true`. If any value in the
+collection expression result in the body expression evaluating to `false`,
+the `all` expression returns `false`.
+
+For empty collections, `any` returns `false` and `all` returns `true`.
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" }
+any group.tasks as t { t.driver is "vmware" }
+```
+
+Since `any` and `all` expressions are themselves boolean expressions, they
+can be combined with other operators:
+
+```sentinel
+any ["a", "b"] as char { char is "a" } or
+other_value is "another"
+```
+
+## Emptiness Comparison
+
+The expressions `is empty` and `is not empty` provide a convenience
+method for determining the emptiness of a value. It supports the same types
+as the [built-in length](/sentinel/functions/length), collections and strings.
+
+```sentinel
+[] is empty // true
+[] is not empty // false
+["foo"] is empty // false
+["foo"] is not empty // true
+undefined is empty // undefined
+undefined is not empty // undefined
+```
+
+## Defined Comparison
+
+The expressions `is defined` and `is not defined` provide a convenience
+method for determining if a value has been defined. In other words, any value
+other than `undefined` can be considered as `defined`.
+
+```sentinel
+[] is defined // true
+4 is defined // true
+true is defined // true
+{} is defined // true
+undefined is defined // false
+[] is not defined // false
+4 is not defined // false
+true is not defined // false
+undefined is not defined // true
+```
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/language/collection-operations.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/language/collection-operations.mdx
new file mode 100644
index 0000000000..41da8f3584
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/language/collection-operations.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Sentinel Language - Collection Operations
+sidebar_current: docs-language-collection-operations
+description: |-
+ Operations for interacting with collections (list, map).
+layout: docs
+---
+
+# Language: Collection Operations
+
+Collection operations are expressions that are performed on a list or map to
+return a variation of the initial data.
+
+At the moment, `filter` is the only collection operation available.
+
+## Filter Expression
+
+`filter` is a [quantifier
+expression](/sentinel/language/spec#quantifier-expressions-any-all-filter-map) that
+returns a subset of the provided collection. Only elements whose filter body
+returns true will be returned. If any of the elements filter body returns
+undefined, the final result will be undefined.
+
+Filter uses the same syntax as the `any` and `all` [boolean
+expressions](/sentinel/language/boolexpr#any-all-expressions):
+
+```sentinel
+filter list as value { condition } // Single-iterator, list
+filter list as idx, value { condition } // Double-iterator, list
+
+filter map as key { condition } // Single-iterator, map
+filter map as key, value { condition } // Double-iterator, map
+```
+
+Examples:
+
+```sentinel
+l = [1, 1, 2, 3, 5, 8]
+evens = filter l as v { v % 2 is 0 } // [2, 8]
+
+m = { "a": "foo", "b": "bar" }
+matched_foo = filter m as _, v { v is "foo" } // { "a": "foo" }
+```
+
+## Map Expression
+
+`map` is a [quantifier
+expression](/sentinel/language/spec#quantifier-expressions-any-all-filter-map) that
+returns a list, regardless of the input collection type. Each element within the
+input collection is evaluted according to the map expression body and appended
+to the result.
+
+```sentinel
+l = [1, 2]
+r = map l as v { v % 2 } // [false, true]
+
+m = { "a": "foo", "b": "bar" }
+r = map m as k, v { v } // ["foo", "bar"]
+```
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/language/conditionals.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/language/conditionals.mdx
new file mode 100644
index 0000000000..1736795dbb
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/language/conditionals.mdx
@@ -0,0 +1,161 @@
+---
+page_title: Sentinel Language - Conditionals
+sidebar_current: docs-language-conditional
+description: |-
+ Conditional statements allow your policy to behave differently depending on a condition.
+layout: docs
+---
+
+# Language: Conditionals
+
+Conditional statements allow your policy to behave differently depending
+on a condition.
+
+Conditional statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## If Statements
+
+`if` statements only execute their bodies if a condition is met.
+The syntax of an `if` statement is:
+
+```sentinel
+if condition {
+ // ... this is executed if condition is true
+}
+```
+
+The `condition` must result in a boolean, such as by calling a function
+or evaluating a [boolean expression](/sentinel/language/boolexpr). If
+the `condition` is `true`, the body (within the `{}`) is executed. Otherwise,
+the body is skipped.
+
+Examples:
+
+```sentinel
+// This would execute the body
+value = 12
+if value is 18 {
+ print("condition met")
+}
+
+// Direct boolean values can be used
+value = true
+if value {
+ print("condition met")
+}
+
+// This would not execute the body since the boolean expression will
+// result in undefined.
+value = {}
+if value["key"] > 12 {
+ print("condition met")
+}
+```
+
+### Else, Else If
+
+An `else` clause can be given to an `if` statement to execute a body
+in the case the condition is _not met_. By putting another `if` statement
+directly after the `else`, multiple conditions can be tested for.
+The syntax is:
+
+```sentinel
+if condition {
+ // ...
+} else {
+ // ...
+}
+
+if condition {
+ // ...
+} else if other_condition {
+ // ...
+} else {
+ // ...
+}
+```
+
+### Scoping
+
+The body of an `if` statement does not create a new
+[scope](/sentinel/language/scope). Any variables assigned within the body
+of an if statement will modify the scope that the `if` statement itself
+is in.
+
+Example:
+
+```sentinel
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+```sentinel
+a = 18
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+## Case Statements
+
+`case` statements are a selection control mechanism that execute a clause based
+on matching expressions. It is worth noting that the expression for `case` is
+optional. When no expression is provided, it defaults the expression to `true`.
+Additionally, the order of clauses is important, as they are evaluated from top
+to bottom, executing the first match.
+The syntax of a case statement is:
+
+```sentinel
+case expression {
+ when clause_expression:
+ // executed when clause_expression and expression are equal
+ else:
+ // executed if no clause matches expression
+}
+```
+
+### When Clause
+
+Any clause that has an expression for comparison must use the `when` keyword.
+It accepts a list of expressions, seperated by a `,`.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+}
+```
+
+```sentinel
+case {
+ when x > 40:
+ return true
+}
+```
+
+### Else Clause
+
+The `else` keyword allows for capturing any expressions that have no matching
+`when` clause.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+ else:
+ return false
+}
+```
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/language/functions.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/language/functions.mdx
new file mode 100644
index 0000000000..8214078b6b
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/language/functions.mdx
@@ -0,0 +1,229 @@
+---
+page_title: Sentinel Language - Functions
+sidebar_current: docs-language-functions
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Functions
+
+Functions allow you to create reusable code to perform computations.
+
+Below is a trivial example function that doubles a number and shows
+the main rule using the function:
+
+```sentinel playground
+double = func(x) {
+ return x * 2
+}
+
+main = rule { double(12) is 24 }
+```
+
+Functions may contain any expressions,
+[conditionals](/sentinel/language/conditionals),
+and [loops](/sentinel/language/loops). They must return a value
+at the end of their execution. If no reasonable return value exists,
+the function should return [undefined](/sentinel/language/undefined).
+
+## Function Types
+
+### Named Functions
+
+-> **NOTE:** Named functions must be created within the [package scope](/sentinel/language/scope#package-scope).
+
+Named functions are declared using the `func` keyword as its own statement.
+They provide a safe method of creating functions and have additional
+restrictions that do not apply to anonymous functions.
+
+Firstly, named functions cannot be re-assigned, and also cannot use a name
+that is already used elsewhere. For instance, the below example will error due
+to the attempt to reassign the named function identifier to a new value:
+
+```sentinel
+func sum(a, b) {
+ return a + b
+}
+
+sum = 4
+```
+
+Additionally, the following will error due to the named function attempting
+to make use of an already assigned identifier:
+
+```sentinel
+sum = 4
+
+func sum(a, b) {
+ return a + b
+}
+```
+
+Named functions are helpful for policy authors to declare critical functions
+whose value or implementation should not be changed.
+
+### Anonymous Functions
+
+An anonymous function is created by assigning a variable to a `func`. The
+variable can be reassigned at any time including to different value types.
+Anonymous functions are helpful for use cases like closures, where a function
+can return another function.
+
+```sentinel
+func makeAdder(a) {
+ return func(b) {
+ return a + b
+ }
+}
+```
+
+## Creating a Function
+
+A function can have zero or more parameters. These parameters are specified
+within parentheses `()` separated by commas. If a function has no parameters,
+the parentheses must still be present.
+
+A function must terminate with a `return` statement to return a value back to
+the caller. Only a single value can be returned. The type of a return can
+vary between return statements.
+
+Anonymous function example:
+
+```sentinel
+add1 = func(x) { return x + 1 }
+```
+
+Named function example:
+
+```sentinel
+func add1(x) {
+ return x + 1
+}
+```
+
+Both examples create a function that adds 1 to the parameter `x`.
+
+## Calling a Function
+
+Functions are called by accessing their name followed by parentheses with
+a list of comma-separated values for the parameters. If the function takes no
+parameters, an empty set of parentheses must still be used to denote a
+function call.
+
+To call the function in the example above:
+
+```sentinel
+x = 1
+y = add1(x)
+```
+
+## Scoping
+
+The body of a `func` creates a new [scope](/sentinel/language/scope).
+
+The parent scope is the scope in which the function is created. This allows
+for the creation of functions within functions that can access their outer
+scopes.
+
+Example:
+
+```sentinel
+f = func() {
+ a = 42
+ print(a) // 42
+ return undefined
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+f = func() {
+ a = 42
+ return undefined
+}
+
+print(a) // 18
+```
+
+And below is an example of creating a function in a function which uses
+outer values:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double()
+}
+
+print(f()) // 84
+```
+
+A more complex example below shows how scoping works when passing
+functions around as arguments or results:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double
+}
+
+double = f()
+print(double()) // 84
+```
+
+## Pass By Value
+
+The parameters to a function are passed by value, but not deep copied.
+This means that elements of collections can still be modified but the
+root value cannot be changed.
+
+Example:
+
+```sentinel
+f = func(x) {
+ x = "value"
+ return x
+}
+
+x = "outside"
+f(x)
+print(x) // "outside"
+```
+
+```sentinel
+f = func(x) {
+ append(x, "value")
+ return x
+}
+
+x = []
+f(x)
+print(x) // ["value"]
+```
+
+## Recursion
+
+A function is allowed to call other functions, including itself.
+This can be used to implement recursion. For example, the fibonacci sequence
+is implemented below:
+
+```sentinel
+fib = func(x) {
+ if x <= 0 {
+ return undefined
+ }
+
+ if x == 1 {
+ return 1
+ } else {
+ return x + fib(x - 1)
+ }
+}
+```
+
+Note that this example also shows using
+[undefined](/sentinel/language/undefined)
+as a return value in cases where a function has undefined behavior.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/language/imports.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/language/imports.mdx
new file mode 100644
index 0000000000..0ed1541b85
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/language/imports.mdx
@@ -0,0 +1,116 @@
+---
+page_title: Sentinel Language - Imports
+sidebar_current: docs-language-imports
+description: >-
+ Imports enable a Sentinel policy to access reusable libraries and external
+ data and functions.
+layout: docs
+---
+
+# Language: Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external
+data and functions. The exact list of available imports is dependent on the
+host system. Host systems may also make the available imports configurable
+via plugins.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that
+the import `calendar` allows access to engineer calendars. This import
+only exists for the purpose of this example and at the time of writing is
+not a real available import.
+
+```sentinel
+import "calendar"
+
+// Get the calendar for Bob for today
+bob_calendar = calendar.for("bob").today
+
+// Allow this policy to pass if Bob is not on vacation.
+main = rule { not bob_calendar.has_event("vacation") }
+```
+
+This example shows an import being used to deny a behavior if Bob is on
+vacation. Hopefully real policies do not depend on a single person being
+in the office, but the example shows the power of imports.
+
+In addition to consuming imports, you can also
+[extend Sentinel by writing custom imports](/sentinel/extending)
+This allows you to add any arbitrary behavior to your Sentinel-protected
+systems.
+
+## Importing
+
+Whether accessing a built-in library or an external plugin, the syntax
+for an import is the same:
+
+```sentinel
+import "name"
+```
+
+This adds the import with the name `name`. It can be referenced using
+the identifier `name`. For example, if the import above exposed first
+and last name data, you could access it via `name.first` or `name.last`.
+
+You can shadow imports by assigning a variable. In the example below,
+the import `name` becomes unavailable within the function because it is
+shadowed by a variable of the same name.
+
+Shadowing an import is not the same as overwriting a variable. Imports
+are not part of the [scope](/sentinel/language/scope), meaning that
+the shadow only exists for the scope it is created within. For everything
+else, the import works even after it is shadowed. The example below shows that
+as well.
+
+```sentinel
+import "name"
+
+f = func() {
+ name = "jane"
+ return name
+}
+
+print(f()) // "jane"
+print(name.first) // "bob"
+```
+
+## Aliases
+
+An import can be aliased to another name using the syntax below:
+
+```sentinel
+import "name" as other
+```
+
+This would make the import "name" available as `other`.
+
+An import may only be imported once, regardless of aliases. The following
+would be **invalid** and would result in an error:
+
+```sentinel
+import "name" as one
+import "name" as two
+```
+
+## Accessing Import Data
+
+Imports are accessed with dot-separated identifiers, such as `name.first` or
+`name.stage.first`. These are called _selector expressions_. An import may
+return nested data that further can be accessed via selector expressions.
+
+In the first example on this page with the "calendar" import, we see this:
+
+```sentinel
+import "calendar"
+
+a = calendar.for("bob") // selector expression calendar.for
+b = a.today // selector expression on the result
+c = b.vacation // accessing further nested data
+```
+
+The exact structure of an import is dependent on the import. Please reference
+the documentation for an import to learn more.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/language/index.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/language/index.mdx
new file mode 100644
index 0000000000..7f588c94cd
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/language/index.mdx
@@ -0,0 +1,114 @@
+---
+page_title: Sentinel Language
+sidebar_current: docs-language-basics
+description: |-
+ Sentinel policies are written using the Sentinel language. This language
+ is easy to learn and easy to write. You can learn the Sentinel language
+ and be productive within an hour. Learning Sentinel doesn't require any
+ formal programming experience.
+layout: docs
+---
+
+# Sentinel Language
+
+Sentinel policies are written using the Sentinel language. This language
+is easy to learn and easy to write. You can learn the Sentinel language
+and be productive within an hour. Learning Sentinel doesn't require any
+formal programming experience.
+
+This language guide will contain many details that are not necessary to
+be productive with Sentinel or are targeted to those who are looking for
+exact information about the language (such as what types of line endings are
+allowed). You can safely ignore these sentences.
+
+You may also view the
+[official language specification](/sentinel/language/spec)
+This is a specific and detailed document on the syntax and behavior of
+the language primarily intended for implementation creators and to disambiguate
+the language.
+
+## Simplest Example
+
+The example below is about the simplest practical example of Sentinel.
+It is reasonable to imagine this as a realistic policy. This shows that
+in most cases, Sentinel will be extremely simple:
+
+```sentinel
+main = rule { request.method is "GET" and request.headers contains "X-Key" }
+```
+
+## Files
+
+Sentinel policies are single files that end in the `.sentinel` file extension.
+There is currently no built-in mechanism to Sentinel for merging multiple
+files. This is purposefully done to make Sentinel policies easy to submit
+to systems that support Sentinel policies.
+
+Sentinel policy files must be UTF-8 encoded and can end in both
+Unix (LF) or Windows (CRLF) line breaks. When running the
+[auto-formatter](#),
+line endings will always use Unix line breaks.
+
+## Ordering
+
+Sentinel policies are executed top-down. For example:
+
+```sentinel
+a = 1 // a = 1 here
+b = a + 1 // b = 2 here
+a = 3 // a = 3, b = 2
+```
+
+In this example, the value of `a` and `b` is shown at each line. Since Sentinel
+executes values top-down, the final value of `a` is 3 and `b` is 2. `b` _does
+not_ become 4.
+
+## Main
+
+Sentinel expects there to be a `main` [rule](/sentinel/language/rules).
+The value of this rule is the result of the entire policy.
+
+The result of the policy depends on the evaluated contents of the `main` rule.
+For booleans, a policy passes on a `true` value, and fails on a `false` value.
+Other types generally follow a zero or zero-length pattern for determining
+success.
+
+| Type | Passing Condition | Failing Condition |
+| ------- | ----------------- | -------------------------- |
+| Boolean | `true` | `false` |
+| String | `""` | Any non-zero length string |
+| Integer | `0` | Any non-zero value |
+| Float | `0.0` | Any non-zero value |
+| List | `[]` | Any non-zero length list |
+| Map | `{}` | Any non-zero length map |
+
+A value of main that falls outside of the above types will result in a policy
+error. As a special case, if `main` evaluates to an undefined value, the error
+message will indicate as such, with a reference to where the undefined value was
+encountered.
+
+## More Complex Example
+
+The simple example above is a full working example. In our experience with
+Sentinel, many policies can be representing using this simple form. However,
+to show more features of the language, a more complex example is shown below.
+This example is also a realistic example of what Sentinel may be used for.
+
+```sentinel
+import "units"
+
+memory = func(job) {
+ result = 0
+ for job.groups as g {
+ for g.tasks as t {
+ result += t.resources.memory else 0
+ }
+ }
+
+ return result
+}
+
+main = rule {
+ memory(job) < 1 * units.gigabyte
+}
+```
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/language/lists.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/language/lists.mdx
new file mode 100644
index 0000000000..bc5269d8eb
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/language/lists.mdx
@@ -0,0 +1,135 @@
+---
+page_title: Sentinel Language - Lists
+sidebar_current: docs-language-lists
+description: Lists are a collection of zero or more values.
+layout: docs
+---
+
+# Language: Lists
+
+Lists are a collection of zero or more values.
+
+Lists can be created using by wrapping values in `[]` and separating them
+by commas. An optional trailing comma is allowed. List elements can be
+differing types. Examples:
+
+```sentinel
+[] // An empty list
+["foo"] // Single element list
+["foo", 1, 2, true] // Multi element list with different types
+["foo", [1, 2]] // List containing another list
+```
+
+A list can be [sliced](/sentinel/language/slices) to create sublists.
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for value inclusion in a list.
+
+## Accessing Elements
+
+List elements can be accessed with the syntax `name[index]` where
+`index` is zero-indexed.
+
+A negative index accesses the list in reverse. It is the same as
+reversing a list and then using a positive index. Similar to a positive
+index, it is bounded by the length of the list as a negative value.
+
+Accessing beyond the length of the list results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+a = ["foo", 1, true, [1, 2]]
+
+a[0] // "foo"
+a[2] // true
+a[4] // undefined
+a[-2] // true
+a[-4] // "foo"
+a[-5] // undefined
+a[3][1] // 2
+```
+
+## List Append
+
+Values can be appended to a list using the built-in
+[append](/sentinel/functions/append) function.
+
+This modifies the list in-place and returns
+[undefined](/sentinel/language/undefined). For more information on
+why `append` behaves this way, please read the full documentation for the
+[append](/sentinel/functions/append) function.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append([1,2], "foo") // [1, 2, "foo"]
+append([1,2], [3]) // [1, 2, [3]]
+append(1, 3) // error()
+```
+
+## List Concatenation
+
+Two lists can be concatenated using the `+` operator or the shorthand
+`+=` assignment operator. For the `+` operator, a new list is returned.
+For `+=`, the left-hand list is modified in place.
+
+Examples:
+
+```sentinel
+[1] + [2] // [1, 2]
+[1] + [[1]] // [1, [1]]
+[1] + 1 // error
+
+a = [1]
+a += [2] // a = [1, 2]
+a += 3 // error
+```
+
+## List Length
+
+The length of a list can be retrieved using the
+[length](/sentinel/functions/length) function.
+
+Examples:
+
+```sentinel
+length([]) // 0
+length(["foo"]) // 1
+```
+
+## Removing Items From a List
+
+You can use a combination of [list concatenation](#list-concatenation) and
+[slicing](/sentinel/language/slices) to remove elements from a list.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+a = a[:2] + a[3:] // [1, 2, 4, 5]
+```
+
+The shorthand shown here is effectively the same as `a[0:2] + a[3:length(a)]`,
+which creates a new list out of the concatenation two sub-lists composed of the
+first two elements, and the rest of the list starting at index 3. This
+effectively removes the 3rd element from the list (index 2).
+
+## List Comparison
+
+Lists may be [compared](/sentinel/language/boolexpr#comparison-operators)
+for equality. Lists are equal if they are of equal length and their
+corresponding elements are comparable and equal. Lists with the same elements
+but in a different order are not equal.
+
+```sentinel
+[1, 2] is [1, 2] // true
+[1, 2] is [2, 1] // false
+["a"] is ["a", "b"] // false
+["a", ["b", "c"]] is ["a", ["b", "c"]] // true
+```
+
+List comparison speed is O(N), meaning that the speed of the comparison is
+_linearly_ proportional to the number of elements in the list. The more
+elements, the more iterations that are necessary to verify equality.
+
+-> The N-value quoted above should account for the sum of the elements of _all_
+lists in the subjects of comparison, as list comparison will recurse into these
+lists to check for equality.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/language/logging-errors.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/language/logging-errors.mdx
new file mode 100644
index 0000000000..705a72cbd4
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/language/logging-errors.mdx
@@ -0,0 +1,52 @@
+---
+page_title: Sentinel Language - Logging and Errors
+sidebar_current: docs-language-log
+description: The built-in functions `print` and `error` can be used for logging and errors.
+layout: docs
+---
+
+# Language: Logging and Errors
+
+The built-in functions `print` and `error` can be used for logging
+and errors. Logging is helpful to understand how a policy is executing
+in development. And errors are useful to halting execution immediately in
+certain scenarios.
+
+## Print
+
+The `print` function is used to output debug information. The exact location
+where this print statements go is dependent on the host application and
+the Sentinel runtime itself.
+
+The `print` function takes zero or more Sentinel values as arguments.
+Each value is formatted for human-friendly output and space-separated.
+Example:
+
+```sentinel
+value = 42
+print("the value is", value) // the value is 42
+
+map = { "foo": false }
+print(map) // { "foo": false }
+
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // false
+```
+
+## Errors
+
+Certain actions in Sentinel, such as accessing an undefined variable,
+attempting to add two incompatible types, etc. result in _runtime errors_.
+These errors halt the execution of a policy immediately. The pass/fail result
+of a policy is considered fail.
+
+Runtime errors can be manually triggered using the `error` function.
+The `error` function behaves exactly like the `print` function except that
+execution is halted immediately.
+
+This should only be used in scenarios where the policy _must fail_ due to
+some unexpected or invalid condition. The line between `error` and
+[undefined](/sentinel/language/undefined) can sometimes be unclear. Undefined
+is recommended when a policy can conceivably recover or safely ignore the
+undefined behavior. An error should be used when the policy should fail and
+notify someone regardless.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/language/loops.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/language/loops.mdx
new file mode 100644
index 0000000000..622732bd35
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/language/loops.mdx
@@ -0,0 +1,92 @@
+---
+page_title: Sentinel Language - Loops
+sidebar_current: docs-language-loops
+description: >-
+ Loop statements allow you to execute a body of code for each element in a
+ collection or for some fixed number of times.
+layout: docs
+---
+
+# Language: Loops
+
+Loop statements allow you to execute a body of code for each element in
+a collection or for some fixed number of times.
+
+Loop statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## For Statements
+
+`for` statements allow repeated execution of a block for each
+element in a collection.
+
+Example:
+
+```sentinel
+// A basic sum
+count = 0
+for [1, 2, 3] as num {
+ count += num
+}
+```
+
+The syntax is `for COLLECTION as value`. This will iterate over the
+collection, assigning each element to `value`. In the example above,
+each element is assigned to `num`. The body is executed for each element.
+In the example above, the body adds `num` to the `count` variable. This
+creates a basic sum of all values in the collection.
+
+For a map, the assigned element is the key in the map. In the example
+below, `name` would be assigned map keys.
+
+```sentinel
+list = []
+for { "a": 1, "b": 2 } as name {
+ append(list, name)
+}
+
+print(list) // ["a" "b"]
+```
+
+An alternate syntax is `for COLLECTION as key, value`. This will assign
+both the key and value to a variable. For a list, the key is the element
+index. For a map, it is the key and value is assigned the element value.
+Example:
+
+```sentinel
+count = 0
+for { "a": 1, "b": 2 } as name, num {
+ count += num
+}
+
+print(count) // 3
+```
+
+## Scoping
+
+The body of a `for` statement creates a new
+[scope](/sentinel/language/scope). If a variable is assigned within
+the body of a for statement that isn't assigned in a parent scope,
+that variable will only exist for the duration of the body execution.
+
+Example:
+
+```sentinel
+for list as value {
+ a = 42
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+for list as value {
+ a = 42
+}
+
+print(a) // 18
+```
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/language/maps.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/language/maps.mdx
new file mode 100644
index 0000000000..298bf9a567
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/language/maps.mdx
@@ -0,0 +1,121 @@
+---
+page_title: Sentinel Language - Maps
+sidebar_current: docs-language-maps
+description: >-
+ Maps are a collection of zero or more key/value pairs. These are useful for
+ storing values that are looked up by a unique key.
+layout: docs
+---
+
+# Language: Maps
+
+Maps are a collection of zero or more key/value pairs. These are useful
+for storing values that are looked up by a unique key.
+
+Maps can be created using `{}` and specifying key/value pairs. Keys and
+values can be differing types. An optional trailing comma is allowed.
+Examples:
+
+```sentinel
+// Empty map
+{}
+
+// Map with a single value on one line
+{ "key": "value" }
+
+// Map with multiple values with differing types on multiple lines
+{
+ "key": "value",
+ 42: true,
+}
+```
+
+Maps are **unordered**. When
+[looping](/sentinel/language/loops)
+over a map, the key/value pairs can be returned in any order.
+
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for key inclusion in a map.
+
+## Accessing Elements
+
+Map elements can be accessed with the syntax `name[key]`. This looks up
+a value by a key.
+
+Accessing a key that doesn't exist results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+map = { "key": "value", 42: true, }
+
+map["key"] // "value"
+map[42] // true
+map[0] // undefined
+```
+
+## Modifying or Adding Elements
+
+Elements can be added or modified in a map by assigning to `name[key]`.
+If the key doesn't exist, the value is added. If the key already exists,
+the value is overridden.
+
+```sentinel
+map = { "key": "value" }
+
+map[42] = true // Add a new key/value
+map["key"] = 12 // Modify the value of "key"
+```
+
+## Deleting Elements
+
+An element can be deleted from a map using the
+[delete](/sentinel/functions/delete) function.
+
+Examples:
+
+```sentinel
+map = { "key": "value" }
+delete(map, "key") // map is now empty
+delete(map, "other") // no effect for non-existent key
+```
+
+## Keys and Values
+
+The keys and values of a map can be retrieved as
+[lists](/sentinel/language/lists) using the
+[keys](/sentinel/functions/keys) and
+[values](/sentinel/functions/values) functions.
+
+Because maps are unordered, the keys and values are returned in an
+unspecified order. It should not be assumed that keys and values will be
+returned in a consistent order.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+## Map Comparison
+
+Maps may be [compared](/sentinel/language/boolexpr#comparison-operators) for
+equality. Maps are equal if they are of equal length and both their
+corresponding keys and values are comparable and equal.
+
+```sentinel
+{"foo": "bar"} is {"foo": "bar"} // true
+{"foo": "bar"} is {"baz": "bar"} // false
+{"foo": "bar"} is {"foo": "baz"} // false
+{"foo": "bar"} is {"foo": "bar", "baz": "qux"} // false
+{1: "a"} is {1.0: "a"} // true (int/float comparable)
+
+// also true (maps are not ordered):
+{"m": {"a": "b"}, "l": ["a"]} is {"l": ["a"], "m": {"a": " b"}}
+```
+
+-> The current worst-case comparison speed for maps is O(N²), or _quadratic
+time_. This is the square of the cumulative elements or the parent and all child
+maps contained within the map. Consider this when making comparisons on larger,
+more complex maps. This may be optimized in the future.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/language/parameters.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/language/parameters.mdx
new file mode 100644
index 0000000000..348acf2593
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/language/parameters.mdx
@@ -0,0 +1,133 @@
+---
+page_title: Sentinel Language - Parameters
+sidebar_current: docs-language-parameters
+description: >-
+ Parameteres allow a Sentinel policy to describe variables that are expected
+ to be supplied by the calling application, in addition to any default value.
+layout: docs
+---
+
+# Language: Parameters
+
+Sentinel allows a policy author to supply parameters to help facilitate policy
+reuse and ensure sensitive values do not need to be hard-coded in a policy.
+
+Parameters are supplied by using the `param` keyword, followed by an identifier.
+A default value can also be supplied by using the `default` keyword.
+
+```sentinel
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+```
+
+Once declared, parameters can be used like any other variable, including being
+re-assigned.
+
+```sentinel
+param foo default 1 // 1 (default)
+
+foo = foo + 1 // 2
+```
+
+## Variable Descriptions
+
+You can supply a description to a parameter by adding a comment at the top of
+it. This value can be communicated to a specific implementation of Sentinel to
+provide information about what the parameter is for during configuration.
+
+```sentinel
+// An example parameter. Must be supplied or the policy will fail.
+param foo
+```
+
+## Supplying Parameter Values Using the Sentinel CLI
+
+In a production implementation, supplying parameters to a policy is an
+implementation-specific detail - see the documentation for your particular
+implementation for details.
+
+Using the [Sentinel CLI](/sentinel/commands), you can supply parameters one of
+four ways.
+
+### Supplying Parameter Values Using the Configuration File
+
+You can supply parameters using the
+[`param`](/sentinel/configuration#parameters) section of the
+[configuration file](/sentinel/configuration).
+
+```hcl
+param "foo" {
+ value = "bar"
+}
+```
+
+This method works for both `sentinel apply` and `sentinel test`.
+
+### Supplying Parameter Values Using the Environment
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can supply a value using environment variables - prefix the parameter with
+`SENTINEL_PARAM_`, using the name of the parameter to supply.
+
+```plaintext
+SENTINEL_PARAM_foo=bar sentinel apply policy.sentinel
+```
+
+### Supplying Parameter Values Using the CLI
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can also use the `-param` CLI argument to supply parameter in a `key=value`
+pair.
+
+```plaintext
+sentinel apply -param foo=bar policy.sentinel
+```
+
+### Interactive CLI Prompting
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+If a required value has not been supplied when a policy is run with `sentinel apply`, it will be prompted for, along with its description:
+
+
+
+ $ sentinel apply policy.sentinel
+
+ policy.sentinel:2:7: requires value for parameter foo
+
+ An example parameter. Must be supplied or the policy will fail.
+
+
+ Values can be strings, floats, or JSON array or object values. To
+ force
+
+ strings, use quotes.
+
+
+ Enter a value: bar
+
+
+
+ Pass
+
+
+
+
+### CLI Value Format
+
+-> **NOTE:** This section contains details for the parameter features supported by `sentinel apply`.
+
+The CLI takes either strings, or JSON numbers, arrays, or maps. If you need a
+literal string value, quote the value.
+
+```sentinel
+foo // string
+42 // number (float)
+"42" // string ("42", without quotes)
+[1, 2] // array (list)
+{"a": "b"} // object (map)
+```
+
+-> **NOTE:** Boolean values are not supported by this method.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/language/rules.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/language/rules.mdx
new file mode 100644
index 0000000000..e31924d414
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/language/rules.mdx
@@ -0,0 +1,204 @@
+---
+page_title: Sentinel Language - Rules
+sidebar_current: docs-language-rules
+description: >-
+ Rules form the basis of a policy by representing behavior that is either
+ passing or failing (true or false).
+layout: docs
+---
+
+# Language: Rules
+
+Rules form the basis of a policy by representing behavior that is either passing
+or failing. A policy can be broken down into a set of rules. Breaking down a
+policy into a set of rules can make it more understandable and aids with
+testing.
+
+An example is shown below:
+
+```sentinel playground
+weather = "sunny"
+day = "wednesday"
+is_sunny = rule { weather is "sunny" }
+is_wednesday = rule { day is "wednesday" }
+
+main = rule {
+ is_sunny and
+ is_wednesday
+}
+```
+
+A rule contains a single expression. This can be a simple [boolean
+expression](/sentinel/language/boolexpr), or an expression representing the
+discovery of a set of violations using more in-depth expressions like [`filter`
+and `map`](/sentinel/language/collection-operations). You can split expressions
+into multiple lines for readability, as you would with any other expression
+within Sentinel.
+
+A rule is also _lazy_ and _memoized_. _Lazy_ means that the rule is only
+evaluated when it is actually required, not at the point that it is
+created. This is covered in more detail in the [lazy](#lazy-and-memoized) section.
+_Memoized_ means that the value is only computed once and then saved. Once
+a rule is evaluated, the result of it is reused anytime the rule is referenced
+again.
+
+## Making rules easier to understand
+
+Rules are an abstraction that make policies much more understandable
+by making it easier for humans to see what the policy is trying to do.
+
+For example, consider the policy before which doesn't abstract into rules:
+
+```sentinel
+main = rule {
+ ((day is "saturday" or day is "sunday") and homework is "") or
+ (day in ["monday", "tuesday", "wednesday", "thursday", "friday"] and
+ not school_today and homework is "")
+}
+```
+
+Assume that `day`, `homework`, and `school_day` are available.
+
+In plain English, this policy is trying to say: you can play with your friends
+only on the weekend as long as there is no homework, or during the week if
+there is no school and no homework.
+
+Despite the relatively simple nature of this policy (a few lines, about 5
+logical expressions), it is difficult to read and understand, especially if
+you've not seen it before.
+
+The same policy using rules as an abstraction:
+
+```sentinel
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+is_valid_weekend = rule { is_weekend and homework is "" }
+is_valid_weekday = rule { not is_weekend and not school_today and homework is "" }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+By reading the names of the rules, its much clearer to see what each individual
+part of the policy is trying to achieve. The readability is improved even
+further when adding comments to the rules to explain them further, which is
+a recommended practice:
+
+```sentinel
+// A weekend is Sat or Sun
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+// A valid weekend is a weekend without homework
+is_valid_weekend = rule { is_weekend and homework is "" }
+
+// A valid weekday is a weekday without school
+is_valid_weekday = rule { not is_weekend and not school_today and homework is "" }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+## "When" Predicates
+
+A rule may have an optional `when` predicate attached to it. When this is
+present, the rule is only evaluated when the predicate results in `true`.
+Otherwise, the rule is not evaluated and the result of the rule is always
+`true`.
+
+"When" predicates should be used to define the context in which the rule
+is semantically meaningful. It is common when translating human language
+policy into Sentinel to have scenarios such as "when the key has a prefix
+of `/account/`, the remainder must be numeric." In that example, when the
+key _does not_ have the specified prefix, the policy doesn't apply.
+
+Assume you have rules `is_prefix` and `is_numeric` to check both the
+conditions in the example above. An example is shown with and without
+the "when" predicate:
+
+```sentinel
+example_no_when = rule { (is_prefix and is_numeric) or not is_prefix }
+
+example_when = rule when is_prefix { is_numeric }
+```
+
+The rules are equivalent in behavior for all values of `is_prefix` and
+`is_numeric`, but the second rule is more succint and easier to understand.
+
+## Testing
+
+To verify a policy works correct, the
+[built-in Sentinel test framework](/sentinel/writing/testing)
+uses rules as the point where you can assert behavior. You say that
+you expect certain rules to be true or false. By doing so, you ensure that
+the policy results in the value you expect using the rule flow that you
+expect.
+
+Therefore, in addition to readability, we recommend splitting policies into
+rules to aid testability.
+
+## Non-Boolean Values
+
+Sentinel supports non-boolean values in rules. This can be useful for passing
+more detail along to a calling integration when more detail is required than a
+boolean value would be able to communicate. This data shows up in the [policy
+trace](/sentinel/writing/tracing) and can be utilized in different ways
+depending on the integration you are using Sentinel with.
+
+Consider a different take on our policy above:
+
+```sentinel
+// A policy to check a set of scheduled days to determine what days you can't
+// come out and play, based on the day not being a weekend day and there being
+// homework to do.
+
+param days
+
+main = rule {
+ filter days as d {
+ d.day not in ["saturday", "sunday"] and
+ d.homework is not ""
+ }
+}
+```
+
+When given a value in the set of `days` that has a `day` that is a weekday, and
+`homework` present, the policy will fail. Additional, when tracing was enabled,
+the days that were detected as violating the policy would be given in the trace
+for the `main` rule.
+
+Generally, for non-boolean values, a policy fails on non-zero data. See the
+details on [the `main` rule](/sentinel/language#main) for more details.
+
+The result of a rule must be either a boolean, string, integer, float, list, or
+map. All other types will result in a runtime error.
+
+## Lazy and Memoized
+
+A rule is _lazy_ and _memoized_.
+
+_Lazy_ means that the rule is only evaluated when it is actually required,
+not at the point that it is created. And _memoized_ means that the value is
+only computed once and then saved. Once a rule is evaluated, the result of it
+is reused anytime the rule is referenced again.
+
+Both of these properties have important implications for Sentinel
+policies:
+
+**Performance:** Rules are a way to improve the performance of a Sentinel
+policy. If you have a boolean expression that is reused a lot, a rule will
+only be evaluated once. In the example shown above, `is_weekend` is used
+multiple times, but will only have to be evaluated once.
+
+**Behavior:** Rules accessing variables see the value of those variables
+at _the time they are evaluated_. This can lead to surprising behavior
+in some cases. For example:
+
+```sentinel playground
+a = 1
+b = rule { a == 1 }
+a = 2
+main = b
+```
+
+In this example, `main` will actually result to `false`. It is evaluated
+when it is needed, which is when the policy executes `main`. At this point,
+`a` is now 2 and `b` has not been evaluated yet. Therefore, `b` becomes `false`.
+All future references to `b` will return `false` since a rule is memoized.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/language/scope.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/language/scope.mdx
new file mode 100644
index 0000000000..f498e26dc2
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/language/scope.mdx
@@ -0,0 +1,47 @@
+---
+page_title: Sentinel Language - Scope
+sidebar_current: docs-language-scope
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Scope
+
+Scope is the context in which variables are created and accessed. If a
+variable exists in a parent scope, it is accessed and can be modified.
+Otherwise, the variable is created in your current scope.
+
+Scopes are created with _blocks_. A block is a possibly empty sequence
+of statements within matching brace brackets `{}`. You may nest blocks
+to create nested scopes.
+
+## Package Scope
+
+The package scope is the top level scope within a policy file and encapsulates
+the entire file contents. Imports, parameters and named functions must be
+declared within the package scope.
+
+## Implicit Scopes
+
+Each `any`, `all`, and `for` statement is considered to be in its own block.
+Note that `if` statements _do not_ create their own block.
+
+## Examples
+
+The example policy below shows various effects of scopes:
+
+```sentinel
+a = 1
+print(a) // 1
+
+f = func() {
+ print(a) // 1
+ a = 12
+ b = 1
+ return undefined
+}
+f()
+
+print(a) // 12
+print(b) // undefined
+```
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/language/slices.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/language/slices.mdx
new file mode 100644
index 0000000000..82dc7f4c18
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/language/slices.mdx
@@ -0,0 +1,45 @@
+---
+page_title: Sentinel Language - Slices
+sidebar_current: docs-language-slices
+description: >-
+ Slices are an efficient way to create a substring or sublist from an existing
+ string or list, respectively.
+layout: docs
+---
+
+# Language: Slices
+
+Slices are an efficient way to create a substring or sublist from an
+existing string or list, respectively.
+
+The syntax for creating a slice is:
+
+```sentinel
+a[low : high]
+```
+
+`low` is the lowest index to slice from. The resulting slice includes
+the value at `low`. `high` is the index to slice to. The resulting slice
+will not include the value at `high`. The length of the resulting list
+or string is always `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+
+a = "hello"
+b = a[1:4] // "ell"
+```
+
+## Convenience Shorthands
+
+Some convenience shorthands are available by omitting the value for either the
+low or high index in the slice expression: omitting `low` implies a low index of
+0, and omitting `high` implies a high index of the length of the list.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+
+b = a[:2] // [1, 2] (same as a[0:2])
+b = a[2:] // [3, 4, 5] (same as a[2:length(a)])
+```
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/language/spec.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/language/spec.mdx
new file mode 100644
index 0000000000..864f86af26
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/language/spec.mdx
@@ -0,0 +1,1333 @@
+---
+page_title: Sentinel Language Specification
+sidebar_current: docs-language-spec
+description: This is the specification for the Sentinel policy language.
+layout: docs
+---
+
+# Sentinel Language Specification
+
+This is the specification for the Sentinel policy language.
+
+The Sentinel language is designed with policy enforcement in mind. It is dynamically typed and garbage collected and has explicit support for _rule_ construction representing boolean logic.
+
+The language is designed to be easy to learn and use by non-programmers. It is expected to be embedded within applications.
+
+## Table of Contents
+
+
+
+
+- [Source code representation](#source-code-representation)
+- [Declarations and Scope](#declarations-and-scope)
+- [Blocks](#blocks)
+- [Lexical Elements](#lexical-elements)
+ - [Comments](#comments)
+ - [Identifiers](#identifiers)
+ - [Keywords](#keywords)
+ - [Operators and Delimiters](#operators-and-delimiters)
+ - [Integer Literals](#integer-literals)
+ - [Floating-point Literals](#floating-point-literals)
+ - [String Literals](#string-literals)
+ - [Implicit Line Joining](#implicit-line-joining)
+ - [Whitespace](#whitespace)
+ - [Semicolons](#semicolons)
+- [Variables](#variables)
+- [Undefined](#undefined)
+- [Expressions](#expressions)
+ - [Operand](#operand)
+ - [Primary Expressions](#primary-expressions)
+ - [Null](#null)
+ - [Booleans](#booleans)
+ - [Boolean Literals](#boolean-literals)
+ - [Boolean Expressions](#boolean-expressions)
+ - [List Literals](#list-literals)
+ - [Map Literals](#map-literals)
+ - [Function Literals](#function-literals)
+ - [Rule Expressions](#rule-expressions)
+ - [Index Expressions](#index-expressions)
+ - [Selectors](#selectors)
+ - [Slice Expressions](#slice-expressions)
+ - [Calls](#calls)
+ - [Operators](#operators)
+ - [Operator Precedence](#operator-precedence)
+ - [Arithmetic Operators](#arithmetic-operators)
+ - [Integer operators](#integer-operators)
+ - [Integer overflow](#integer-overflow)
+ - [Floating-point operators](#floating-point-operators)
+ - [String Concatenation](#string-concatenation)
+ - [List Concatenation](#list-concatenation)
+ - [Comparison Operators](#comparison-operators)
+ - [Logical Operators](#logical-operators)
+ - [Set Operators](#set-operators)
+ - [Matches Operator](#matches-operator)
+ - [Else Operator](#else-operator)
+ - [Quantifier Expressions (any, all, filter, map)](#quantifier-expressions-any-all-filter-map)
+- [Statements](#statements)
+ - [Expression Statements](#expression-statements)
+ - [Assignments](#assignments)
+ - [If Statements](#if-statements)
+ - [Case Statements](#case-statements)
+ - [For Statements](#for-statements)
+ - [Break Statements](#break-statements)
+ - [Continue Statements](#continue-statements)
+ - [Return Statements](#return-statements)
+- [Imports](#imports)
+- [Parameters](#parameters)
+- [Built-in Functions](#built-in-functions)
+ - [Length](#length)
+ - [Collections](#collections)
+ - [List Append](#list-append)
+ - [Map Delete](#map-delete)
+ - [Keys and Values](#keys-and-values)
+ - [Range](#range)
+ - [Type Conversion](#type-conversion)
+ - [Printing](#printing)
+ - [Errors](#errors)
+- [Program Execution](#program-execution)
+
+
+
+## Source code representation
+
+Source code is Unicode text encoded in UTF-8. A "character" referenced in this document refers to a Unicode code point. Each code point is distinct: upper and lower case letters are different characters.
+
+The underscore character \_ (U+005F) is considered a "letter".
+
+```ebnf
+letter = unicode_letter | "_" .
+decimal_digit = "0" … "9" .
+octal_digit = "0" … "7" .
+hex_digit = "0" … "9" | "A" … "F" | "a" … "f" .
+```
+
+## Declarations and Scope
+
+A declaration binds an identifier to a value. An identifier is declared at the point it is first assigned. An assignment is considered a first assignment if the identifier hasn't already been previously declared in the current scope or any parent scopes.
+
+The scope of an identifier is the extent of source text in which the identifier denotes the specified value. Sentinel is lexically scoped using blocks.
+
+An identifier declared in a block may be redeclared in an inner block. While the identifier of the inner declaration is in scope, it denotes the value assigned in the inner declaration.
+
+## Blocks
+
+A block is a possibly empty sequence of statements within matching brace brackets.
+
+```ebnf
+Block = "{" StatementList "}" .
+StatementList = { Statement ";" } .
+```
+
+Blocks nest and affect scoping.
+
+In addition to explicit blocks in the source code, there are implicit blocks:
+
+1. The universe block encompasses all source text.
+2. Each program has a program block containing all source text for that program.
+3. Each "any", "all", and "for" statements is considered to be in its own implicit block. "if" does not create an implicit block.
+
+## Lexical Elements
+
+### Comments
+
+Comments are sections of source text used for documentation.
+
+```plaintext
+# Single line comment
+// Single line comment
+/* multi
+line
+ comment */
+```
+
+Three forms of comments are supported: two single-line forms and one multi-line form. A single-line comment begins with the token `//` or `#`. Everything between the starting token and the end of the line is ignored.
+
+A multi-line comment begins with the token `/*` and ends with the token `*/`. Everything between `/*` and `*/` is ignored.
+
+A comment may not start inside a string literal or inside another comment.
+
+A multi-line comment containing no newlines acts like a space.
+
+### Identifiers
+
+Identifiers name program entities such as rules, variables, and functions. An identifier is a sequence of one or more letters and digits. The first character in an identifier must be a letter.
+
+```ebnf
+identifier = letter { letter | unicode_digit } .
+```
+
+```sentinel
+a
+_a
+_A
+αβ
+```
+
+#### Pre-Declared Identifiers
+
+The following identifiers are implicitly declared in the [universe block](#blocks):
+
+```plaintext
+Constants:
+ false null true undefined
+
+Functions:
+ append bool delete error float int keys length print range string values
+```
+
+### Keywords
+
+The following keywords are reserved and may not be used as identifiers:
+
+```plaintext
+all
+any
+as
+break
+case
+continue
+default
+else
+empty
+filter
+for
+func
+if
+import
+map
+param
+return
+rule
+when
+```
+
+### Operators and Delimiters
+
+The following character sequences represent operators, delimiters, and other special tokens:
+
+```plaintext
++
+-
+*
+/
+%
++=
+-=
+*=
+/=
+%=
+==
+<
+>
+=
+!
+!=
+<=
+>=
+( )
+[ ]
+{ }
+,
+.
+:
+;
+and
+contains
+else
+in
+is
+matches
+not
+or
+xor
+```
+
+As a special case, `else` is both a keyword and operator, depending on the context. See [Else Operator](#else-operator) for more details.
+
+### Integer Literals
+
+An integer literal is a sequence of digits representing an integer constant. An optional prefix sets a non-decimal base: `0` for octal, `0x` or `0X` for hexadecimal. In hexadecimal literals, letters `a-f` and `A-F` represents values 10 through 15.
+
+Integers are signed 64-bit values (-9223372036854775808 to 9223372036854775807).
+
+```ebnf
+int_lit = decimal_lit | octal_lit | hex_lit .
+decimal_lit = ( "1" … "9" ) { decimal_digit } .
+octal_lit = "0" { octal_digit } .
+hex_lit = "0" ( "x" | "X" ) hex_digit { hex_digit } .
+```
+
+```
+42
+0600
+0xBadFace
+170141183460469231731687303715884105727
+```
+
+### Floating-point Literals
+
+A floating-point literal is a decimal representation of a floating-point constant. It has an integer part, a decimal point, a fractional part, and an exponent part. The integer and fractional part comprise decimal digits; the exponent part is an e or E followed by an optionally signed decimal exponent. One of the integer part or the fractional part may be elided; one of the decimal point or the exponent may be elided.
+
+Floating-point numbers are IEEE-754 64-bit floating numbers.
+
+```ebnf
+float_lit = decimals "." [ decimals ] [ exponent ] |
+ decimals exponent |
+ "." decimals [ exponent ] .
+decimals = decimal_digit { decimal_digit } .
+exponent = ( "e" | "E" ) [ "+" | "-" ] decimals .
+```
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+### String Literals
+
+String literals are character sequences between double quotes, as in "bar". Within the quotes, any character may appear except newline and unescaped double quote. The text between the quotes forms the value of the literal.
+
+A multi-character sequence beginning with a backslash encode values in various formats.
+
+Backslash escapes allow arbitrary values to be encoded as ASCII text. There are four ways to represent the integer value as a numeric constant: `\x` followed by exactly two hexadecimal digits; `\u` followed by exactly four hexadecimal digits; `\U` followed by exactly eight hexadecimal digits, and a plain backslash `\` followed by exactly three octal digits. In each case the value of the literal is the value represented by the digits in the corresponding base.
+
+After a backslash, certain single-character escapes represent special values:
+
+```plaintext
+\a U+0007 alert or bell
+\b U+0008 backspace
+\f U+000C form feed
+\n U+000A line feed or newline
+\r U+000D carriage return
+\t U+0009 horizontal tab
+\v U+000b vertical tab
+\\ U+005c backslash
+\" U+0022 double quote
+```
+
+The three-digit octal (\nnn) and two-digit hexadecimal (\xnn) escapes represent individual bytes of the resulting string; all other escapes represent the (possibly multi-byte) UTF-8 encoding of individual characters. Thus inside a string literal \377 and \xFF represent a single byte of value 0xFF=255, while ÿ, \u00FF, \U000000FF and \xc3\xbf represent the two bytes 0xc3 0xbf of the UTF-8 encoding of character U+00FF.
+
+A string value is a (possibly empty) sequence of bytes. Strings are immutable: once created, it is impossible to change the contents of a string.
+
+The length of a string s (its size in bytes) can be discovered using the built-in function `length`. An individual character (of type string) can be accessed by integer indices 0 through `length(s)-1`.
+
+```ebnf
+string_lit = `"` { unicode_value | byte_value } `"` .
+unicode_value = unicode_char | little_u_value | big_u_value | escaped_char .
+byte_value = octal_byte_value | hex_byte_value .
+octal_byte_value = `\` octal_digit octal_digit octal_digit .
+hex_byte_value = `\` "x" hex_digit hex_digit .
+little_u_value = `\` "u" hex_digit hex_digit hex_digit hex_digit .
+big_u_value = `\` "U" hex_digit hex_digit hex_digit hex_digit
+ hex_digit hex_digit hex_digit hex_digit .
+escaped_char = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | `"` ) .
+```
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+### Implicit Line Joining
+
+Expressions can be split over more than one line. For example:
+
+```sentinel
+a or
+b or # This is a comment
+c
+```
+
+Implicitly continued lines can have trailing comments. Blank continued lines are allowed.
+
+### Whitespace
+
+Whitespace is needed to separate tokens, but no distinction is made between the number and combination of whitespace characters. Whitespace characters can be space (U+0020), horizontal tabs (U+0009), carriage returns (U+000D), and newlines (U+000A).
+
+```sentinel
+a or b
+
+a or b
+
+a or
+ b
+
+rule { a or b }
+
+rule {
+ a or b }
+
+rule {
+ a or
+ b
+}
+```
+
+### Semicolons
+
+The formal grammar uses semicolons ";" as terminators in a number of productions.
+It is idiomatic Sentinel source to omit semicolons in most cases.
+
+A semicolon is automatically inserted into the token stream immediately after
+a line's final token if that token is:
+
+- An identifier
+- An integer, float, or string literal
+- The keyword `break`, `continue`, or `return`
+- The delimiter `)`, `]`, or `}`
+
+## Variables
+
+A variable is a storage location for holding a value.
+
+A variable has a dynamic type, which is the concrete type of the value assigned to the variable at run time. The dynamic type may vary during execution and change as a result of further assignments.
+
+A variable's value is retrieved by referring to the variable in an expression; it is the most recent value assigned to the variable. If a variable has not yet been declared (initially assigned), it is an error.
+
+```sentinel
+x = 7 // x is type int
+x = "foo" // x is now type string
+
+x = y // error if y is not previously assigned
+```
+
+## Undefined
+
+The value denoted by the keyword `undefined` represents undefined behavior or values. It can be created directly using the keyword `undefined`. It is also returned as a result of expressions in specified cases.
+
+`undefined` is a valid operand for any operations. Only `undefined or true` will result in true. All other operations result in `undefined`. An exception is if `undefined` is not reached as a result of short-circuit operations.
+
+```sentinel
+undefined OR true = true
+undefined OR false = undefined
+undefined OR undefined = undefined
+undefined AND true = undefined
+undefined AND false = undefined
+undefined AND undefined = undefined
+undefined XOR true = undefined
+undefined XOR false = undefined
+undefined XOR undefined = undefined
+
+// Short-circuit examples
+false OR true OR undefined = true
+false OR undefined OR true = true
+true AND false AND undefined = false
+true AND undefined AND false = undefined
+
+// Non-logical operators
+undefined + 5 = undefined
+-undefined = undefined
+!undefined = undefined
+```
+
+If the result of the main rule is `undefined`, it is treated as `false` but is indicative of erroneous logic.
+
+## Expressions
+
+### Operand
+
+Operands denote the elementary values in an expression. An operand may be a literal, an identifier denoting a variable, rule, or function, or a parenthesized expression.
+
+```ebnf
+Operand = Literal | OperandName | MethodExpr | "(" Expression ")" .
+Literal = BasicLit | MapLit | ListLit | FunctionLit | RuleLit .
+BasicLit = int_lit | float_lit | string_lit .
+OperandName = identifier .
+```
+
+### Primary Expressions
+
+Primary expressions are the operands for unary and binary expressions.
+
+```ebnf
+PrimaryExpr =
+ Operand |
+ PrimaryExpr Selector |
+ PrimaryExpr Index |
+ PrimaryExpr Slice |
+ PrimaryExpr Arguments .
+
+Selector = "." identifier .
+Index = "[" Expression "]" .
+Slice = "[" [ Expression ] ":" [ Expression ] "]" .
+Arguments = "(" [ Expression { "," Expression } ] ")" .
+```
+
+```sentinel
+x
+2
+(s + ".txt")
+f(3.1415, true)
+m["foo"]
+s[i : j + 1]
+obj.color
+f.p[i].x()
+x is empty
+x is not empty
+```
+
+### Null
+
+The reserved word `null` denote the singleton value `null`. Null represents the explicit absence of a value. Behavior of `null` within expressions is specified explicitly for each expression.
+
+### Booleans
+
+### Boolean Literals
+
+The reserved words `true` and `false` denote objects that represent the boolean values `true` and `false`, respectively. These are boolean literals.
+
+```ebnf
+BoolLit = "true" | "false" .
+```
+
+#### Boolean Expressions
+
+Boolean expressions are expressions that must result in a boolean value. Any other value type becomes the `undefined` value.
+
+```ebnf
+BoolExpr = Expression .
+```
+
+### List Literals
+
+A list literal denotes a list, which is an integer indexed collection of values.
+
+```ebnf
+ListLit = "[" [ ElementList [ "," ] ] "]" .
+ElementList = Element { "," Element } .
+Element = Expression | LiteralValue .
+```
+
+A list may contain zero or more values. The number of values in a list is its length.
+
+A list has a set of indices. An empty list has an empty set of indices. A non-empty list has the index set `{0...n - 1}` where `n` is the length of the list. Attempting to access a list using an index that is not a member of its set of indices results in the `undefined` value.
+
+### Map Literals
+
+A map literal denotes a map, which is an unordered group of elements indexed by a set of unique keys.
+
+```ebnf
+MapLit = "{" [ KeyedElementList [ "," ] ] "}" .
+KeyedElementList = KeyedElement { "," KeyedElement } .
+KeyedElement = Element ":" Element .
+```
+
+Keys can only be a boolean, numeric, or string type.
+
+The value of a non-existent key is the `undefined` value.
+
+### Function Literals
+
+A function literal represents a function.
+
+```ebnf
+FunctionLit = "func" Function .
+Function = Parameters FunctionBody .
+FunctionBody = Block .
+Parameters = "(" [ IdentifierList [ "," ] ] ")" .
+IdentifierList = identifier { "," identifier } .
+```
+
+```sentinel
+func(a, b) { return b }
+```
+
+Function literals are only allowed in the file scope. They may refer to variables defined in surrounding blocks.
+
+A function must terminate with a return statement. If a function has no meaningful return value, it should return `undefined`.
+
+### Rule Expressions
+
+A rule is an expression that is evaluated lazily and the result is memoized.
+
+If the optional "when" predicate is present, the rule is evaluated only when
+the "when" boolean expression results in true. If the predicate is false,
+the rule is not evaluated and returns true. The predicate is evaluated when
+the rule would be evaluated; it is also lazy and memoized in the same way.
+
+```ebnf
+RuleExpr = "rule" [ "when" BoolExpr ] "{" Expr "}" .
+```
+
+```sentinel
+rule { x is y }
+
+rule {
+ x == "value" or
+ y == "other"
+}
+
+rule when x is y { y > 42 }
+
+rule { map ["a", "b", "c"] as _, id {
+ { "id": id }
+}}
+```
+
+### Index Expressions
+
+A primary expression of the form `a[x]` denotes the element of a list or map indexed by x. The value x is called the index or key.
+
+For `a` of type map:
+
+- If `a` contains a key `x`, `a[x]` is the map value with key `x`.
+- If `a` does not contain key `x`, `a[x]` is the `undefined` value.
+
+For `a` of type list:
+
+- `x` must be an integer
+- `x` must be in the range `[-1 * length(a), length(a)-1]`
+- If `x` is contained in the set of indices of `a`, `a[x]` is the list element at index `x`. If `x` is negative, `a[x]` is equivalent to `a[length(a)+x]`.
+- If `x` is not contained in the set of indices of `a`, `a[x]` is the `undefined` value.
+
+For `a` of value `null`:
+
+- `a[x]` is the `undefined` value.
+
+Otherwise `a[x]` is an error.
+
+### Selectors
+
+For a primary expression x, the selector expression `x.f` denotes the field `f` of the value `x`. The identifier `f` is called the selector. The type of the selector expression is the type of the selector.
+
+As a special case, selectors can be [reserved words](#keywords) and keyword [operators](#operators-and-delimiters), but cannot be any other non-identifier element.
+
+Selectors are used to access data from [imports](#imports). The first primary expression `x` in `x.f` denotes the import name. The field `f` is the selector to access data from the import.
+
+Selectors may also be used to access map data with static keys. They are syntactic sugar over index expressions. `math.pi` is equivalent to `math["pi"]` and exhibit the same limitations and behavior as an index expression. Selectors cannot, however, be used to assign values to map data.
+
+Selectors on undefined result in undefined.
+
+```sentinel
+math.pi
+time.pst.hour
+```
+
+### Slice Expressions
+
+Slice expressions construct a substring or list from a string or list.
+
+The primary expression
+
+```sentinel
+a[low : high]
+```
+
+constructs a substring or list. The indices `low` and `high` select which elements of operand `a` appear in the result. The result has indices starting at 0 and length equal to `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+```
+
+For convenience, any of the indices may be omitted. A missing `low` index defaults to zero; a missing `high` index defaults to the length of the sliced operand:
+
+```sentinel
+a[2:] // same as a[2 : length(a)]
+a[:3] // same as a[0 : 3]
+a[:] // same as a[0 : length(a)]
+```
+
+The indices are in range if `0 <= low <= high <= length(a)`, otherwise they are out of range. If the indices are out of range at run time, the result is the `undefined` value.
+
+If `a` is the value `null`, the result is the `undefined` value.
+
+If `a` is any other value type, it is an error.
+
+### Calls
+
+```ebnf
+CallExpr = identifier Arguments .
+```
+
+Given an expression `f` where `f` is a function value:
+
+```sentinel
+f(a1, a2, … an)
+```
+
+calls `f` with arguments `a1, a2, ... an`. The type of the expression is the result of `f`. The arguments are evaluated left to right. Arguments are passed by value.
+
+### Operators
+
+```ebnf
+Expression = UnaryExpr | Expression binary_op Expression .
+UnaryExpr = PrimaryExpr | unary_op UnaryExpr .
+
+binary_op = logical_op | set_op | rel_op | add_op | mul_op | else_op .
+logical_op = "and" | "or" | "xor" .
+set_op = ["not"] ( "contains" | "in" ).
+rel_op = "==" | "!=" | "<" | "<=" | ">" | ">=" |
+ "is" | "is not" | "matches" | "not matches" .
+add_op = "+" | "-" .
+mul_op = "*" | "/" | "%" .
+else_op = "else" .
+unary_op = "+" | "-" | "!" | "not" | empty_op | defined_op .
+empty_op = "is empty" | "is not empty" .
+defined_op = "is defined" | "is not defined" .
+```
+
+#### Operator Precedence
+
+Unary operators have the highest precedence.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Binary operators of the same precedence associate from left to right. For instance, x / y _ z is the same as (x / y) _ z.
+
+### Arithmetic Operators
+
+Arithmetic operators apply to numeric values and yield a result of the same type when both operands are the same.
+
+Arithmetic operations between integer and floating-point types result in a floating-point value with the integer operand treated as a floating-point value for purposes of calculation.
+
+Arithmetic operations between numeric values (integer, floating-point) and non-numeric values (strings, lists) are not permitted.
+
+All five supported arithmetic operators (+, -, \*, /, %) apply to both integer and floating-point types; + also applies to strings.
+
+```plaintext
++ sum integers, floats, strings, lists
+* difference integers, floats
+* product integers, floats
+/ quotient integers, floats
+% remainder integers, floats
+```
+
+#### Integer operators
+
+For two integer values x and y, the integer quotient `q = x / y` and remainder `r = x % y` satisfy the following relationships:
+
+```sentinel
+x = q*y + r and |r| < |y|
+```
+
+with x / y truncated towards zero.
+
+```plaintext
+ x y x / y x % y
+ 5 3 1 2
+-5 3 -1 -2
+ 5 -3 -1 2
+-5 -3 1 -2
+```
+
+As an exception to this rule, if the dividend x is the most negative value for the int type of x (-9223372036854775808), the quotient `q = x / -1` is equal to x (and r = 0).
+
+If the divisor is a constant, it must not be zero. If the divisor is zero at run time, an error occurs.
+
+#### Integer overflow
+
+For signed integers, the operations `+`, `-`, and `*` may legally overflow and the resulting value exists and is deterministically defined by the signed integer representation, the operation, and its operands. No exception is raised as a result of overflow.
+
+#### Floating-point operators
+
+For floating-point numbers, +x is the same as x, while -x is the negation of x. The result of a floating-point division by zero is not specified beyond the IEEE-754 standard.
+
+#### String Concatenation
+
+Strings can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = "hi"
+y = "hello"
+x = x + ", " + y // "hi, hello"
+x += " and good bye" // "hi, hello and good bye"
+```
+
+String addition creates a new string by concatenating the operands.
+
+#### List Concatenation
+
+Lists can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = [1, 2]
+x = x + [2, 3] // [1, 2, 2, 3]
+x += [4] // [1, 2, 2, 3, 4]
+```
+
+List addition creates a new list by concatenating the operands.
+
+### Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+In any comparison, the two operands must be equivalent types. The only exception are integers and floats, which are considered numeric and may be compared. Any comparison between other non-matching types results in the `undefined` value.
+
+The equality operators `==`, `!=`, `is`, and `is not` apply to operands that are comparable. The ordering operators `<`, `<=`, `>`, and `>=` apply to operands that are ordered. The behavior of `is` with `==` and `is not` with `!=` is identical. These terms and the result of the comparisons are defined as follows:
+
+- Boolean values are comparable. Two boolean values are equal if they are either both true or both false.
+- Integer values are comparable and ordered, in the usual way.
+- Floating point values are comparable and ordered, as defined by the IEEE-754 standard.
+- An integer compared with floating point value treats the integer as the converted floating point value.
+- String values are comparable and ordered, lexically byte-wise.
+- Lists are comparable. Lists are equal if they are of equal length and their
+ corresponding elements are comparable and equal.
+- Maps are comparable. Maps are equal if they are of equal length and both their
+ corresponding keys and values are comparable and equal.
+
+If either operand is the `undefined` value, the result of the expression is the `undefined` value.
+
+### Emptiness Comparisons
+
+Checking the emptiness of an object can be achieved by using one of the emptiness expressions, `is empty` or `is not empty`. Although similar to [Comparison Operators](#comparison-operators) in that they yield a boolean value and read as though a comparison is taking place, both `is empty` and `is not empty` are evaluated as a multi-word expression.
+
+An emptiness comparison can only be performed against collections, strings or `undefined`, with the same rules as the [built-in length](/sentinel/functions/length) function.
+
+```sentinel
+"" is empty // true
+"foo" is empty // false
+[] is empty // true
+[1] is empty // false
+{} is empty // true
+{"a": "b"} is empty // false
+
+"" is not empty // false
+"foo" is not empty // true
+[] is not empty // false
+[1] is not empty // true
+{} is not empty // false
+{"a": "b"} is not empty // true
+
+undefined is empty // undefined
+undefined is not empty // undefined
+```
+
+### Logical Operators
+
+Logical operators apply to boolean values and yield a boolean result.
+
+Logical operators are evaluated left-to-right and perform short-circuit logic. The right-hand side is not guaranteed to be evaluated if a short-circuit can be performed.
+
+```plaintext
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+### Set Operators
+
+The set operators `contains` and `in` test for set inclusion for lists
+and maps, and substring inclusion for strings.
+
+Set operators may be negated by prefixing the operator with `not`: `not contains` and `not in`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+`contains` tests if the left-hand collection contains the right-hand value. For lists, this tests equality of any value. For maps, this tests equality of any key. For strings, this tests for substring existence.
+
+`in` tests if the right-hand collection contains the left-hand value. The behavior is equivalent to `contains`.
+
+The collection must be a list, map, or string. If it is the `undefined` value, the result is the `undefined` value. For any other value, the result is an error.
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+
+"test" contains "est" // true
+"test" contains "best" // false
+"test" in "testing" // true
+"best" in "testing" // false
+```
+
+### Matches Operator
+
+The matches operator tests if a string matches a regular expression.
+
+The matches operators may be negated by prefixing the operator with `not`: `not matches`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+The left-hand value is the string to test. The right-hand value is a string value representing a regular expression.
+
+The syntax of the regular expression is the same general syntax used by Python, Ruby, and other languages. More precisely, it is the syntax accepted by RE2. The regular expression is not anchored by default; any anchoring must be explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+If either operand is `undefined`, the result is the `undefined` value. For any other non-string value, the result is an error.
+
+### Else Operator
+
+Else operators can be used to provide a default value for an expression that may evaluate to the `undefined` value. Else operators return their left-hand value unless it is `undefined`, otherwise they return their right-hand value.
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else null
+```
+
+### Quantifier Expressions (any, all, filter, map)
+
+Quantifiers are expressions that apply a predicate to a collection (list or map). The purpose of the quantifier is to produce a result based on its particular type.
+
+`any` and `all` expressions are existential and universal quantifiers, respectively. `any` expressions are equivalent to a chain of `or` and `all` expressions are equivalent to a chain of `and`. Both expressions implement short-circuiting equivalent to logical operators.
+
+The `map` expression is an apply-to-all quantifier and returns a new list for all values in the input collection. The output list will be the same length as the input collection.
+
+The `filter` expression is a subset quantifier and returns the subset of all values in the input collection for which the supplied predicate applies. This will be zero or more elements, up to the length of the input.
+
+The body of a quantifier expression is a boolean expression.
+
+`any` returns the boolean value `true` if any value in the collection expression results in the body expression evaluating to `true`. If the body expression evaluates to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression result in the body expression evaluating to `true`. If any value in the collection expression result in the body expression evaluating to `false`, the all expression returns `false`.
+
+For empty collections, `any` returns false and `all` returns `true`.
+
+`map` always returns a list, regardless of if the input collection is a list itself; maps (as in the data type) also return lists when processed through `map`. Each element is the result of the supplied expression body.
+
+`filter` returns a list or map, the same type as its input collection. Only the elements for which the expression body evaluated to `true` will be returned. If an iteration of the expression body results in `undefined`, the entire result set is `undefined`.
+
+When the collection is a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When the collection is a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+QuantExpr = QuantOp Expression "as" [ identifier "," ] identifier "{" BoolExpr "}" .
+QuantOp = "all" | "any" | "filter" | "map" .
+```
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" } // true if every iterations is true
+any group.tasks as t { t.driver is "vmware" } // true if any iteration is true
+map group.tasks as t { { "driver": t.driver } } // [{"driver": "vmware"}, ...]
+filter group.tasks as t { t.driver is "vmware" } // subset of tasks that is true
+```
+
+## Statements
+
+### Expression Statements
+
+Function call expressions may exist in the statement context.
+
+```ebnf
+ExpressionStmt = Expression .
+```
+
+### Assignments
+
+Assignments set the value of an expression to the value of another expression.
+
+```ebnf
+Assignment = AssignExpr assign_op Expression .
+AssignExpr = identifier | IndexExpr .
+assign_op = [ add_op | mul_op ] "=" .
+```
+
+The assignment `x op= y` is equivalent to `x = x op (y)` for supported values of `op`.
+
+```sentinel
+a = 1
+b[12] = 42
+c["key"] = "value"
+
+a *= 12
+b[12] += 8
+```
+
+Assignments to lists and maps can be carried out using [index expressions](#index-expressions), with the operands of the index expression being evaluated alongside the right hand side expression in a right-to-left (RHS, LHS) order.
+
+In addition to the rules detailed in the section describing their use, the following rules apply when assigning to maps or lists using index expressions:
+
+- The [variable](#variables) must exist and be a list or map. Attempts to use an index assignment on an unknown variable, or a variable that not a list or map, results in a runtime error.
+- Assigning to a list or map index that already exists overwrites that index's data with evaluated right hand side's value.
+- Assigning to an unknown key in a map creates a key for that map with the evaluated right hand side's value assigned to that key.
+- Attempting to assign a value to a list index that is out of range results in a runtime error.
+
+### If Statements
+
+If statements specify the conditional execution of two branches according to the value of a boolean expression. If the expression evaluates to true, the "if" branch is executed, otherwise, if present, the "else" branch is executed.
+
+```ebnf
+IfStmt = "if" BoolExpr Block [ "else" ( IfStmt | Block ) ] .
+```
+
+```sentinel
+if x < y {
+ return x
+} else if x > z {
+ return z
+} else {
+ return y
+}
+```
+
+### Case Statements
+
+Case statements specify a selection control mechanism to conditionally execute a branch based on expression equality.
+
+Each clause that wishes to provide an expression must use the "when" keyword. Multiple expressions can be provided, separated with a comma (",").
+
+There can be one, optional, "else" clause within a `case` statement. The "else" clause is executed if all other clauses failed to run.
+
+The clauses are evaluated in the order they are listed, with the first successful evaluation being executed.
+
+A `case` statement can optionally provide an expression. If no expression is provided, it is the equivalent of writing `case true {`.
+
+```ebnf
+CaseStmt = "case" [ Expression ] "{" { CaseWhenClause } "}" .
+CaseWhenClause = CaseWhenCase ":" StatementList .
+CaseWhenCase = "when" Expression { "," Expression } | "else" .
+```
+
+```sentinel
+case x {
+when y, z:
+ return true
+else:
+ return false
+}
+
+case {
+when x > 42:
+ return true
+else:
+ return false
+}
+```
+
+### For Statements
+
+A `for` statement specifies repeated execution of a block.
+
+The expression must evaluate to a list or a map.
+
+When iterating over a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When iterating over a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+ForStmt = "for" Expressions "as" [ identifier "," ] identifier Block .
+```
+
+```sentinel
+for [1, 2, 3] as v { count += v } // basic sum
+
+for [1, 2, 3] as idx, v {
+ if idx > 1 { count += v }
+}
+
+data = { "a": 12, "b": 32 }
+for data as k { count += data[k] } // sum map values
+for data as k, v { count += v }
+```
+
+### Break Statements
+
+A `break` statement terminates execution of the innermost `for` statement
+within the same function.
+
+```ebnf
+BreakStmt = "break" .
+```
+
+```sentinel
+// Will only print "1"
+for [1, 2, 3] as v {
+ print(v)
+ break
+}
+```
+
+### Continue Statements
+
+A `continue` statement begins the next iteration of the innermost `for` loop.
+The `for` loop must be within the same function.
+
+```ebnf
+ContinueStmt = "continue" .
+```
+
+```sentinel
+// Will print 1, 3
+for [1, 2, 3] as v {
+ if v == 2 {
+ continue
+ }
+
+ print(v)
+ break
+}
+```
+
+### Return Statements
+
+A return statement in a function F terminates the execution of F and provides the result value.
+
+```ebnf
+ReturnStmt = "return" Expression .
+```
+
+A function must terminate with a return statement.
+
+The return statement can be invoked at any time during a function to terminate execution.
+
+## Imports
+
+An import adds an assignment to the global scope to external definitions.
+
+The import declarations must only appear at the top of the source file, before any other statements. Comments may appear before imports.
+
+```ebnf
+ImportDecl = "import" Name ["as" identifier] .
+Name = string_lit .
+```
+
+An import name and identifier must be distinct. Two names may not repeated even if they're declared with different identifiers.
+
+If an identifier is not specified, the identifier is the name of the import itself.
+
+An import is not a valid value. The identifier representing the import may not be used as an expression, such as in assignment statements or call expressions.
+
+Values in imports are not assignable directly via their selectors. Function calls may be used to alter the state of the external data represented by the import. Whether or not this is supported is specific to the import implementation. The exception to this assignment restriction is the memoized data returned by a call to an import (see below).
+
+Data returned by imports can be memoized, meaning that data is returned by the import in the form of a map which is then used as much as possible without having to return to the import for more data. For example, `t = time.now` returns a map so that `t.hour` will be able to be looked up without having to go back to the import for that data.
+
+Data returned by imports may be callable, meaning that methods may be called on the memoized data returned by an import. For example, given `t = time.now`, `t.after(some_previous_time)` is valid, with the receiver data being set to the local memoized data stored in `t`. It is a valid case to accept modifications to the receiver data (example: assignment to the memoized data via index expressions), as long as all data in the receiver continues to be string-keyed. It is an invalid case to accept receiver data with non-string-typed keys. Methods may also alter the contents of receiver data so that it is different after the call is finished.
+
+As with methods, imports may also have callable keys where the data may not be memoized. Example: in the event of `v = foo.bar`, if `v.baz` is not included in the memoized data, a call will be made to the import to fetch it. The same rules and restrictions to receiver data apply to callable keys as do to method calls.
+
+The support for callable methods and non-memoized keys on data returned by imports is specific to the import implementation.
+
+The handling of import loading is specific to the runtime implementation.
+
+Implicit imports may be added by the runtime, for example for standard library definitions. An explicit import of the same name should override a matching implicit import. For example, if "time" is an implicit import and the program specifies `import "time" as cal`, then only `cal` is defined.
+
+```sentinel
+import "time"
+import "math" as m
+```
+
+## Parameters
+
+```ebnf
+ParamDecl = "param" identifier [ "default" Expression ]
+```
+
+A parameter is a way for a policy to describe variables that are expected to be supplied by the calling application, in addition to any default value.
+
+Parameter declarations must appear at the top of the source file, following imports.
+
+Like imports, comments may appear before parameters; this is mainly pertinent when the policy is not importing anything, which would make parameters the first declarations that appear in a policy.
+
+A parameter declaration describes a valid variable that is added to the scope of a policy at runtime. This variable has the same properties and rules as other variables assigned during the course of policy evaluation as defined by the specification. This includes having a dynamic type and being able to be re-assigned during execution.
+
+Parameters may only be strings, integers, floating point numbers, booleans, lists, or maps. More complex types such as rules or functions are not allowed.
+
+A parameter can specify a default value by adding one in the declaration via use of the "default" keyword, and supplying a literal for the default value. The literal for a default is a very limited expression, comprising of:
+
+- For strings, a simple string literal;
+- For integers and floating-point numbers, either a literal denoting the value, or a unary expression with the literal, and the operators - or +;
+- For booleans, the pre-declared identifiers true or false;
+- For lists and maps, their respective literals composed of values and keys (for maps) of the above values only.
+
+Any other type of expression or identifier is not allowed.
+
+A parameter that does not have a default value defined is required by the policy. Evaluating a policy with a required parameter that has not been supplied at execution results in a runtime error.
+
+Parameters must not conflict with imports or any other identifiers currently defined in the global scope, including any reserved or pre-declared identifiers. Attempting to assign a parameter to an existing value results in a runtime error.
+
+```sentinel
+import "time"
+
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+param time default "11:00" // error, conflicts with "time" import
+param undefined // error, reserved identifier
+```
+
+## Built-in Functions
+
+Built-in functions are predeclared. They are called like any other function.
+
+### Length
+
+The built-in function `length` returns the length of a collection of string.
+
+The length of a string is the number of bytes in the string. It is not necessarilly the number of characters.
+
+The length of a collection is the number of elements in that collection.
+
+The length of `undefined` is `undefined`.
+
+### Collections
+
+#### List Append
+
+The built-in function `append` appends a value to the end of a list in-place.
+
+Appending to a non-list value results in an immediate `fail`.
+
+The return value of `append` is always the `undefined` value.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+#### Map Delete
+
+The built-in function `delete` deletes elements from a map by key. The map is modified in-place.
+
+Deleting a key that does not exist does nothing.
+
+Calling delete for a non-map value results in an immediate `fail`.
+
+The return value of `delete` is always the `undefined` value.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
+
+#### Keys and Values
+
+The built-in function `keys` and `values` return the keys and values of a map, respectively. The return value is a list. Both functions return values in an unspecified order.
+
+The `keys` or `values` of `undefined` is `undefined`.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+### Range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it defaults to `1`.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4]
+range(1, 5, 2) // [1,3]
+range(0, -3, -1) // [0,-1,-2]
+```
+
+### Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a value of that type according to the rules below.
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
+
+### Printing
+
+The built-in function `print` can be used to output formatted debug information. The runtime may omit print function calls depending on its execution mode. The runtime may choose where the output of a print function goes.
+
+`print` is a variadic function, accepting one or more values to be printed; values are separated by a single whitespace character (`" "`).
+
+The return value of `print` is always `true` to allow it to be used in boolean expressions.
+
+```sentinel
+print("hello") // "hello"
+print("hello", "world") // "hello world"
+print("The", "number", "is", 42) // "The number is 42"
+print([1, 2, 3]) // "[1, 2, 3]"
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // "false"
+```
+
+### Errors
+
+The built-in function `error` is equivalent to `print` but immediately causes execution to halt and the main rule to evaluate to `false`. The program execution is considered to have failed.
+
+## Program Execution
+
+Program execution begins by evaluating the source top to bottom. If evaluation is successful, the `main` rule is evaluated and its result is returned. Execution can be interrupted at any time by an error, which can be called explicitly or implicitly through illegal or undefined behavior.
+
+---
+
+> The content of this page is licensed under the [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
+>
+> Based on [The Go Programming Language Specification](https://golang.org/ref/spec) licensed under [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/language/undefined.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/language/undefined.mdx
new file mode 100644
index 0000000000..dc51531783
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/language/undefined.mdx
@@ -0,0 +1,97 @@
+---
+page_title: Sentinel Language - Undefined
+sidebar_current: docs-language-undefined
+description: >-
+ The value `undefined` is a special value representing undefined behavior or an
+ unknown value. Any operation on an `undefined` value results in `undefined`.
+layout: docs
+---
+
+# Language: Undefined
+
+The value `undefined` is a special value representing undefined behavior
+or an unknown value.
+
+Undefined is not an error because there are situations where the undefined
+value can be safely ignored and the language also provides construts for
+recovering from an undefined value.
+
+The undefined value can be created manually with `undefined`. In most cases,
+undefined is created by accessing a non-existent list element or value.
+The undefined value is created in a number of other circumstances. The documentation
+will note when an undefined value may be created.
+
+Almost any operation with `undefined` results in `undefined`. For example,
+performing math on undefined, attempting to call a function that is undefined,
+etc.
+
+## Main and Undefined
+
+If the value `undefined` is returned from the top-level `main` rule, then
+the policy is considered `false`. However, the runtime provides mechanisms
+for the host application to determine the policy failure was caused by
+an undefined value and report that to the user.
+
+The `main` rule returning the undefined value should be considered a logical
+error in most cases and should probably be corrected by the policy writer.
+
+## Debugging Undefined
+
+When an `undefined` value is first created (either explicitly or implicitly),
+the runtime will record the position where the undefined was created. This
+location can be used to find logic that could be erroneous.
+
+## Boolean Logic and Undefined
+
+Boolean logic is one way to safely recover from `undefined`. If boolean
+logic can safely determine a result despite an undefined value, that result
+will be returned.
+
+For example: `undefined or true` will result in `true`, because no matter
+what actual value `undefined` could've been if the policy behaved properly,
+the boolean expression would still be true.
+
+Another scenario where `undefined` can be safely ignored is short-circuit
+logic with `and`. `false and undefined` will result in `false`.
+
+The full table of logical operations on `undefined` is below:
+
+```sentinel
+undefined or true = true
+undefined or false = undefined
+undefined or undefined = undefined
+undefined and true = undefined
+undefined and false = undefined
+undefined and undefined = undefined
+undefined xor true = undefined
+undefined xor false = undefined
+undefined xor undefined = undefined
+
+// Short-circuit examples
+false or true or undefined = true
+false or undefined or true = true
+true and false and undefined = false
+true and undefined and false = undefined
+```
+
+## Else Expressions
+
+The `else` expression allows explicit recovery from undefined and is useful
+for setting default values.
+
+`else` is an operator where the left and right hand side are values. If the
+left-hand value is `undefined`, the right-hand value is returned. Otherwise,
+the left-hand value is returned.
+
+Examples:
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else "default"
+
+// In more complex scenarios
+
+foo() else 42 == 12
+a = config.value else "default"
+```
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/language/values.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/language/values.mdx
new file mode 100644
index 0000000000..f932480340
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/language/values.mdx
@@ -0,0 +1,210 @@
+---
+page_title: Sentinel Language - Values
+sidebar_current: docs-language-values
+description: Values are the data that Sentinel policies operate on.
+layout: docs
+---
+
+# Language: Values
+
+Values are the _data_ that Sentinel policies operate on. You can create this
+data yourself, for example by just typing the number `42`, or you may access
+this data from an external source to make policy decisions.
+
+Values have a _type_, which determines what kind of operations can be performed
+on the data. Example types are booleans (true and false), numbers,
+and strings (text).
+
+This page documents all the available value types. You can also
+[convert between types](#type-conversion).
+
+## Boolean
+
+A boolean is a value that is either true or false.
+
+A boolean value is created literally with `true` or `false`.
+
+As a policy language, booleans are central to the behavior of Sentinel.
+Booleans are used as conditions in [if statements](/sentinel/language/conditionals),
+are the result of [rules](/sentinel/language/rules), and more. The ultimate
+result of a Sentinel policy is true or false.
+
+## Integer
+
+An integer is a whole number.
+
+An integer is a 64-bit value. This means it can represent numbers from
+-9,223,372,036,854,775,808 to 9,223,372,036,854,775,808.
+
+Integers are created by typing them out literally with no
+separators (such as a comma). An optional prefix can set a non-decimal base:
+`0` for octal, and `0x` for hexadecimal. In hexadecimal literals, letters
+`a-f` and `A-F` represent values 10 to 15.
+
+Example integers are shown below:
+
+```sentinel
+42
+0600
+0xBadFace
+170141183460469
+```
+
+Integers are used for math and numerical comparison.
+
+## Float
+
+A float is a number with a decimal point. It has an integer part, a decimal
+point, a fractional part, and optionally an exponent part. Example
+floats are shown below:
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+Floats are IEEE-754 64-bit floating point numbers.
+
+## String
+
+Strings are text values.
+
+Strings are created by wrapping text in double quotes, such as `"hello"`.
+Within the quotes, any character may appear except newline and an unescaped
+double quote. The text between the quotes is the value.
+
+Because Sentinel policies must be UTF-8 encoded text, strings themselves are
+UTF-8 encoded text. This means you can put any value UTF-8 character into
+a string, such as `"日本語"`.
+
+You can have a string with a literal double-quote by _escaping_ it with
+a backslash. For example: `"they said \"hello\""` turns into the value
+`they said "hello"`.
+
+Backslash escapes can be used for much more than only escaping a double quote.
+Newlines are `\n`, a literal backslash is `\\`, and many more. The full list
+of available backslash escapes can be found
+[in the language specification](/sentinel/language/spec#string-literals).
+
+Example strings:
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+Strings do not support indexing, however it is possible to achieve a similar
+result through the combination of the [strings](/sentinel/imports/strings)
+standard import and list indexing.
+
+```sentinel playground
+import "strings"
+
+asciistr = "Hello, world!"
+utf8str = "日本語"
+
+utf8split = rule {
+ strings.split(utf8str, "")[2] == "語"
+}
+
+asciisplit = rule {
+ strings.split(asciistr, "")[5] == ","
+}
+
+main = rule { utf8split and asciisplit }
+```
+
+Strings support [slicing](/sentinel/language/slices) to efficiently create
+substrings.
+
+## List
+
+See [Lists](/sentinel/language/lists).
+
+## Map
+
+See [Maps](/sentinel/language/maps).
+
+## Rule
+
+See [Rules](/sentinel/language/rules).
+
+## Function
+
+See [Functions](/sentinel/language/functions).
+
+## Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a
+value of that type. Some examples are shown below followed by a list of exact
+rules for type conversion.
+
+```sentinel
+int(42) // 42
+int("42") // 42
+int(42.8) // 42
+int(true) // 1
+
+float(1.2) // 1.2
+float(1) // 1.0
+float("4.2") // 4.2
+float(true) // 1.0
+
+string("foo") // "foo"
+string(88) // "88"
+string(0xF) // "15"
+string(true) // "true"
+
+bool("true") // true
+bool(1) // true
+bool(-1) // true
+bool(0.1) // true
+bool("false") // false
+bool(0) // false
+```
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/language/variables.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/language/variables.mdx
new file mode 100644
index 0000000000..7516890212
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/language/variables.mdx
@@ -0,0 +1,99 @@
+---
+page_title: Sentinel Language - Variables
+sidebar_current: docs-language-vars
+description: Variables store values that can be used later.
+layout: docs
+---
+
+# Language: Variables
+
+Variables store values that can be used later.
+
+Most notably, a variable assignment of `main` is required for all
+Sentinel policies. This is the main [rule](/sentinel/language/rules) that
+is executed to determine the result of a policy. The `main` rule may
+use other variables that are assigned in the policy.
+
+Example:
+
+```sentinel
+a = 1
+b = a + 1
+```
+
+In this example, two variables are assigned: `a` and `b`. `a` is given
+the value of "1" and `b` uses the `a` to calculate its own value.
+
+## Syntax
+
+The syntax for assigning a variable is:
+
+```sentinel
+IDENTIFIER = VALUE
+```
+
+On the left of the equal sign is an _identifier_. This is a name for your
+variable and will be how you reference it later. An identifier is any
+combination of letters and digits, but must start with a letter. An
+underscore `_` is also a valid letter.
+
+On the right is any valid Sentinel value or expression. A value is a
+literal value such as a number or string. An expression is some computed
+value such as doing math, calling a function, etc.
+
+## Assignment and Reassignment
+
+A variable is _assigned_ when it is given a value. You can also _reassign_
+variables at any time by setting it to a new value. The new value takes
+effect for any subsequent use of that variable. A variable can be reassigned
+to a different type.
+
+For example:
+
+```sentinel
+a = 1 // a = 1
+b = a // b = 1
+a = "value" // a = "value", b = 1
+c = a // c = "value", b = 1
+```
+
+In the above example you can see that the variables are set and reassigned.
+Notice that the value of a variable is the _current_ value, and that reassigning
+a variable only affects _future_ uses of that variable. You can see this with
+`c` and `b` being different values.
+
+## Unassigned Variables
+
+Using a variable that is _unassigned_ is an error.
+
+In the example below, the first line would result in the policy erroring.
+Sentinel is executed top-down and the value of `c` is not available yet
+on the first line.
+
+```sentinel
+a = c // Error!
+c = 1
+```
+
+## Type Conversion
+
+While a topic more related to [values][lang-values], variables can be assigned
+(or-reassigned) a different value type via type conversion.
+
+[lang-values]: /sentinel/language/values
+
+```sentinel
+s = "1.1"
+a = int(s) // a = 1
+a = float(s) // a = 1.1
+
+a = 1
+s = string(a) // s = "1"
+```
+
+For more details, see the type conversion sections in the
+[values][lang-values-type-conversion] page, or the [Sentinel Language
+Specification][lang-spec-type-conversion].
+
+[lang-values-type-conversion]: /sentinel/language/values#type-conversion
+[lang-spec-type-conversion]: /sentinel/language/spec#type-conversion
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/nomad.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/nomad.mdx
new file mode 100644
index 0000000000..a105b0337d
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/nomad.mdx
@@ -0,0 +1,51 @@
+---
+page_title: Nomad and Sentinel
+sidebar_current: docs-app-nomad
+description: >-
+ Nomad Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies can currently execute on job
+ submission (creation, update).
+layout: docs
+---
+
+# Nomad
+
+Nomad is a simple, flexible scheduler and workload orchestrator.
+[Nomad Enterprise](https://www.hashicorp.com/products/nomad/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies can currently execute on job submission (creation, update).
+
+Sentinel policies have full access to the job structure. This allows the
+Sentinel policy to control behavior based on any attribute within a job,
+such as the driver, resource requests, network configuration, volume
+configuration, and more. The information that Sentinel policies have access
+to will expand over time.
+
+Nomad fully supports all [enforcement levels](/sentinel/concepts/enforcement-levels).
+For soft mandatory policies, the `sentinel-override` capability must be
+available on the user's ACL policy to allow override. Overrides are always
+logged.
+
+The Nomad integration with Sentinel is documented in depth in the
+[Nomad Enterprise documentation](https://www.nomadproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Only allow Docker-based jobs.**
+
+```sentinel
+# Test policy only allows Docker based tasks
+main = rule { all_drivers_docker }
+
+# all_drivers_docker checks that all the drivers in use are Docker
+
+all_drivers_docker = rule {
+ all job.task_groups as tg {
+ all tg.tasks as task {
+ task.driver is "docker"
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/terraform.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/terraform.mdx
new file mode 100644
index 0000000000..5922fd0cfb
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/terraform.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Terraform and Sentinel
+sidebar_current: docs-app-terraform
+description: >-
+ Sentinel can be used with Terraform and Terraform Enterprise to enforce policy
+ on Terraform configurations, states, and plans.
+layout: docs
+---
+
+# Terraform
+
+[Terraform Enterprise](https://www.hashicorp.com/products/terraform/) uses Sentinel to enforce
+policy on [Terraform](https://www.terraform.io) configurations, states, and plans.
+
+The Sentinel integration with Terraform runs within
+[Terraform Enterprise](https://www.hashicorp.com/products/terraform/)
+after a `terraform plan` and before a `terraform apply`. The policies
+have access to the created plan, the state at the time of the plan,
+and the configuration at the time of the plan.
+
+The Terraform integration with Sentinel is documented in depth in the [Terraform Enterprise documentation](https://www.terraform.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show basic examples.
+
+### Examples
+
+**Example: All AWS instances must have a tag**
+
+```sentinel
+import "tfplan"
+
+main = rule {
+ all tfplan.resources.aws*instance as *, instances {
+ all instances as \_, r {
+ (length(r.applied.tags) else 0) > 0
+ }
+ }
+}
+```
+
+**Example: Only allow GCP instance sizes smaller than `n1-standard-16`**
+
+```sentinel
+import "tfplan"
+
+allowed_machine_types = [
+ "n1-standard-1",
+ "n1-standard-2",
+ "n1-standard-4",
+ "n1-standard-8",
+]
+
+main = rule {
+ all tfplan.resources.google_compute_instance as _, instances {
+ all instances as _, r {
+ r.applied.machine_type in allowed_machine_types
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/vault.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/vault.mdx
new file mode 100644
index 0000000000..6db2f8af22
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/vault.mdx
@@ -0,0 +1,64 @@
+---
+page_title: Vault and Sentinel
+sidebar_current: docs-app-vault
+description: >-
+ Vault Enterprise uses Sentinel to augment the built-in policy system to
+ provide Role Governing Policies (RGPs) and Endpoint Governing Policies (EGPs)
+ to enable complex, flexible policies across identities and endpoints.
+layout: docs
+---
+
+# Vault
+
+[Vault Enterprise](https://www.hashicorp.com/products/vault/) uses Sentinel
+to augment the built-in policy system to provide Role Governing Policies (RGPs)
+and Endpoint Governing Policies (EGPs) to enable complex, flexible policies
+across identities and endpoints.
+
+**Role Governing Policies (RGPs)** are Sentinel policies that are tied to
+particular tokens, Identity entities, or Identity groups. They have access to
+a rich set of controls across various aspects of Vault. These are evaluated
+whenever a token they're attached to is used.
+
+**Endpoint Governing Policies (EGPs)** are Sentinel policies that are tied to
+particular paths instead of tokens. They have access to as much request
+information as possible, but they can take effect even on unauthenticated
+paths, such as login paths.
+
+The Vault integration with Sentinel is documented in depth in the
+[Vault Enterprise documentation](https://www.vaultproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Endpoint policy that requires MFA authentication from a corporate network.**
+
+```sentinel
+import "sockaddr"
+
+# We expect logins to come only from our private IP range
+cidrcheck = rule {
+ sockaddr.is_contained(request.connection.remote_addr, "10.20.0.0/16")
+}
+
+# Require Ping MFA validation to succeed
+ping_valid = rule {
+ mfa.methods.ping.valid
+}
+
+main = rule when request.path is "auth/ldap/login" {
+ ping_valid and cidrcheck
+}
+```
+
+**Example: Endpoint policy that disallows tokens created before a certain time.**
+
+```sentinel
+import "time"
+import "strings"
+
+main = rule when not strings.has_prefix(request.path, "auth/ldap/login") {
+ time.load(token.creation_time).unix > time.load("2017-09-17T13:25:29Z").unix
+}
+```
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/writing/basic.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/writing/basic.mdx
new file mode 100644
index 0000000000..b62533466e
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/writing/basic.mdx
@@ -0,0 +1,113 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-basic
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Policy Basics
+
+Sentinel policies are easy to write while still supporting advanced constructs
+for creating complex policies. This page will explain the basics of writing
+Sentinel policies to get started. You don't need any prior Sentinel knowledge,
+but we do recommend reading the
+[getting started guide](/sentinel/intro/getting-started/first-policy) and
+[language guide](/sentinel/language) after this.
+
+Sentinel policies are text files written using the [Sentinel language](/sentinel/language).
+The policies are evaluated top-to-bottom. The value of `main` after execution
+determines whether a policy passes or fails.
+
+## The Simplest Policy
+
+Sentinel only requires that a policy have a `main` variable that evaluates to
+a boolean value.
+
+A valid example is shown below:
+
+```sentinel playground
+main = 10 > 5
+```
+
+This type of minimal policy is not purely academic. In practice, simple
+policies can often be reduced to a single line logical statement resulting
+in `true` or `false`. However, the expression is usually wrapped in a
+[rule](/sentinel/language/rules) for testing reasons.
+
+You can verify Sentinel will execute this minimal policy using the CLI:
+
+```
+$ sentinel apply minimal.sentinel
+Pass
+```
+
+## Logical Expressions
+
+Policy is at its core a set of logic: you can or can not perform some action
+under a certain set of circumstances. Those circumstances are logical
+expressions. Therefore, Sentinel policies primarily translate into logical
+expressions.
+
+Detailed documentation on boolean expressions is
+[available in the language guide](/sentinel/language/boolexpr).
+
+A simple numerical comparison was seen in the first example on this page.
+Sentinel also provides inclusion operators such as `contains`, `any`, `all`, and
+more. Sentinel allows some operators to have aliases to promote readability
+while remaining programmer-familiar, such as `==` which can equivalently be `is`.
+
+The example below verifies that all numbers in a list are even:
+
+```sentinel playground
+numbers = [6, 22, 8, 4, 12]
+main = all numbers as n { n % 2 is 0 }
+```
+
+## Variables
+
+A policy will very often use variables. Applications such as
+[Nomad](https://www.nomadproject.io) inject variables into the global
+scope of a policy for making policy decisions. For example, Nomad injects
+the job that is being run into the policy scope. Knowing how to use
+variables is critical to effectively using Sentinel.
+
+Detailed documentation on how to define and access variables is
+[available in the language guide](/sentinel/language/variables).
+
+Variables can be defined and used explicitly. For example:
+
+```sentinel playground
+value = 10
+main = value > 5
+```
+
+But they may also be introduced implictly by the host system. Nomad
+injects `job` into policies to describe the job that is being run. The
+policy below is a valid policy that requires a job have two task groups.
+Notice that `job` is not defined anywhere. It is implicitly inserted by
+the host application (in this case Nomad). Refer to the application you're
+writing policy for to determine if it implicitly inserts values.
+
+```json sentinel
+{
+ "policy": "main = length(job.task_groups) is 2",
+ "globals": {
+ "job": {
+ "task_groups": ["foo", "bar"]
+ }
+ }
+}
+```
+
+## And more!
+
+The Sentinel language supports many more features such as functions,
+loops, and more. You can learn about all of this in the
+[complete language guide](/sentinel/language).
+
+The other pages in the [writing policy](/sentinel/writing)
+will cover other information you need to know about writing Sentinel
+policies that isn't simply a language reference.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/writing/debugging.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/writing/debugging.mdx
new file mode 100644
index 0000000000..7dc284d222
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/writing/debugging.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-debugging
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Debugging
+
+As policies become more complex, it becomes harder to understand exactly why
+a policy may be behaving differently than you expect.
+
+Prior to actively debugging, there are multiple best practices we recommend
+following. We recommend breaking your policy down into a set of
+[rules](/sentinel/writing/rules). This will make it easier to understand
+[traces](/sentinel/writing/tracing) and make it easier to
+[test](/sentinel/writing/testing) your policies.
+This should help to debug policies up to a significant complexity.
+
+## Print Logging
+
+The built-in [print function](/sentinel/language/logging-errors#print)
+can be used to log data. The print output is only shown during failures
+or when [tracing is explicitly enabled](/sentinel/writing/tracing).
+
+The `print` function outputs each argument, which can be of any type, converted
+to a human-friendly string format. Arguments are separated with a single space.
+
+Example:
+
+```sentinel playground
+value = 42
+print("the number is", value) // the number is 42
+
+object = { "foo": false }
+print(object) // { "foo": false }
+
+main = rule { true }
+```
+
+The `print` function returns the value `true` so that it can also be
+used within rule expressions. Note that the `print` function should be
+used at the beginning of statements otherwise they may never be
+reached. This is due to internal performance techniques within Sentinel
+like [short-circuiting](https://en.wikipedia.org/wiki/Short-circuit_evaluation).
+
+Example:
+
+```sentinel playground
+// rule_a and rule_b will evaluate to false
+rule_a = rule { print("rule_a is printed") and false } // "rule_a is printed"
+rule_b = rule { false and print("rule_b is printed") } // Nothing is printed
+
+// rule_c and rule_d will evaluate to true
+rule_c = rule { print("rule_c is printed") or true } // "rule_c is printed"
+rule_d = rule { true or print("rule_d is printed") } // Nothing is printed
+
+main = rule { rule_a or rule_b or rule_c and rule_d }
+```
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/writing/imports.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/writing/imports.mdx
new file mode 100644
index 0000000000..74ac751ee4
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/writing/imports.mdx
@@ -0,0 +1,117 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom import](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only local
+context for making policy decisions.
+
+Sentinel also comes with a set of [standard imports](/sentinel/imports).
+Standard imports are available to every Sentinel policy to help policy writers
+with common tasks such as working with the time, network addresses, and more.
+
+-> **This page is about writing policies that _use_ imports.** If you're
+interested in _creating_ a new import, please see the section on [extending
+Sentinel](/sentinel/extending) for information on how to write modules and
+import plugins.
+
+## Using Imports
+
+To use an import, you use the `import` keyword at the top of your policy. This
+specifies the name of the import you want to use. The application you're writing
+the policy for must already be
+[configured](/sentinel/configuration#imports) to provide that
+import.
+
+Details on imports can be found in the
+[import section in the language reference](/sentinel/language/imports).
+
+In the example below, we use the [time](/sentinel/imports/time) import:
+
+```sentinel playground
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+There are two options to develop a policy locally when using imports:
+configure Sentinel to launch the import on `apply`, or mock the import
+using `mock`. The former requires access to the import while the
+latter is faster (doesn't have to launch a process for plugins) and
+doesn't require the import.
+
+## Mocking Imports
+
+The first option to developing policies locally is to mock the import values.
+When mocking an import, you don't need the import to be available. This can be
+useful since some imports may not be available as a plugin and may only be
+available to the application the policy runs in.
+
+Mocks are specified via the [configuration
+file](/sentinel/configuration). Mocks can also be used for
+[testing](/sentinel/writing/testing).
+
+You can supply mock configuration one of two ways, depending on your use case:
+
+- **[Using static data](/sentinel/configuration#mocking-static-data):**
+ Use this method when you can accurately represent your mock data in JSON and
+ do not need to mock complex Sentinel features such as functions.
+- **[Using Sentinel code](/sentinel/configuration#mocking-with-sentinel-code):**
+ Use this method when using a static JSON object is insufficient, such as when
+ you need to mock functions or other complex Sentinel features.
+
+Our example does not require complex data to be mocked, so a static object
+is sufficient:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ hour = 12
+ weekday_name = "Tuesday"
+ }
+ }
+}
+```
+
+This can be used via the CLI:
+
+```
+$ sentinel apply -config=config.hcl policy.sentinel
+Pass
+```
+
+## Launching Import Plugins
+
+If you have access to the plugin binary, you can launch the import. The
+benefit of this is that it is really using the import to test your
+policy. If the import changes, your policies may start failing. If you
+only use mock data and the import changes, your policies will still
+appear to work.
+
+Imports are configured in the configuration file:
+
+```hcl
+import "plugin" "custom_time" {
+ source = "/path/to/sentinel-time-import"
+}
+```
+
+This would require the `sentinel-time-import` binary. For this example
+this doesn't currently exist. We plan on writing one to provide for this
+section of the documentation.
+
+## Custom Imports
+
+You can also [create your own imports](/sentinel/extending).
+
+If your policy decisions could benefit from accessing external information,
+then you can use custom imports as a way to do this.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/writing/index.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/writing/index.mdx
new file mode 100644
index 0000000000..064069d389
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/writing/index.mdx
@@ -0,0 +1,35 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing
+description: >-
+ Sentinel provides a language and workflow for building policy across any
+ system that embeds Sentinel. By learning Sentinel once, you are able to
+ effectively control access to many systems using Sentinel's powerful features.
+ Basic concepts that are important to understand for Vault usage.
+layout: docs
+---
+
+# Writing Sentinel Policy
+
+This section covers how to write Sentinel policies.
+
+It is recommended that you complete the
+[getting started guide](/sentinel/intro/getting-started/first-policy) prior to
+reading this section of the documentation. The getting started guide will
+explain all the basics of writing and testing policies. This section of the
+documentation will serve as more of a reference guide to all available
+features of Sentinel.
+
+Sentinel provides a language and workflow for building policy across any system
+that embeds Sentinel. By learning Sentinel once, you are able to effectively
+control access to many systems using Sentinel's powerful features.
+
+Sentinel also provides a [local CLI](/sentinel/commands) for developing and testing
+Sentinel policies. This CLI can be integrated into a daily developer's workflow
+along with CI to treat [policy as code](/sentinel/concepts/policy-as-code).
+
+Sentinel uses its own [language](/sentinel/language) for writing
+policies. You can view a [language reference](/sentinel/language)
+as well as the [specification](/sentinel/language/spec) for details. You
+don't have to read those documents immediately, since the language should
+be easy enough to pick up throughout this section.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/writing/rules.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/writing/rules.mdx
new file mode 100644
index 0000000000..ab4fc69d08
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/writing/rules.mdx
@@ -0,0 +1,65 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-rules
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Rules
+
+Rules form the basis of a policy by representing behavior that is either passing
+or failing. Rules are a first class language construct in Sentinel. A policy can
+and should be broken down into rules to aid with readability, testability, and
+performance.
+
+Rules provide:
+
+- **Readability:** A policy that is broken down into rules can be read
+ more easily. The logic becomes clearer to see for policy writers when
+ they come back to it.
+
+- **Reportability:** When [tracing](/sentinel/writing/tracing) is enabled,
+ rules form the foundation of the data returned. All evaluated rules are added
+ to the trace with the data the rule evaluated to, and their position within
+ policy or module source files.
+
+- **Testability:** [Policy testing](/sentinel/writing/testing) is
+ built on asserting the values of rules. By breaking logic down into
+ rules, you're able to more effectively test your policies.
+
+- **Performance:** Rules are only evaluated on demand, and are also only
+ evaluated once. This means that a rule referenced multiple times only has a
+ one-time performance cost. For complex logic, this could result in improved
+ performance.
+
+An example usage of rules is shown below:
+
+```json sentinel
+{
+ "policy": "is_sunny = rule { weather is \"sunny\" }\nis_wednesday = rule { day is \"wednesday\" }\nmain = rule { is_sunny and is_wednesday }",
+ "globals": {
+ "weather": "sunny",
+ "day": "wednesday"
+ }
+}
+```
+
+Rules can also contain non-boolean data. This can be useful for passing more
+detail to the caller than an opaque boolean value would be able to communicate.
+
+```sentinel playground
+days = [{"weather": "sunny"}]
+main = rule {
+ filter days as d {
+ d.weather is not "sunny"
+ }
+}
+```
+
+Details about rules and their behavior can be found in
+[the language reference](/sentinel/language/rules). Rules have important
+behavior so we recommend reading the language reference on rules.
+Rules will be used throughout the remainder of the documentation.
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/writing/testing.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/writing/testing.mdx
new file mode 100644
index 0000000000..61ab0ea46c
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/writing/testing.mdx
@@ -0,0 +1,491 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-testing
+description: >-
+ Sentinel has a built-in test framework to validate a policy behaves as
+ expected.
+layout: docs
+---
+
+# Testing
+
+Sentinel has a built-in test framework to validate a policy behaves as expected.
+
+With an ever-increasing amount of automation surrounding technology,
+the guardrails provided by policies are a critical piece towards ensuring
+expected behavior. As a reliance on correct policy increases, it is important
+to test and verify policies.
+
+Testing is a necessary step to fully realize
+[policy as code](/sentinel/concepts/policy-as-code). Just as good software
+is well tested, a good set of policies should be equally well tested.
+
+## Test Folder Structure
+
+Policies are tested by asserting that [rules](/sentinel/writing/rules)
+are the expected values given a pre-configured input. Tests are run
+by executing the [test command](/sentinel/commands/test).
+
+Sentinel is opinionated about the folder structure required for tests.
+This opinionated structure allows testing to be as simple as running
+`sentinel test` with no arguments. Additionally, it becomes simple to test
+in a CI or add new policies.
+
+The structure Sentinel expects is `test//*.[hcl|json]` where ``
+is the name of your policy file without the file extension. Within that folder
+is a list of HCL or JSON files. Each file represents a single test case.
+Therefore, each policy can have multiple tests associated with it.
+
+-> Note that the primary configuration file format for Sentinel is HCL. While
+you can write configuration files in a HCL-equivalent JSON, we only discuss the
+use of HCL on this page.
+
+## Test Case Format
+
+Each HCL file within the test folder for a policy is a single test case.
+
+The file is the same configuration format as the [CLI configuration
+file](/sentinel/configuration). The format lets you define mock data, imports to
+use, and more. This mock data is the key piece in being able to test policies:
+you craft a specific scenario and assert your policy behaves as you expect.
+
+Test cases also use the `test` block within the configuration file to assert the
+value of [rules](/sentinel/writing/rules). If the `test` key is omitted, the
+policy is expected to pass. If the test key is specified, only the rules
+specified in the map will be asserted. This means if you omit `main`, then the
+final policy result is not asserted.
+
+Example with assertions:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+
+test {
+ rules = {
+ main = false
+ is_open_hours = false
+ is_weekday = true
+ }
+}
+```
+
+The configuration above specifies some parameter data, and asserts the result of
+some rules. This is the same configuration used in the example section below.
+
+## Example
+
+Lets use the following file as an example. Save this file to a directory
+and name it `policy.sentinel`. It can be named anything with the `sentinel`
+extension, but by naming it `policy.sentinel` your output should match
+the example output on this page.
+
+```sentinel
+// The day of the week.
+param day
+
+// The hour of the day.
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+### A Passing Test
+
+Next, let's define a single test case. Relative to where you saved
+the policy, create a file at the path `test/policy/good.hcl`.
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 14
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/good.hcl
+```
+
+This passed because the policy passed. We didn't assert any specific
+rules. By not specifying any assertions, `test` expects the policy itself
+to fully pass.
+
+### A Failing Test
+
+Define another test case to fail. We want to verify our policy fails when expected, too.
+
+Save the following as `test/policy/7-am.hcl`:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+FAIL - policy.sentinel
+ FAIL - test/policy/7-am.hcl
+ expected "main" to be true, got: false
+
+ trace:
+ policy.sentinel:9:1 - Rule "main"
+ bool: false
+
+ policy.sentinel:8:1 - Rule "is_open_hours"
+ bool: false
+
+ PASS - test/policy/good.hcl
+```
+
+As you can see, the test fails because "main" is false. This is good
+because the policy _should_ have failed since we specified an invalid
+hour. But, we expect main to be false and don't want our test to fail!
+Update `7-am.hcl` to add test assertions:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+
+test {
+ rules = {
+ main = false
+ is_open_hours = false
+ }
+}
+```
+
+And when we run the tests:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/7-am.hcl
+ PASS - test/policy/good.hcl
+```
+
+The test passes. We asserted that we expect the `main` rule to be false,
+the `is_open_hours` rule to be false, and the `is_weekday` rule to be
+true. By asserting some rules are true, we can verify that our policy
+is failing for reasons we expect.
+
+## Mocking
+
+The above example demonstrates how to test by supplying different
+[parameters](/sentinel/language/parameters). Parameters in a policy can be
+specifically useful when you want to control user-defined input values to a
+policy.
+
+However, generally, when testing, you will need mimic the conditions you will
+see in production. Production implementations of Sentinel will supply data using
+one of two methods:
+
+- **Global data:** Data is injected directly into the policy's scope and is
+ accessible using normal identifiers, similar to variables.
+- **Imports:** Data is stored behind an [import](/sentinel/language/imports)
+ and loaded on demand as needed by the policy author.
+
+Proper testing of a policy requires that these values be able to be _mocked_ -
+or, in other words, simulated in a way that allows the accurate testing of the
+scenarios that a policy could reasonably pass or fail under.
+
+Mocking both globals and imports can be done by setting various parts of the
+configuration file.
+
+### Mocking Globals
+
+Demonstrating the mocking of globals can be seen by making a few modifications to
+our example policy, removing the `param` declarations:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Then, change the `param` section in the configuration file to
+[`global`](/sentinel/configuration#global).
+
+```json
+global "day" {
+ value = "monday"
+}
+
+global "hour" {
+ value = 14
+}
+```
+
+This test should still pass, as if nothing had happened, although what we've
+done is shifted our parameters to globals, simulating an environment where `day`
+and `hour` are already defined for us.
+
+### Mocking Imports
+
+To mock imports, we need to use the
+[`mock`](/sentinel/configuration#mock-imports) section of the
+configuration file.
+
+Let's say the above example is behind an import named `time`.
+
+-> **NOTE:** [`time`](/sentinel/imports/time) is a valid standard import.
+This example may not be accurate to the
+import's syntax.
+
+The code now looks like this:
+
+```sentinel
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To mock this import, we can [mock it as static
+data](/sentinel/configuration#mocking-static-data). The configuration
+file now looks like, without assertions:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ weekday_name = "Monday"
+ hour = 14
+ }
+ }
+}
+```
+
+The policy will now pass, with the `time` import mocked.
+
+Data can also be [mocked as Sentinel
+code](/sentinel/configuration#mocking-with-sentinel-code). In this case,
+the above configuration file would look like:
+
+```hcl
+mock "time" {
+ module {
+ source = "mock-time.sentinel"
+ }
+}
+```
+
+And a file named `mock-time.sentinel` would now hold your mock values:
+
+```sentinel
+day = "monday"
+hour = 14
+```
+
+Mocking as Sentinel code allows more complex details to be mocked as well, such
+as functions. Say we wanted to mock the `time.load()` function. To mock this,
+just add it to the `mock-time.sentinel` file:
+
+```sentinel
+load = func(_) {
+ return {
+ "weekday_name": "Monday",
+ "hour": 14,
+ }
+}
+```
+
+Your code can now be written as:
+
+```sentinel
+import "time"
+
+t = time.load("a_mock_timestamp")
+
+is_weekday = rule { t.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { t.hour > 8 and t.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To see more details, see the [Mock
+Imports](/sentinel/configuration#mock-imports) section in the
+configuration file.
+
+## Asserting Non-Boolean Rules
+
+Non-boolean rules can also be asserted by `sentinel test`.
+
+To assert non-boolean values, simply enter the expected value into the rule
+contents:
+
+```hcl
+param "maintenance_days" {
+ value = [
+ {
+ day = "wednesday"
+ hour = 9
+ },
+ {
+ day = "friday"
+ hour = 1
+ },
+ {
+ day = "sunday"
+ hour = 1
+ },
+ ]
+}
+
+test {
+ rules = {
+ main = [
+ {
+ day = "wednesday"
+ hour = 9
+ },
+ ]
+ }
+}
+```
+
+This would assert a policy checking for violations of a maintenance policy,
+saying that maintenance hours should happen before 6AM on any given day:
+
+```sentinel
+param maintenance_days
+
+main = rule {
+ filter maintenance_days as d {
+ d.hour >= 6
+ }
+}
+```
+
+## Test JSON Output
+
+Running `sentinel test` with the `-json` flag will give you the test results in
+a JSON output format, suitable for parsing by reporting software.
+
+-> **Note:** The JSON output format is currently under development and the
+format may change at a later time. Additionally, support for other well-known
+formats, such as JUnit, may become available in the future.
+
+The current format is an object with the only top-level key being `policies`,
+with each test grouped up by policy being run.
+
+The policy result fields are:
+
+- `path`: The path of the policy and the index of the test result in the
+ `policies` field in the root object.
+- `status`: A string representation of the policy's test status as a whole. Can
+ be one of `PASS`, `FAIL`, `ERROR`, or `?`. The final status, `?`, represents a
+ policy that has no tests to process, and acts like a passing test.
+- `errors`: An array of any error messages encountered during processing the
+ policy for testing. Usually reserved for policy file or parser-related errors.
+ For case-specific errors, see the error field for the particular case.
+- `cases`: A map of case results, indexed by case path.
+
+The case result fields are:
+
+- `path`: The path of the test case and the result's index in the `cases` field
+ in the policy object.
+- `status`: A string representation of the case's test status. Can be one of
+ `PASS`, `FAIL` or `ERROR`.
+- `errors`: An array of any error messages encountered during running this test
+ case.
+- `trace`: The trace for this policy in JSON format. See the
+ [tracing](/sentinel/writing/tracing) page for more details.
+- `rule_detail`: When the `status` of this test case is `FAIL`, contains an
+ object of assertion failure detail, indexed by rule. Only failures are counted
+ here; any rules not found here can be assumed to have passed or not asserted.
+- `config_warnings`: An array of strings denoting any configuration warnings
+ found while processing the configuration for this test case.
+- `config_legacy`: Denotes whether or not the configuration is a legacy JSON
+ configuration and needs to be modernized. This field may be removed in future
+ releases.
+
+A passing example is shown below:
+
+```json
+{
+ "policies": {
+ "policy.sentinel": {
+ "path": "policy.sentinel",
+ "status": "PASS",
+ "errors": null,
+ "cases": {
+ "test/policy/pass.hcl": {
+ "path": "test/policy/pass.hcl",
+ "status": "PASS",
+ "errors": null,
+ "trace": {
+ "description": "A very basic policy to determine if a run is within working hours.",
+ "error": null,
+ "print": "",
+ "result": true,
+ "rules": {
+ "is_open_hours": {
+ "desc": "Passes if run during business hours.",
+ "ident": "is_open_hours",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 290,
+ "line": 13,
+ "column": 1
+ },
+ "value": true
+ },
+ "is_weekday": {
+ "desc": "Passes if the day does not fall on the weekend.",
+ "ident": "is_weekday",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 193,
+ "line": 10,
+ "column": 1
+ },
+ "value": true
+ },
+ "main": {
+ "desc": "",
+ "ident": "main",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 338,
+ "line": 14,
+ "column": 1
+ },
+ "value": true
+ }
+ }
+ },
+ "rule_detail": {},
+ "config_warnings": null,
+ "config_legacy": false
+ }
+ }
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.24.x/content/sentinel/docs/writing/tracing.mdx b/content/sentinel/v0.24.x/content/sentinel/docs/writing/tracing.mdx
new file mode 100644
index 0000000000..f2069bd682
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/docs/writing/tracing.mdx
@@ -0,0 +1,401 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-tracing
+description: >-
+ Sentinel provides execution traces to better understand the execution of a
+ policy.
+layout: docs
+---
+
+# Traces
+
+Sentinel provides execution traces to better understand the execution of a policy.
+
+When using the Sentinel [CLI](/sentinel/commands), traces are shown
+on policy failure during `apply` or `test`, or when the `-trace` flag is
+explicitly specified. For Sentinel-enabled applications, traces are optional
+and may require special configuration to enable.
+
+The availability of the trace may vary from application to application. While
+some applications may only log traces on failure, others, such as [Terraform
+Cloud](https://www.terraform.io/cloud), log the trace on every execution. For
+more details, consult your implementation's documentation.
+
+-> **Note:** We're actively improving Sentinel tracing in each release
+to provide better data and improve readability. The exact format of a
+trace may not match the Sentinel version embedded in the application you're
+using, but the data should be similar. The canonical format for a trace should
+get more stable as we approach a 1.0 release.
+
+## Analyzing a Trace
+
+To show traces, let's use the example policy below with the CLI:
+
+```sentinel
+param day
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+
+main = rule { is_open_hours and is_weekday }
+```
+
+Let's cause the policy to fail so the trace is more interesting.
+If you're on a terminal that supports it, the trace output will be colored
+so you can more clearly see passes, failures, and rules.
+
+
+
+ $ sentinel apply main.sentinel{'\n'}
+
+ main.sentinel:1:7: requires value for parameter day
+
+ {'\n'}
+ {'\n'}
+ {'\n'}
+ {' '}Values can be strings, floats, or JSON array or object values. To force
+ {'\n'}
+ {' '}strings, use quotes.{'\n'}
+ {'\n'}
+ {' '}Enter a value: sunday{'\n'}
+ {'\n'}
+
+ main.sentinel:2:7: requires value for parameter hour
+
+ {'\n'}
+ {'\n'}
+ {'\n'}
+ {' '}Values can be strings, floats, or JSON array or object values. To force
+ {'\n'}
+ {' '}strings, use quotes.{'\n'}
+ {'\n'}
+ {' '}Enter a value: 14{'\n'}
+ {'\n'}
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+
+ Fail - main.sentinel
+
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:7:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}
+ false
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:5:1 - Rule "is_open_hours"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}true{'\n'}
+ {'\n'}
+
+ main.sentinel:4:1 - Rule "is_weekday"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}false{'\n'}
+
+
+
+We can see all of our rules neatly and clearly displayed along with the result
+of the policy. If you are getting the trace due to a failed policy and not
+because you explicitly used `-trace`, an informational message is displayed.
+
+As we can see from our basic example, the `main` rule has failed and its result
+is highlighted in red. All peripheral rules that were also evaluated as part of
+the policy are also displayed below the main rule. Source positions are also
+displayed so that you can find the references to the rules in your code.
+
+Via the trace, we can clearly see that while `is_open_hours` returned a true
+result, `is_weekday` did not, and per the logic in our code, the policy is
+rejected.
+
+## Non-Boolean Rules and the Trace
+
+The trace is particularly important when working with rules that return
+non-boolean data, such as rules where you want to see violations instead of a
+simple opaque boolean value. Let's take our example from the [rules](./rules)
+section, and write a small static policy for it:
+
+```sentinel playground
+days = [
+ {"weekday": "Sunday", "weather": "clouds"},
+ {"weekday": "Monday", "weather": "rain"},
+ {"weekday": "Tuesday", "weather": "sunny"},
+ {"weekday": "Wednesday", "weather": "sunny"},
+ {"weekday": "Thursday", "weather": "clouds"},
+ {"weekday": "Friday", "weather": "rain"},
+ {"weekday": "Saturday", "weather": "sunny"},
+]
+
+main = rule {
+ filter days as d {
+ d.weather is not "sunny"
+ }
+}
+```
+
+Here is the output of our policy:
+
+
+
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+ {'\n'}
+
+ Fail - weather.sentinel
+
+ {'\n'}
+ {'\n'}
+
+ weather.sentinel:11:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+
+ {' [\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Sunday"'}
+ {'\n'}
+ {' "weather": "clouds"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Monday"'}
+ {'\n'}
+ {' "weather": "rain"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Thursday"'}
+ {'\n'}
+ {' "weather": "clouds"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Friday"'}
+ {'\n'}
+ {' "weather": "rain"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' ]'}
+
+
+
+
+We can now see all of the weekdays with non-sunny weather.
+
+Note that to prevent formatting issues and excessive output, the human-readable
+trace is limited in the volume of output it will display for collections.
+
+## JSON Output
+
+The trace can also be displayed in JSON by adding `-json` to `sentinel apply`
+and `sentinel test`, and will display the trace in its entirety, unlike the
+standard CLI output.
+
+Here is the result of running `sentinel apply -json` against our weather policy:
+
+```json
+{
+ "can_override": false,
+ "error": null,
+ "policies": [
+ {
+ "allowed_failure": false,
+ "error": null,
+ "policy": "weather.sentinel",
+ "result": false,
+ "trace": {
+ "description": "",
+ "error": null,
+ "print": "",
+ "result": false,
+ "rules": {
+ "main": {
+ "desc": "",
+ "ident": "main",
+ "position": {
+ "filename": "weather.sentinel",
+ "offset": 328,
+ "line": 11,
+ "column": 1
+ },
+ "value": [
+ {
+ "weather": "clouds",
+ "weekday": "Sunday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Monday"
+ },
+ {
+ "weather": "clouds",
+ "weekday": "Thursday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Friday"
+ }
+ ]
+ }
+ }
+ }
+ }
+ ],
+ "result": false
+}
+```
+
+Note that the trace is always included with this output, regardless of whether
+or not the policy passes or fails; using `-trace` is unnecessary.
+
+Additionally, with `sentinel apply`, you can display the value of a single rule
+in JSON, instead of the entire trace. This is done with the `-json-rule` flag.
+
+-> **Note:** `sentinel apply -json-rule` needs to be run either against a single
+on-disk policy, or against a single policy within a policy set. It is ignored
+in full policy set executions and functions exactly like `-json` in these
+scenarios.
+
+Here is the result of executing `sentinel apply -json-rule main weather.sentinel`:
+
+```json
+[
+ {
+ "weather": "clouds",
+ "weekday": "Sunday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Monday"
+ },
+ {
+ "weather": "clouds",
+ "weekday": "Thursday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Friday"
+ }
+]
+```
+
+## Other Trace Details
+
+There are some other details that are good to know when working with the trace:
+
+- Only rules that get executed are added to the trace. Considering our first
+ example where the `main` rule is the expression `is_open_hours and is_weekday`,
+ if our expression was using an `or` operator instead of `and`, in addition to
+ the policy passing, `is_open_hours` would be present in the trace, but
+ `is_weekday` would not be, as the policy would have never evaluated that rule.
+- Descriptions for both rules and policies will show up in the trace as well if
+ present. Here is the output to our first policy with the appropriate
+ annotations:
+
+
+
+ ...{'\n\n'}
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+
+ Fail - main.sentinel
+
+ {'\n'}
+ {'\n'}
+ Description:{'\n'}
+ {' A very basic policy to determine if a run is within working\n'}
+ {' hours.\n'}
+ {'\n'}
+
+ main.sentinel:7:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}
+ false
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:5:1 - Rule "is_open_hours"
+
+ {'\n'}
+ {' '}
+ Description:
+ {'\n'}
+ {' Passes if run during business hours.\n'}
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}true{'\n'}
+ {'\n'}
+
+ main.sentinel:4:1 - Rule "is_weekday"
+
+ {'\n'}
+ {' '}
+ Description:
+ {'\n'}
+ {' Passes if the day does not fall on the weekend.\n'}
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}false{'\n'}
+
+
diff --git a/content/sentinel/v0.24.x/content/sentinel/intro/getting-started/first-policy.mdx b/content/sentinel/v0.24.x/content/sentinel/intro/getting-started/first-policy.mdx
new file mode 100644
index 0000000000..38d7b22c9a
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/intro/getting-started/first-policy.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Your First Sentinel Policy
+sidebar_current: intro-getting-started-first
+description: Let's begin by writing a working Sentinel policy.
+layout: intro
+---
+
+# Your First Sentinel Policy
+
+Sentinel is a system to enforce complex policies on an integrated application.
+
+Writing Sentinel policy requires minimal programming experience. The Sentinel
+language is designed to be approachable and learned quickly and easily. Whether
+you're a professional programmer or someone who uses SQL and Excel, you can
+learn to write Sentinel policies.
+
+Let's begin by writing a simple, working Sentinel policy:
+
+```sentinel playground
+hour = 4
+main = rule { hour >= 0 and hour < 12 }
+```
+
+This is a valid Sentinel policy. It will pass since we hardcoded the
+`hour` to be 4. In a real system, `hour` may be something that is provided
+to us and actually set to the current hour. We'll learn more about that later.
+
+For now, try running this policy locally. Save the above example to a file
+named `policy.sentinel` and execute it. Then, modify the policy to make it
+fail. Play around more if you'd like.
+
+```
+$ sentinel apply policy.sentinel
+Pass
+```
+
+## Main
+
+Every Sentinel policy must have a `main` rule. This is the rule that
+is evaluated to determine the result of a policy.
+
+A rule describes an expression that generally means one of two things:
+
+- Does a policy _pass a condition_ that would authorize an operation? In our
+ above example, describe a policy that checks the supplied hour (4) is within an
+ authorized time window (between 0 - midnight, and 12 noon).
+- Conversely, can a policy find any _violations_ that would block authorization
+ of the operation? Building on the above, consider a policy that takes a
+ schedule, and finds all time blocks that fall outside of the example time window
+ supplied in the above policy.
+
+It is easy to imagine that such a rule might be used in a system such
+as [Nomad](https://www.nomadproject.io) to restrict the times when a
+deploy can occur. The power of arbitrary logical statements within Sentinel
+allows Sentinel policies to restrict almost any behavior.
+
+Next, we'll
+[introduce and explain rules](/sentinel/intro/getting-started/rules)
+so we can use them in our policies.
diff --git a/content/sentinel/v0.24.x/content/sentinel/intro/getting-started/imports.mdx b/content/sentinel/v0.24.x/content/sentinel/intro/getting-started/imports.mdx
new file mode 100644
index 0000000000..dbbb146ecf
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/intro/getting-started/imports.mdx
@@ -0,0 +1,58 @@
+---
+page_title: Imports
+sidebar_current: intro-getting-started-imports
+description: Imports enable Sentinel to access external data and functions.
+layout: intro
+---
+
+# Imports
+
+Imports enable Sentinel to access external data and functions.
+
+Sentinel ships with a set of [standard imports](/sentinel/imports).
+Standard imports are available by default to every Sentinel policy. Note that
+the application embedding Sentinel may allow or deny the available
+set of imports.
+
+To use an import, use the `import` statement. Then, access data and
+functions on the import using the import name followed by a period and
+the field you want to access. The example below checks whether the request
+path starts with a prefix. Assume that `request_path` is available.
+
+```sentinel
+import "strings"
+
+main = rule { strings.has_prefix(request_path, "/admin") }
+```
+
+## External Data
+
+The true power in imports is their ability to reference external data.
+Imports can be added to a Sentinel-enabled application through
+[plugins](/sentinel/extending). This enables any Sentinel-enabled
+application to make policy decision based on any source of data.
+
+This ability allows the policy system to enforce almost any necessary
+organizational policy, since the ability of a policy isn't restricted
+purely to the embedding application's data model.
+
+For example, policies in [Nomad](https://www.nomadproject.io) can
+access data in [Consul](https://www.consul.io) to determine attributes
+of a policy. In the example below, we use a hypothetical Consul import:
+
+```sentinel
+import "consul"
+
+main = rule {
+ job.tasks[0].resources.memory <= int(consul.get("policy/nomad/max-memory"))
+}
+```
+
+In this example, a Nomad job's memory usage is limited to the value of
+a Consul key/value item. By simply changing a Consul KV entry, policy
+can be changed. Imports can do anything, you can [write your own import plugins](/sentinel/extending)
+to extend Sentinel.
+
+Next, we'll learn how to
+[test policies](/sentinel/intro/getting-started/testing)
+to ensure our policy logic is correct.
diff --git a/content/sentinel/v0.24.x/content/sentinel/intro/getting-started/index.mdx b/content/sentinel/v0.24.x/content/sentinel/intro/getting-started/index.mdx
new file mode 100644
index 0000000000..5c9b6b43c5
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/intro/getting-started/index.mdx
@@ -0,0 +1,16 @@
+---
+page_title: Install Sentinel CLI - Getting Started
+sidebar_current: intro-getting-started-overview
+description: The first step to using Sentinel is to get the CLI installed.
+layout: intro
+---
+
+# Getting Started With Sentinel
+
+This section covers getting started with Sentinel. Here, we will take you
+through the first steps that you will need to do to get started with installing
+the Sentinel CLI, writing your first policy, and getting familiar with rules,
+boolean logic, imports, and testing.
+
+You can select a topic on the menu to get started. The first step is to [install
+the Sentinel CLI](/sentinel/intro/getting-started/install).
diff --git a/content/sentinel/v0.24.x/content/sentinel/intro/getting-started/install.mdx b/content/sentinel/v0.24.x/content/sentinel/intro/getting-started/install.mdx
new file mode 100644
index 0000000000..a23a5b7239
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/intro/getting-started/install.mdx
@@ -0,0 +1,56 @@
+---
+page_title: Install Sentinel CLI - Getting Started
+sidebar_current: intro-getting-started-install
+description: The first step to using Sentinel is to get the CLI installed.
+layout: intro
+---
+
+# Install Sentinel CLI
+
+Sentinel is a policy framework that is embedded in the enterprise versions of
+HashiCorp tools. The policies you write are deployed to these applications and
+enforced there.
+
+The Sentinel CLI is a command-line interface for local development and testing.
+For the getting started guide, we'll use the CLI to learn how to write policies
+for Sentinel-enabled applications. The Sentinel CLI is distributed as a [binary
+package](/sentinel/install) for all supported platforms and architectures.
+
+## Installation Instructions
+
+To install the Sentinel CLI, find the [appropriate package](/sentinel/install) for your
+system and download it. The CLI is packaged as a zip archive.
+
+After downloading Sentinel, unzip the package. The CLI runs as a single binary
+named `sentinel`. Any other files in the package can be safely removed and
+Sentinel will still function.
+
+The final step is to make sure that the `sentinel` binary is available on the `PATH`.
+See [this page](https://stackoverflow.com/questions/14637979/how-to-permanently-set-path-on-linux)
+for instructions on setting the PATH on Linux and Mac.
+[This page](https://stackoverflow.com/questions/1618280/where-can-i-set-path-to-make-exe-on-windows)
+contains instructions for setting the PATH on Windows.
+
+## Verifying the Installation
+
+After installing the Sentinel CLI, verify the installation worked by opening a
+new terminal session and checking that the `sentinel` binary is available. By
+executing `sentinel`, you should see help output similar to the following:
+
+```
+$ sentinel
+Usage: sentinel [--version] [--help] []
+
+Available commands are:
+ apply Execute a policy and output the result
+ fmt Format Sentinel policy to a canonical format
+ test Test policies
+ version Prints the Sentinel version
+```
+
+If you get an error that the binary could not be found, then your `PATH`
+environment variable was not setup properly. Please go back and ensure that your
+`PATH` variable contains the directory where Sentinel was installed.
+
+Otherwise, the CLI is installed and ready to go. Let's celebrate by [writing our
+first policy!](/sentinel/intro/getting-started/first-policy)
diff --git a/content/sentinel/v0.24.x/content/sentinel/intro/getting-started/logic.mdx b/content/sentinel/v0.24.x/content/sentinel/intro/getting-started/logic.mdx
new file mode 100644
index 0000000000..d3716a64ea
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/intro/getting-started/logic.mdx
@@ -0,0 +1,54 @@
+---
+page_title: Logic
+sidebar_current: intro-getting-started-logic
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Logic
+
+The core of a policy is a set of logical expressions to determine whether
+a behavior is allowed or not.
+Sentinel makes it easy to write readable logical expressions to express
+the intended policy.
+
+The logical expression syntax will be familiar to anyone who has programmed
+before. Sentinel also supports a couple more unique constructs to assist
+with common policy requirements. Detailed documentation on how to write
+logical expressions and the supported operators is available in
+[the boolean expression reference](/sentinel/language/boolexpr).
+
+Example logic:
+
+```sentinel
+a is b // Equality, you can also use ==
+a is not b // Inequality, you can also use !=
+
+a > b // Relational operators, comparison
+a < b
+a >= b
+a <= b
+
+a contains b // Inclusion check, substrings, etc.
+a not contains b // Negated version of above
+```
+
+The full list of available operators can be found in
+[the boolean expression reference](/sentinel/language/boolexpr).
+
+Logic can be combined with `and` or `or`. When combined with `and`, both
+sides must result in `true`. When combined with `or`, only one side needs
+to be true to result in `true`.
+
+With these building blocks, basic rules can be built up:
+
+```sentinel
+main = rule {
+ hour >= 8 and
+ hour < 17
+}
+```
+
+Next, we'll introduce
+[imports](/sentinel/intro/getting-started/imports)
+so that we can write rules that interact with external data.
diff --git a/content/sentinel/v0.24.x/content/sentinel/intro/getting-started/next-steps.mdx b/content/sentinel/v0.24.x/content/sentinel/intro/getting-started/next-steps.mdx
new file mode 100644
index 0000000000..23b470be59
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/intro/getting-started/next-steps.mdx
@@ -0,0 +1,21 @@
+---
+page_title: Next Steps
+sidebar_current: intro-getting-started-nextsteps
+description: That concludes the getting started guide for Sentinel.
+layout: intro
+---
+
+# Next Steps
+
+That concludes the getting started guide for Sentinel. Hopefully you're excited
+about the possibilities of Sentinel and ready to put this knowledge to use to
+improve the safety of your environments.
+
+We've covered the basics of the core features of Sentinel in this guide.
+We recommend the following as next steps:
+
+- [Documentation](/sentinel) - The documentation is an in-depth
+ reference guide of all the features of Sentinel.
+
+- [Language Reference](/sentinel/language) - The language reference
+ is a complete reference to the features of the Sentine policy language.
diff --git a/content/sentinel/v0.24.x/content/sentinel/intro/getting-started/rules.mdx b/content/sentinel/v0.24.x/content/sentinel/intro/getting-started/rules.mdx
new file mode 100644
index 0000000000..f12a61fe75
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/intro/getting-started/rules.mdx
@@ -0,0 +1,86 @@
+---
+page_title: Rules
+sidebar_current: intro-getting-started-rules
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Rules
+
+Rules in Sentinel are a first-class concept. Within a policy, rules serve a few
+purposes:
+
+- They make complex logic more understandable by allowing said logic to be
+ broken down.
+- They allow assertion of this logic through
+ [testing](/sentinel/intro/getting-started/testing) of the rule's contents.
+- They facilitate reporting of data as rules get published as part of the [policy
+ trace](/sentinel/writing/tracing).
+
+A rule functions in ways similar to both a variable and a function: they hold a
+value, but are lazily evaluated; a rule's value is not assigned until the first
+time it's referenced in a policy. Additionally, while the value of evaluated
+rules will be available within a policy's trace after it's evaluated, values of
+variables - and the return value of functions - are not. Finally, a rule value
+is memoized - further references to the rule will not change its result.
+
+Rules can hold more than just boolean data. For more advanced rule patterns, see
+[the language reference](/sentinel/language/rules).
+
+## Writing Rules
+
+Let's look at the Sentinel policy we wrote in the previous section:
+
+```sentinel playground
+hour = 4
+main = rule { hour >= 0 and hour < 12 }
+```
+
+The logic in this policy is straightforward and easy to understand. However,
+it is common for policy logic to become much more complicated. Rules are the
+key abstraction for making complex logic understandable. We can turn the
+policy above into a set of rules:
+
+```sentinel playground
+hour = 4
+
+past_midnight = rule { hour >= 0 }
+before_noon = rule { hour < 12 }
+
+main = rule {
+ past_midnight and
+ before_noon
+}
+```
+
+This policy is easier to read. Our original policy was already quite
+simple, but it is easy to imagine a more complex policy becoming much
+more readable by extracting logical expressions into a set of rules.
+
+Rules don't just exist to make a policy more readable. They're also a
+testing and debugging point. For testing, you can assert that certain rules
+are certain values given a specific input to verify that your policy works
+as you expect. Testing and debugging are covered in later sections.
+
+## Conditional Rules
+
+In many cases, a rule is only valid when something else is also true.
+
+Consider the human language statement "before you eat, you must wash your hands."
+The rule "you must wash your hands" is only valid "before you eat". In any
+other scenario, the rule is meaningless.
+This is also true in a technical context. Imagine the rule "if the driver
+is Docker, you must not expose any ports." For this example, assume
+rules `is_docker` and `has_no_exposed_ports` exist. You can write this rule
+like this:
+
+```sentinel
+main = rule when is_docker { has_no_exposed_ports }
+```
+
+When `is_docker` is true, the rule is evaluated as normal. However,
+when `is_docker` is false, the rule always returns `true`, because the
+logic of the rule is meaningless.
+
+Let's learn how to create more complex rules with
+[logical expressions](/sentinel/intro/getting-started/logic).
diff --git a/content/sentinel/v0.24.x/content/sentinel/intro/getting-started/testing.mdx b/content/sentinel/v0.24.x/content/sentinel/intro/getting-started/testing.mdx
new file mode 100644
index 0000000000..7149a7966f
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/intro/getting-started/testing.mdx
@@ -0,0 +1,72 @@
+---
+page_title: Testing
+sidebar_current: intro-getting-started-testing
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Testing
+
+It is important to ensure the policies you write are correct. Sentinel
+includes a built-in test framework that can be run locally and in CI
+environments to test that policies behave as expected.
+
+A common pitfall with many simple ACL systems is that they provide no easy
+way to verify their correctness. You basically have to set the ACL and try
+the behaviors against a real system to verify it is working as expected.
+This requires a lot of setup and is unique to each system.
+
+[Sentinel's built-in test framework](/sentinel/writing/testing) has zero
+dependencies. Contained within the [Sentinel CLI](/sentinel/commands), it can
+mock the data that real systems are exposing to the policy. It is designed to be
+CI-friendly and enables continuous testing of your policies. This is necessary
+for [policy as code](/sentinel/concepts/policy-as-code).
+
+Detailed documentation on testing policies is available in [the Sentinel Testing
+reference](/sentinel/writing/testing).
+
+## Writing Tests
+
+For this example, save the following policy as `policy.sentinel`:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Next, let's write a passing test case. This will test that the policy tests
+when we expect it to pass. Save the following in `test/policy/good.hcl`:
+
+```hcl
+global "day" {
+ value = "monday"
+}
+
+global "hour" {
+ value = 14
+}
+```
+
+And run `sentinel test`:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/good.json
+```
+
+The `sentinel test` command will automatically find all Sentinel policies
+and their associated test cases and run them all. The test framework will
+run all HCL files as individual test cases, allowing you to test a variety
+of scenarios for your policies.
+
+Try adding another test case to force the policy to fail. This test case can
+be saved at `test/policy/fail.hcl`. For test cases with intentional
+failures, you'll need to use the [test assertions described in the testing
+reference](/sentinel/writing/testing#a-failing-test).
+
+Now that you have a good grasp of what Sentinel is and how to use it, please feel
+free to look through the
+[next steps](/sentinel/intro/getting-started/next-steps)
+you can take to further your Sentinel knowledge.
diff --git a/content/sentinel/v0.24.x/content/sentinel/intro/index.mdx b/content/sentinel/v0.24.x/content/sentinel/intro/index.mdx
new file mode 100644
index 0000000000..785a442fb8
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/intro/index.mdx
@@ -0,0 +1,32 @@
+---
+layout: intro
+page_title: Documentation
+sidebar_current: docs-home
+description: >-
+ Sentinel is a language and framework for policy built to be embedded in
+ existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+[introduction](/sentinel/intro) to understand what Sentinel is, how it
+compares to other software, and more.
+
+
diff --git a/content/sentinel/v0.24.x/content/sentinel/intro/what.mdx b/content/sentinel/v0.24.x/content/sentinel/intro/what.mdx
new file mode 100644
index 0000000000..36853d366c
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/intro/what.mdx
@@ -0,0 +1,64 @@
+---
+page_title: Introduction
+sidebar_current: intro-what
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: intro
+---
+
+# Introduction to Sentinel
+
+Welcome to the intro guide to Sentinel! This guide is the best
+place to start with Sentinel.
+
+If you are already familiar with the basics of Sentinel, the
+[documentation](/sentinel) provides a better reference
+guide for all available features as well as internals.
+
+## What is Sentinel?
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+Sentinel provides a language for writing policy and a workflow for developing
+and testing policies independent of the software they'll be written to. We
+also provide a framework for developers to build plugins that allow a
+Sentinel-enabled system to access external information to make policy
+decisions.
+
+Most systems today have some degree of access control. You are able to define
+identities and what they have access to. These ACL systems solve an immediate
+and necessary problem of locking down a system in very broad strokes. Sentinel
+is a reusable system for more advanced software policy decisions. Sentinel
+enables:
+
+- **Fine-Grained Policy**: Most ACL systems only enable coarse-grained
+ behaviors: "read", "write", etc. Sentinel enables fine-grained behavior such
+ as disallowing a certain API call when specific parameters are present.
+
+- **Logic-Based Policy**: You can write policy using full
+ conditional logic. For example, you may only allow a certain application behavior
+ on Monday to Thursday unless there is a manager override.
+
+- **Accessing External Information**: Sentinel can source external information
+ to be used in policy decisions. For example, a policy that restricts the size
+ of a payload may read data from [Consul](https://www.consul.io) to determine
+ the payload size limit.
+
+- **Enforcement levels**: Sentinel allows policies to be defined along
+ with an "enforcement level" that dictates the pass/fail behavior of a policy.
+ Advisory policies warn if they fail, soft mandatory policies can have their
+ failures overridden, and hard mandatory policies must pass under all
+ circumstances. Having this as a built-in concept enables you to model
+ policy more accurately and completely for your organization.
+
+## Next Steps
+
+See the page on [Why Sentinel?](/sentinel/intro/why) to learn more
+about the origins of the product. Then, continue onwards with
+the [getting started guide](/sentinel/intro/getting-started/install) to write
+your first policies and see how Sentinel works in practice.
diff --git a/content/sentinel/v0.24.x/content/sentinel/intro/why.mdx b/content/sentinel/v0.24.x/content/sentinel/intro/why.mdx
new file mode 100644
index 0000000000..98b58aab92
--- /dev/null
+++ b/content/sentinel/v0.24.x/content/sentinel/intro/why.mdx
@@ -0,0 +1,49 @@
+---
+page_title: Why Sentinel?
+sidebar_current: intro-why
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: intro
+---
+
+# Why Sentinel?
+
+The growth of infrastructure and applications has been enabled in part
+by an increasing trend towards automation everywhere.
+Configuration as code (such as Chef or Puppet with [Packer](https://www.packer.io))
+has enabled automated machine configuration. Infrastructure
+as code (such as [Terraform](https://www.terraform.io)) has enabled
+automatic infrastructure creation. And schedulers (such as
+[Nomad](https://www.nomadproject.io) and Kubernetes) have enabled
+automatic application deployment.
+Sentinel enables guardrails to be put in place
+on automation while allowing the codification and automatic enforcement
+of business requirements in critical areas of your infrastructure.
+
+Meanwhile, businesses have business requirements and sometimes legal
+requirements which must be expressed in policies. Traditionally, these
+policies are enforced by humans. But in a highly automated world, the
+automation is only as fast as its slowest component. In many cases, this
+is the human verification step.
+
+As an example: before infrastructure as code and autoscaling, if an order
+came through for 5,000 new machines, a human would likely respond to the
+ticket verifying that the user really intended to order 5,000 new machines.
+Today, automation can almost always freely order 5,000 new compute instances
+without any hesitation, which can result in unintended expense or system
+instability.
+
+Sentinel introduces [policy as code](/sentinel/concepts/policy-as-code)
+and a powerful framework built-in to HashiCorp tooling to allow automation
+guardrails, business requirements, legal compliance, and more to be
+actively enforced by the running systems in realtime.
+
+With Sentinel, you can require an override to create certain numbers of
+infrastructure resources. You can disallow unsafe deployment configurations
+with Nomad. You can enforce certain key/value formats in Consul. You
+can restrict secret access by time in Vault. And more.
+
+Sentinel is available today in HashiCorp enterprise products. You can
+try Sentinel immediately with the [getting started guide](/sentinel/intro/getting-started/install) or learn more about the integrations in the
+[documentation](/sentinel).
diff --git a/content/sentinel/v0.24.x/data/docs-nav-data.json b/content/sentinel/v0.24.x/data/docs-nav-data.json
new file mode 100644
index 0000000000..0bfc027b10
--- /dev/null
+++ b/content/sentinel/v0.24.x/data/docs-nav-data.json
@@ -0,0 +1,366 @@
+[
+ {
+ "title": "Release Notes",
+ "path": "changelog"
+ },
+ {
+ "title": "Basic Concepts",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "concepts"
+ },
+ {
+ "title": "Policy as Code",
+ "path": "concepts/policy-as-code"
+ },
+ {
+ "title": "Policy Language",
+ "path": "concepts/language"
+ },
+ {
+ "title": "Imports",
+ "path": "concepts/imports"
+ },
+ {
+ "title": "Enforcement Levels",
+ "path": "concepts/enforcement-levels"
+ }
+ ]
+ },
+ {
+ "title": "Configuration File Syntax",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "configuration"
+ },
+ {
+ "title": "Override Files",
+ "path": "configuration/overrides"
+ },
+ {
+ "title": "Remote Sources",
+ "path": "configuration/remote-sources"
+ }
+ ]
+ },
+ {
+ "title": "Commands (CLI)",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "commands"
+ },
+ {
+ "title": "apply",
+ "path": "commands/apply"
+ },
+ {
+ "title": "fmt",
+ "path": "commands/fmt"
+ },
+ {
+ "title": "test",
+ "path": "commands/test"
+ }
+ ]
+ },
+ {
+ "title": "Writing Policy",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "writing"
+ },
+ {
+ "title": "Basics",
+ "path": "writing/basic"
+ },
+ {
+ "title": "Rules",
+ "path": "writing/rules"
+ },
+ {
+ "title": "Traces",
+ "path": "writing/tracing"
+ },
+ {
+ "title": "Testing",
+ "path": "writing/testing"
+ },
+ {
+ "title": "Imports",
+ "path": "writing/imports"
+ },
+ {
+ "title": "Debugging",
+ "path": "writing/debugging"
+ }
+ ]
+ },
+ {
+ "title": "Extending Sentinel",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "extending"
+ },
+ {
+ "title": "Modules",
+ "path": "extending/modules"
+ },
+ {
+ "title": "Plugins",
+ "path": "extending/plugins"
+ },
+ {
+ "title": "Static Imports",
+ "path": "extending/static-imports"
+ },
+ {
+ "title": "Internals",
+ "path": "extending/internals"
+ }
+ ]
+ },
+ {
+ "title": "Features",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "features"
+ },
+ {
+ "title": "terraform",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "features/terraform"
+ },
+ {
+ "title": "tfplan/v1",
+ "path": "features/terraform/tfplan-v1"
+ },
+ {
+ "title": "tfplan/v2",
+ "path": "features/terraform/tfplan-v2"
+ },
+ {
+ "title": "tfconfig/v1",
+ "path": "features/terraform/tfconfig-v1"
+ },
+ {
+ "title": "tfconfig/v2",
+ "path": "features/terraform/tfconfig-v2"
+ },
+ {
+ "title": "tfstate/v1",
+ "path": "features/terraform/tfstate-v1"
+ },
+ {
+ "title": "tfstate/v2",
+ "path": "features/terraform/tfstate-v2"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Language",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "language"
+ },
+ {
+ "title": "Variables",
+ "path": "language/variables"
+ },
+ {
+ "title": "Values",
+ "path": "language/values"
+ },
+ {
+ "title": "Lists",
+ "path": "language/lists"
+ },
+ {
+ "title": "Maps",
+ "path": "language/maps"
+ },
+ {
+ "title": "Rules",
+ "path": "language/rules"
+ },
+ {
+ "title": "Imports",
+ "path": "language/imports"
+ },
+ {
+ "title": "Parameters",
+ "path": "language/parameters"
+ },
+ {
+ "title": "Boolean Expressions",
+ "path": "language/boolexpr"
+ },
+ {
+ "title": "Arithmetic",
+ "path": "language/arithmetic"
+ },
+ {
+ "title": "Slices",
+ "path": "language/slices"
+ },
+ {
+ "title": "Conditionals",
+ "path": "language/conditionals"
+ },
+ {
+ "title": "Loops",
+ "path": "language/loops"
+ },
+ {
+ "title": "Collection Operations",
+ "path": "language/collection-operations"
+ },
+ {
+ "title": "Functions",
+ "path": "language/functions"
+ },
+ {
+ "title": "Scope",
+ "path": "language/scope"
+ },
+ {
+ "title": "Undefined",
+ "path": "language/undefined"
+ },
+ {
+ "title": "Logging and Errors",
+ "path": "language/logging-errors"
+ },
+ {
+ "title": "Specification",
+ "path": "language/spec"
+ }
+ ]
+ },
+ {
+ "title": "Builtin Functions",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "functions"
+ },
+ {
+ "title": "append",
+ "path": "functions/append"
+ },
+ {
+ "title": "delete",
+ "path": "functions/delete"
+ },
+ {
+ "title": "error",
+ "path": "functions/error"
+ },
+ {
+ "title": "keys",
+ "path": "functions/keys"
+ },
+ {
+ "title": "length",
+ "path": "functions/length"
+ },
+ {
+ "title": "print",
+ "path": "functions/print"
+ },
+ {
+ "title": "range",
+ "path": "functions/range"
+ },
+ {
+ "title": "values",
+ "path": "functions/values"
+ }
+ ]
+ },
+ {
+ "title": "Standard Imports",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "imports"
+ },
+ {
+ "title": "base64",
+ "path": "imports/base64"
+ },
+ {
+ "title": "decimal",
+ "path": "imports/decimal"
+ },
+ {
+ "title": "http",
+ "path": "imports/http"
+ },
+ {
+ "title": "json",
+ "path": "imports/json"
+ },
+ {
+ "title": "runtime",
+ "path": "imports/runtime"
+ },
+ {
+ "title": "sockaddr",
+ "path": "imports/sockaddr"
+ },
+ {
+ "title": "strings",
+ "path": "imports/strings"
+ },
+ {
+ "title": "time",
+ "path": "imports/time"
+ },
+ {
+ "title": "types",
+ "path": "imports/types"
+ },
+ {
+ "title": "units",
+ "path": "imports/units"
+ },
+ {
+ "title": "version",
+ "path": "imports/version"
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Consul",
+ "path": "consul"
+ },
+ {
+ "title": "Nomad",
+ "path": "nomad"
+ },
+ {
+ "title": "Terraform",
+ "path": "terraform"
+ },
+ {
+ "title": "Vault",
+ "path": "vault"
+ }
+]
diff --git a/content/sentinel/v0.24.x/data/intro-nav-data.json b/content/sentinel/v0.24.x/data/intro-nav-data.json
new file mode 100644
index 0000000000..c95181af70
--- /dev/null
+++ b/content/sentinel/v0.24.x/data/intro-nav-data.json
@@ -0,0 +1,47 @@
+[
+ {
+ "title": "What is Sentinel?",
+ "path": "what"
+ },
+ {
+ "title": "Why Sentinel?",
+ "path": "why"
+ },
+ {
+ "title": "Getting Started",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "getting-started"
+ },
+ {
+ "title": "Installing the CLI",
+ "path": "getting-started/install"
+ },
+ {
+ "title": "Your First Policy",
+ "path": "getting-started/first-policy"
+ },
+ {
+ "title": "Rules",
+ "path": "getting-started/rules"
+ },
+ {
+ "title": "Logic",
+ "path": "getting-started/logic"
+ },
+ {
+ "title": "Imports",
+ "path": "getting-started/imports"
+ },
+ {
+ "title": "Testing",
+ "path": "getting-started/testing"
+ },
+ {
+ "title": "Next Steps",
+ "path": "getting-started/next-steps"
+ }
+ ]
+ }
+]
diff --git a/content/sentinel/v0.24.x/img/sentinel-import-topology.svg b/content/sentinel/v0.24.x/img/sentinel-import-topology.svg
new file mode 100644
index 0000000000..36f7c79bc9
--- /dev/null
+++ b/content/sentinel/v0.24.x/img/sentinel-import-topology.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/content/sentinel/v0.24.x/redirects.jsonc b/content/sentinel/v0.24.x/redirects.jsonc
new file mode 100644
index 0000000000..d9e73f4a49
--- /dev/null
+++ b/content/sentinel/v0.24.x/redirects.jsonc
@@ -0,0 +1,15 @@
+[
+ {
+ "source": "/",
+ "destination": "/sentinel",
+ "permanent": true,
+ },
+ {
+ "source": "/sentinel/commands/config",
+ "destination": "/sentinel/configuration",
+ "permanent": true,
+ },
+ // disallow ".html" or "/index.html" in favor of cleaner, simpler paths
+ { "source": "/:path*/index", "destination": "/:path*", "permanent": true },
+ { "source": "/:path*.html", "destination": "/:path*", "permanent": true },
+]
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/changelog.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/changelog.mdx
new file mode 100644
index 0000000000..f2cd468309
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/changelog.mdx
@@ -0,0 +1,861 @@
+---
+page_title: Sentinel Runtime Release Notes
+sidebar_current: docs-changelog
+description: >-
+ These are the public release notes for the Sentinel runtime. See this document
+ for the latest updates.
+layout: docs
+---
+
+# Sentinel Runtime Release Notes
+
+These are the release notes for the Sentinel runtime.
+
+Each version of the runtime is released with a corresponding version of
+[Sentinel CLI](/sentinel/commands). To download the CLI, see the [install
+page](/sentinel/install).
+
+Sentinel integrations and embedded runtimes may not always have the latest
+version installed, depending on the product's individual release cycle. For more
+information, contact the support team for your specific integration.
+
+
+## 0.25.1 (Apr 18, 2024)
+
+BREAKING CHANGES:
+
+- `imports/http`: The default `Accept` header has been changed to `*/*`, removing a redundant
+extension that some servers may not accept by default.
+
+## 0.25.0 (Apr 8, 2024)
+
+ENHANCEMENTS:
+
+- `cmd/apply`: JSON results will return an additional `duration` field for individual policies as well as the evaluation as a whole.
+- `cmd/test`: `sentinel test` will return an additional `duration` field in the JSON output.
+
+BUG FIXES:
+
+- `cmd`: Removed redundant debug logging for custom plugins.
+
+## 0.24.4 (Mar 21, 2024)
+
+ENHANCEMENTS:
+
+- `runtime/format`: Null values will now print correctly for rule value outputs.
+- `config`: Some internal changes to configuration parsing workflow.
+
+## 0.24.3 (Feb 9, 2024)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.24.2 (Jan 31, 2024)
+
+BUG FIXES:
+
+- `imports/static`: Fixed an issue where `nil` values provided to the built-in
+static import were being treated as `undefined` in policy.
+
+## 0.24.1 (Jan 19, 2024)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where the Sentinel cache was unstable due to concurrent
+tests.
+
+## 0.24.0 (Dec 7, 2023)
+
+ENHANCEMENTS:
+
+- `cmd/apply`, `cmd/test`: Custom import plugins can now be fetched from remote
+sources.
+- `cmd/apply`, `cmd/test`: Static imports can now be fetched from remote
+sources.
+- `features/terraform`: Added support for `resource_drift` in the `tfplan/v2` import.
+
+## 0.23.1 (Oct 19, 2023)
+
+BUG FIXES:
+
+- `cmd/apply`: Warnings and errors are included in the JSON output if the json flag is enabled.
+
+## 0.23.0 (Sept 5, 2023)
+
+FEATURES:
+
+- `features/apply_all`: Adds the `apply-all` feature, allowing for all policies to evaluate regardless of result, instead of exiting on first failure.
+
+BUG FIXES:
+
+- `features/terraform`: The `tfplan/v1` import correctly handles nested attribute schemas.
+
+## 0.22.1 (June 22, 2023)
+
+BUG FIXES:
+
+- `sentinel/eval`: Under certain conditions, per-policy parameters would cause
+ Sentinel to panic. This has been resolved.
+
+## 0.22.0 (May 31, 2023)
+
+- `config`: Configuration now supports a `sentinel` block to manage the
+ Sentinel runtime.
+
+## 0.21.1 (May 8, 2023)
+
+BUG FIXES:
+
+- `config`: An issue with certain identifiers being treated as incorrectly invalid
+ has been resolved.
+
+## 0.21.0 (March 8, 2023)
+
+BREAKING CHANGES:
+
+- `lang/ast`: `is empty` and `is not empty` are now treated as `ast.UnaryExpr`
+ expressions, with `ast.IsEmptyExpr` being removed.
+- `lang/ast`: You can now assert if a value is defined or not using the `is defined`
+ and `is not defined` syntax.
+
+FEATURES:
+
+- `config`: Parameter values can now be provided for individual policies within
+ a policy block.
+
+## 0.20.0 (February 16, 2023)
+
+ENHANCEMENTS:
+
+- `cmd/testcmd`: Allows sentinel tests to run concurrently by default. Sequential style testing can
+ be enabled by running with the `-maxConcurrency=1` option.
+- `cmd/testcmd`: Allows sentinel test command to timeout after a certain duration. This can be provided
+ by the user or will default to 5 minutes.
+- `cmd/apply`: The policy enforcement level is now included in the JSON output.
+- `lang/ast`: Functions can now be declared as named statements, providing
+ a safer function declaration.
+
+BREAKING CHANGES:
+
+- `cmd/apply`: Policies provided directly to the apply command will now default their enforcement
+ level to `advisory`, aligning with the `policy` configuration block.
+- `sentinel`: JSON results will no longer return `allowed_failure` or `can_override` fields.
+- `sentinel/result`: A new package has been added which provides additional methods to return
+ supplemental data about the evaluation result.
+
+## 0.19.5 (February 9, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `SelectorExpr` will correctly rewrite `X`.
+
+## 0.19.4 (February 8, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `Compile` function will correctly set the `Original`
+ field when rewriting a `CallExpr` as an `ImportExpr`.
+
+## 0.19.3 (February 3, 2023)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.19.2 (January 17, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `Compile` function will no longer modify `ast.CallExpr`
+ arguments in place.
+
+## 0.19.1 (December 14, 2022)
+
+BUG FIXES:
+
+- `lang/ast`: The `Rewrite` function will no longer modify the provided Node
+ in place.
+
+ENHANCEMENTS:
+
+- `lang/semantic`: The Break semantic check uses the AST walker.
+
+## 0.19.0 (December 6, 2022)
+
+BREAKING CHANGES:
+
+- `config`: Attempts to override a standard import with a custom import will
+ now be met with an error.
+- `imports`: All plugin imports now return `sdk.Plugin` instead of `sdk.Import`,
+ as per the SDK update to v0.4.0.
+
+ENHANCEMENTS:
+
+- `runtime/importer`: An `Import` and `ImportInstance` interface have been
+ added to improve the import capabilities.
+- `runtime/importer`: The `Importer` interface now returns a `Import` interface
+ instead of a `sdk.Import`.
+- `lang/semantic`: The Import Assignment semantic check uses the AST walker.
+- `lang/ast`: Added an AST Walker so the AST rewriter does not need to be used for basic AST tasks.
+
+FEATURES:
+
+- `config`: Configuration syntax now allows for configuration of imports within
+ the standard library.
+- `config`: A new configuration syntax for defining modules and plugins is now
+ available.
+- Static data can now be added via `import` configuration.
+- `cmd/apply`: The `apply` command now supports multiple primary configuration
+ files by default, loading all configuration files within the working
+ directory.
+- `cmd/apply`: The `apply` command now accepts applying configuration overrides
+ to primary configuration.
+
+## 0.18.13 (October 31, 2022)
+
+BUG FIXES:
+
+- `cmd/apply`: `sentinel apply` no longer aborts on advisory policy failure.
+
+## 0.18.12 (September 15, 2022)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where nested `CallExpr` were not rewritten.
+
+## 0.18.11 (June 8, 2022)
+
+NOTES:
+
+- `config`: The `enforcement_level` key for policies is now optional, defaulting to `"advisory"` when not supplied.
+
+## 0.18.10 (May 20, 2022)
+
+NOTES:
+
+- `runtime/initwd`: Improvements to the installer, including timeouts and filesize limits, as well as disabling support
+ for symlinks within `git` or `hg` repositories.
+
+## 0.18.9 (March 23, 2022)
+
+NOTES:
+
+- This release fixes an issue with the Docker container build pipeline. There are no changes to the
+Sentinel runtime or CLI.
+
+## 0.18.8 (March 22, 2022)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue in the installer for Windows paths.
+
+## 0.18.7 (February 24, 2022)
+
+NOTES:
+
+- **Darwin arm64**. This release will be our first to include Darwin arm64 architecture.
+
+BUG FIXES:
+
+- `lang/ast`: Fixed issues where `IndexSpec` and `RuleLit` nodes where returning incorrect end positions.
+- `lang/ast`: Fixed an issue where `ParamSpec` nodes where returning incorrect end positions.
+
+## 0.18.6 (February 2, 2022)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.18.5 (January 14, 2022)
+
+IMPROVEMENTS:
+
+- `runtime/initwd`: Changed the sum algorithm used during remote installation from `md5` to `sha256` to reduce chance of collision.
+
+## 0.18.4 (July 20, 2021)
+
+FEATURES:
+
+- `imports/static`: Improved handling of struct tags when performing encoding, allowing `-` to mark as ignored.
+
+## 0.18.3 (June 1, 2021)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue for remote source installation where duplicate sub-directories resulted in incorrect installation.
+- `imports/static`: Fixed an issue where global configuration that contained an empty map could not be accessed without raising selector issues.
+
+## 0.18.2 (May 18, 2021)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where import expressions inside lists and maps were not being evaluated correctly.
+
+## 0.18.1 (May 11, 2021)
+
+BUG FIXES:
+
+- `imports/json`: Fixed an issue where empty maps where failing when passed at any level to `marshal`.
+
+## 0.18.0 (March 25, 2021)
+
+FEATURES:
+
+- `imports/http`: Added support for POST actions within the `http` import
+ through addition of the `post` function. Additionally, support for adding
+ a body to the `request` object has been added via `with_body`.
+
+## 0.17.4 (February 2, 2021)
+
+BUG FIXES:
+
+- `runtime/format`: Fixed a nesting issue with the rule printer where nesting
+ state leaks were leading to all collection values eventually being truncated,
+ regardless of their nesting level.
+
+## 0.17.3 (January 15, 2021)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue where local file installation failed on Windows
+ when using `sentinel test`.
+
+## 0.17.2 (January 13, 2021)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where duplicate log messages were being printed with
+ `sentinel test`.
+
+## 0.17.1 (January 7, 2021)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where hard-coded path separator caused `sentinel
+ test` issues in Windows.
+
+## 0.17.0 (January 5, 2021)
+
+LANGUAGE CHANGES:
+
+- **Map Expression** `map`. A new quantifier expression, `map` has been added.
+ `map` takes a collection and applies an operation to each element in the
+ collection, returning a list of results with the resulting values.
+- **Emptiness checks** `is empty` and `is not empty`. Two new expressions,
+ `is empty` and `is not empty` have been added. As they are aliases to the
+ `length` built-in, only `string`, `map`, `list` or `undefined` is
+ supported.
+- **Comparable maps** Maps (the data type) are now comparable. Maps are equal if
+ they are of equal length and both their corresponding keys and values are
+ comparable and equal.
+- **Rich Return Types** Rules can now process and return non-boolean data.
+ Scalar, list, and map types are supported. When non-boolean values are used,
+ the presence of a non-zero or non-zero-length `main` rule determines policy
+ failure. More details can be found on
+ https://docs.hashicorp.com/sentinel/language#main.
+
+FEATURES:
+
+- **CLI**: `sentinel apply` and `sentinel test` now have options for JSON
+ output. For more details, see the CLI documentation.
+- `imports/base64`: Added a new import, `base64`, to assist with encoding and
+ decoding base64 strings.
+
+## 0.16.1 (October 21, 2020)
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where `contains` to ensure any instance of `undefined` would correctly result in `undefined`.
+- `runtime/eval`: Fixed an issue in `any` and `all` expressions to ensure correct boolean logic when dealing with a collection containing `undefined`.
+- `imports`: Fixed an issue where import processes were not killed, which caused non-graceful closures for the clients.
+- `lang/scanner`: Fixed an issue where inline `#` style comments were not being consumed.
+
+## 0.16.0 (October 14, 2020)
+
+BREAKING CHANGES:
+
+- `cmd/doc`: Removal of the deprecated `sentinel doc` command.
+- `sentinel`: Replace `whitelist` and `blacklist` with `allowlist` and `denylist`, as per inclusive language changes.
+
+FEATURES:
+
+- `imports/version`: Added a new import, `version`, which supplies helpers for dealing with semantic versioning.
+- `cmd/apply`: Added an `HCL` based configuration file, which will eventually deprecate the legacy `JSON` configuration.
+- `cmd/apply`: Added support for download remote policies and modules.
+- `cmd/test`: Added support for downloading remote test modules.
+- `cmd/apply`: Added support for evaluating a policy based on it's key within the configuration.
+- `cmd/apply`: Added support for running all policies in a configuration by default.
+
+## 0.15.6 (July 7, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.15.5 (May 20, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel integrations. There are no user-facing changes.
+
+## 0.15.4 (May 13, 2020)
+
+BUG FIXES:
+
+- `imports`: Fixed an issue with the standard imports that was affecting the handling of null data within lists.
+
+## 0.15.3 (April 16, 2020)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where `IndexExpr` were not rewritten, as well as ensuring `SelectorExpr` uses any nested rewrites.
+- `lang/printer`: Fixed issue printing deeply nested structures and/or loops.
+
+IMPROVEMENTS:
+
+- `imports/decimal`: Added alias methods to improve consistency of method names within the `decimal` import. The alias methods are `is_nan` for `isnan`, as well as both `is_inf` and `is_infinite` for `isinfinite`.
+- `command/apply`: `sentinel apply` will now output the policy description when a trace is forced via `-trace`.
+- `sentinel`: Improved `String` output formatting of Policy docstring for `EvalPolicyResult`.
+
+## 0.15.2 (April 2, 2020)
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue with the `printer` that was causing a panic
+ when a `#` line comment was used with no text following it.
+- `imports/types`: Fixed an issue with the `types` import where calls to
+ `type_of` for undefined types were incorrectly returning as map types. These
+ will now be correctly be identified as undefined as expected.
+
+## 0.15.1 (March 6, 2020)
+
+BUG FIXES:
+
+- `sentinel`: Fixed how modules are managed internally in the runtime to correct
+ race conditions in concurrent scenarios and to allow for per-evaluation module
+ restrictions. This functionality is only of interest to embedded applications
+ with long-lived runtimes and is currently not implemented in any HashiCorp
+ integration.
+
+## 0.15.0 (March 5, 2020)
+
+NOTES:
+
+- **Windows Digital Signature**. Our windows releases for this version and up will be signed
+ and verified according to Microsoft's requirements.
+
+FEATURES:
+
+- `cmd/config`: Modules can now be loaded off of local disk using the Sentinel
+ CLI. For information on how to do so, see the Sentinel documentation.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: Changed modules so that they now have singleton scope when
+ loaded. Importing a module under two different aliases, or within a nested
+ module, will now share scopes - modification of state in one will alter the
+ other.
+- `lang/object`: Introduced an interface to allow the duplication of various
+ types, like collections.
+- `runtime/eval`: Changed how collection data is returned from modules. This
+ data is now duplicated to prevent accidental or deliberate circumvention of
+ the prohibition on assignment to import data, and brings behavior to parity
+ with binary imports.
+
+## 0.14.4 (February 6, 2020)
+
+IMPROVEMENTS:
+
+- `imports/time`: Changed the `add` timespace function in the `time` import to
+ allow it to take float types as durations. These values will be truncated to
+ the appropriate integer-value duration.
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where out-of-place right-hand braces were being
+ parsed as empty statements, instead of raising syntax errors.
+
+## 0.14.3 (January 22, 2020)
+
+IMPROVEMENTS:
+
+- `cmd/test`: Fail when an assertion is not found in a trace.
+- `cmd/test`: Added a message to notify when no rules were evaluated in a test.
+
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue where import aliases (the `as baz` in `import
+ "foo/bar" as baz`) were being removed when formatting with `sentinel fmt`.
+
+- `imports/http`: Fixed an issue with the http import where the client library's
+ debug logs were being printed to standard error.
+
+## 0.14.2 (January 15, 2020)
+
+NOTES:
+
+With this release, we are discontinuing support for MacOS 32-bit. 64-bit builds
+are now the only builds available for MacOS.
+
+IMPROVEMENTS:
+
+- `lang/printer`: A newline is now added to files formatted via `sentinel fmt`.
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue in printing where multi-line expressions would
+ indent infinitely. They will now only indent once at the most. This is mostly
+ seen when using `sentinel fmt`.
+- `runtime/encoding`: Fixed an issue where if a plugin returned a deeply
+ embedded undefined value, the value was instead decoded into a map.
+
+## 0.14.1 (January 6, 2020)
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where reserved words could not be used as
+ selectors when the selector expression was the last lexeme on a line.
+
+## 0.14.0 (December 18, 2019)
+
+LANGUAGE CHANGES:
+
+- **Filter Expression** `filter`. A new quantifier expression, `filter` has been added.
+ `filter` returns a subset of the provided collection based on a boolean condition that
+ is asserted for each element.
+
+NOTES:
+
+- **Apple Notarization**. Our darwin releases for this version and up will be signed
+ and notarized according to Apple's requirements.
+
+## 0.13.1 (November 25, 2019)
+
+BUG FIXES:
+
+- `runtime/eval`: Parameters are no longer allowed in mock files. Adding one to
+ a mock will result in a runtime error.
+- `runtime/eval`: Fixed an issue where import calls from within mocks were
+ failing under certain circumstances.
+- `imports/decimal`: `int` should now correctly provide the truncated integer
+ representation of a decimal number, not the rounded one.
+
+## 0.13.0 (November 15, 2019)
+
+LANGUAGE CHANGES:
+
+- **Policy Parameters**. The new [policy
+ parameters](https://docs.hashicorp.com/sentinel/language/parameters) feature
+ allows authors to use a `param` declaration at the top of a policy to supply
+ values that are expected to come from outside of a policy. See the linked
+ documentation for more details.
+
+FEATURES:
+
+- `imports/http`: The [`http`
+ import](https://docs.hashicorp.com/sentinel/imports/http) is a new addition
+ to the standard library enabling the use of HTTP-accessible data from outside
+ the runtime in policy rules.
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where loading other imports before a mocked
+ import would cause those imports to no longer be visible from within the
+ policy.
+
+## 0.12.0 (October 7, 2019)
+
+LANGUAGE CHANGES:
+
+- **New `case` statement**. This statement is a selection control mechanism to
+ conditionally execute a branch based on expression equality, allowing
+ simplification of complex conditional chains that may otherwise need to be
+ written with `else if`. See the [Case
+ Statements](https://docs.hashicorp.com/sentinel/language/spec#case-statements)
+ section of the Sentinel language specification for more details.
+
+IMPROVEMENTS:
+
+- `lang/semantic`: Added a semantic check to ensure usage of append is not using
+ a return value.
+
+BUG FIXES:
+
+- `runtime/eval`: Corrected an issue where calling a method on an import object
+ value that was the result of a method call on another import object value
+ would have erroneously tried to call an import of the name of the "parent"
+ import object value. Example: in `a = subject.new(); b = a.call(); c =
+ b.call()`, `b.call()` would attempt to call a method named `call` on the root
+ namespace for an import named `a`. This has now been corrected so that
+ `b.call()` will now correctly call the `call` method for the respective object
+ namespace residing in the import named `subject`.
+- `imports`: Some standard imports may have been returning null for some unknown
+ keys in objects when they should have been returning undefined. This was due
+ to an SDK issue which was corrected in SDK version 0.3.2, which has now been
+ corrected.
+- `cmd/test`: `sentinel test` will now correctly fail a policy if it encounters
+ an error.
+- `cmd/test`: `sentinel test` will now correctly display errors and other output
+ that were missing due to a formatting issue.
+- `runtime/eval`: The `append` builtin now correctly returns undefined for all
+ calls, as called for by the Specification. Note that in most cases, the
+ semantic check outlined above will trigger an error if the return value is
+ used.
+
+## 0.11.0 (September 5, 2019)
+
+LANGUAGE CHANGES:
+
+- **New builtin function:** `range()`. This function existed in the spec in
+ earlier versions but was removed as it lacked an implementation. This has now
+ been implemented and re-added to the spec. See the
+ [Range](https://docs.hashicorp.com/sentinel/language/spec#range) section of
+ the Sentinel Specification for more details.
+- Lists are now comparable. Lists are equal if their corresponding elements are
+ comparable and equal.
+- Method calls on values returned by imports are now supported. See the
+ [Imports](https://docs.hashicorp.com/sentinel/language/spec#imports) section
+ of the Sentinel Specification for more details.
+
+FEATURES:
+
+- `imports/decimal`: This is a new import designed to do exact precision
+ mathematical calculations.
+- `runtime/eval`: Compound call expressions that refer to imports (example:
+ foo.bar().baz() when `foo` is a loaded import) will now function as expected.
+ Previously, this was only supported up to the first call (example:
+ `foo.bar()`).
+- `imports/time`: Timespaces returned by calls such as `time.now` are now
+ callable. Example: `t = time.now; t.after(some_previous_time)` will now
+ function.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: The implementation of comparison of non-comparable types has
+ now changed. Rather than triggering a runtime error, non-comparable types will
+ now return false when attempting to compare.
+
+## 0.10.4 (August 15, 2019)
+
+BUG FIXES:
+
+- `runtime/encoding`: Fixed an issue with conversion of null values that could
+ lead to crashes in imports.
+
+## 0.10.3 (July 15, 2019)
+
+BUG FIXES:
+
+- `command/config`: Parsing a configuration file where malformed data is
+ encountered after apparently properly-formed data will now report an error. An
+ example would be a situation where misplaced braces would cause a JSON object
+ to only parse part of the configuration file.
+
+## 0.10.2 (June 25, 2019)
+
+BUG FIXES:
+
+- `lang/parser`: Corrected an issue where parsing certain compound binary
+ expressions where the negation predicate (`not`) was in use would cause the
+ negation to have no effect. Example: `foo else "bar" not in "baz"` would have
+ been parsed and evaluated as `foo else "bar" in "baz"`, effectively producing
+ the opposite result.
+
+## 0.10.1 (May 9, 2019)
+
+BUG FIXES:
+
+- `command/test`: `sentinel test` now displays policies without
+tests as an unknown result with [no test files], instead of the somewhat
+erroneous behavior of displaying it as a PASS. A full test run with a mixture
+of passing tests and no tests still results in an overall successful result.
+
+## 0.10.0 (April 18, 2019)
+
+LANGUAGE CHANGES:
+
+- Mixed-number arithmetic operations are now allowed. Addition (`+`),
+ subtraction (`-`), multiplication (`*`), division (`/`), and remainder
+ operations (`%`) are no longer restricted to number values of the same
+ type, and can mix integer and floating point. The result of these operations
+ is always a floating-point number.
+- Remainder (modulo, `%`) operations are now allowed on floating-point numbers.
+
+
+BUG FIXES:
+
+- `lang/parser`: Fixed a bug that affected the use of `not` with `contains`,
+ `matches`, or `in` in a compound expression.
+- `runtime/eval`: Fixed error messages on evaluation errors with `contains` and
+ `in` to indicate that strings are an allowed type along with lists and maps.
+
+
+## 0.9.2 (March 15, 2019)
+
+This is a dependency update related to the changes mentioned in 0.9.1. No other
+changes have been made.
+
+## 0.9.1 (March 14, 2019)
+
+This is a patch release that is required to integrate with the latest versions
+of the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). No other
+changes have been made.
+
+## 0.9.0 (January 28, 2019)
+
+FEATURES:
+
+- `imports/strings`: Added a `join` function to the `strings` import. This can
+ be used to join a list into a string with a specific separator.
+ Multi-dimensional lists and all Sentinel primitives are supported.
+
+## 0.8.1 (January 17, 2019)
+
+BUG FIXES:
+
+- `lang/eval`: Fixed a bug that prevents the effective use of `return`
+ statements in `for` loops.
+
+## 0.8.0 (January 14, 2019)
+
+FEATURES:
+
+- Mocks can now be represented by Sentinel code. This allows for the mocking of
+ functions and other complex data structures that cannot be represented in
+ JSON. For more information on using this feature via mocks, click
+ [here](https://docs.hashicorp.com/sentinel/commands/config#mocking-with-sentinel-code).
+
+
+IMPROVEMENTS:
+
+- Import validation has now been moved to the semantic checking phase. This
+ should result in better reporting of validation errors. In addition, import
+ validation will now enforce the use of an `as` identifier when an import path
+ is not a valid identifier on its own (example: `import "foo/bar"`).
+
+## 0.7.0 (December 12, 2018)
+
+BUG FIXES:
+
+- There have been changes to the runtime in how scope is handled over multiple
+ policy executions. Scope is now correctly unique per single policy execution,
+ and values set or builtins that are overridden in one policy will no longer
+ affect those values within another.
+
+## 0.6.0 (November 30, 2018)
+
+FEATURES:
+
+- `imports/runtime`: This new import allows for one to check various aspects of
+ the Sentinel runtime as it may be embedded in the simulator or a specific
+ implementation. For now, it allows the version to be checked.
+
+## 0.5.1 (November 28, 2018)
+
+IMPROVEMENTS:
+
+- `imports/time`: Added the `zone` and `zone_string` attributes to assist with
+ validation of a timespace's zone.
+- `command/fmt`: Added a new -check flag. This option does not commit changes,
+ but instead checks to see what files need formatting and outputs them on
+ stdout.
+
+BUG FIXES:
+
+- `command/test`: Ensure that passing test results are correctly output one per
+ line. Tests are also now run in a deterministic fashion based on
+ lexicographical (alphabetical) order.
+- `imports/time`: `month_name` and `weekday_name` will now show up correctly in
+ a returned timespace result.
+
+
+## 0.5.0 (November 5, 2018)
+
+IMPROVEMENTS:
+
+- `spec`: Selectors can now contain any reserved word (example: `rule`) or
+ keyword operator (example: `any`, `all`, `is`, `not`). This only works for the
+ _selector_ part of the expression (after the first period) - the first primary
+ expression (before the first period) still needs to be an identifier that does
+ not conflict with reserved words.
+
+BUG FIXES:
+
+- The simulator should now display import function call names correctly in
+ import errors.
+
+## 0.4.0 (October 1, 2018)
+
+FEATURES:
+
+- `builtin`: Added the `bool` built-in type conversion function. Booleans will
+ also now accepted as conversion into other values as well, with the full list
+ of behaviors available in the spec.
+
+## 0.3.2 (September 27, 2018)
+
+FEATURES:
+
+- `command/apply`: `sentinel apply` now prints out messages output by the
+ `print()` function when a trace is output on policy failure, or when a trace
+ is forced with `-trace`.
+- `imports/time`: Added the `month_name` and `weekday_name` keys to the
+ timespace, which return full-English names for the month and day of the week.
+
+
+BUG FIXES:
+
+- `command/fmt`: `sentinel fmt -` Will no longer print out the filter status
+ message on the output stream when `-write=false` Is not explicitly stated.
+ This brings the behavior of the command in line with the help text.
+- `runtime`: Index operations on the right-hand-side that have negative indexes
+ that go out of range (example: `length(list) * -1 - 1`) now correctly return
+ `undefined`. left-hand-side index assignments with a out-of-range negative
+ index still return runtime errors.
+
+## 0.3.1 (August 3, 2018)
+
+BUG FIXES:
+
+- `runtime`: Basic index assignment has been implemented as per the spec.
+- `runtime`: Index expressions for lists with negative indexes will no longer panic if the
+ list index is less than `length(list) * -1`.
+
+## 0.3.0 (July 20, 2018)
+
+FEATURES:
+
+- **New standard import: `types`.** This can be used to dynamicaly detect the
+ type of some value.
+
+## 0.2.0 (April 11, 2018)
+
+FEATURES:
+
+- **New standard import: `json`.** Marshal and unmarshal JSON documents and
+ access their contents as native Sentinel values.
+- **`break` and `continue`.** These are now both specified and implemented.
+ `break` allows loop exiting and `continue` allows immediate execution of the
+ next iteration.
+
+IMPROVEMENTS:
+
+- runtime: `print()` map values are now ordered alphabetically by keys.
+
+BUG FIXES:
+
+- command/test: If no `test` block exists, test behaves like it is asserting
+ `main: true`.
+- runtime: default maximum stack depth to 500
+- runtime: `print()` map values now appear like more typical maps.
+- runtime: division by zero is an error, not a crash
+- runtime: plugins that send map values with `null` values now decode properly
+ into native Sentinel values.
+
+## 0.1.0 (September 19, 2017)
+
+Initial release.
+
+
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/commands/apply.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/commands/apply.mdx
new file mode 100644
index 0000000000..c5e81d6d65
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/commands/apply.mdx
@@ -0,0 +1,63 @@
+---
+page_title: 'Command: apply'
+sidebar_current: docs-commands-apply
+description: >-
+ The `sentinel apply` command is used to execute a policy locally for
+ development purposes.
+layout: docs
+---
+
+# Command: `apply`
+
+The `sentinel apply` command is used to execute a policy locally for development
+purposes.
+
+## Usage
+
+Usage: `sentinel apply [options] POLICY`
+
+This command executes the policy file at the path specified by POLICY. In
+addition to a path to a policy, POLICY can reference a label of a
+[policy](/sentinel/configuration#policies) entry in the configuration.
+
+Use the exit code of this command to determine the exact status of the policy
+evaluation. `0` is pass, `1` is fail, `2` is undefined (fail, but because the
+result was undefined), and `3` is a runtime error. Errors unrelated to the
+policy status itself are returned with an exit status of `9`.
+
+To control the behavior of the `apply` command, create a [configuration
+file](/sentinel/configuration). With this, you can define available
+[import plugins](/sentinel/concepts/imports), mock data, and global values.
+This can help you simulate a policy embedded within an application.
+
+As of the 0.16 release, POLICY is optional. When it is not supplied,
+`sentinel apply` will run **all** policies in the supplied configuration.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-config=path` - Path to a configuration file specifying available imports,
+ mock data, globals, etc. The default is `sentinel.[hcl|json]`.
+
+- `-json` Enable JSON output. Using this mode, all other output will be
+ suppressed and the full detail of the apply will be output in a
+ machine-readable format. Enabling this implies `-trace`. See the section on
+ [tracing JSON output](/sentinel/writing/tracing#json-output) for more details.
+
+- `-json-rule=RULE` Enable JSON output, outputting only the value of the
+ specified rule. When running against a policy set, the policy must be supplied
+ to enable per-rule filtering. Implies `-json`.
+
+- `-global key=value` - Set global values. This is the same as setting `global`
+ in the configuration file, and will override any of these respective values
+ set in the configuration. The value is either a string, or a JSON number,
+ array, or object. To force strings, use quotes.
+
+- `-param key=value` - Set parameters, the same as setting `param` in the
+ configuration file. Values are handled in the same way they are with the
+ `-global` flag.
+
+- `-trace` - Always show the execution trace. This shows intermediate
+ boolean expression values. This always shows for failed policies.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/commands/fmt.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/commands/fmt.mdx
new file mode 100644
index 0000000000..8297316d8b
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/commands/fmt.mdx
@@ -0,0 +1,33 @@
+---
+page_title: 'Command: fmt'
+sidebar_current: docs-commands-fmt
+description: The `sentinel fmt` command formats a policy source to a canonical format.
+layout: docs
+---
+
+# Command: `fmt`
+
+The `sentinel fmt` command formats a policy source to a canonical format.
+
+## Usage
+
+Usage: `sentinel fmt [options] FILE ...`
+
+This command formats all the specified policy files to a canonical format.
+
+By default, policy files are overwritten in place. This behavior can be
+changed with the `-write` flag. If a specified FILE is `-` then stdin is
+read and the output is always written to stdout.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-write=true` - Write formatted policy to the named source file. If false,
+ output will go to stdout. If multiple files are specified, the output will
+ be concatenated directly.
+
+- `-check=false` - Don't format, only check if formatting is necessary. Files
+ that require formatting are printed, and a non-zero exit code is returned if
+ changes are required.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/commands/index.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/commands/index.mdx
new file mode 100644
index 0000000000..7177093cec
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/commands/index.mdx
@@ -0,0 +1,23 @@
+---
+page_title: Commands (CLI)
+sidebar_current: docs-commands
+description: >-
+ Sentinel provides a `sentinel` command-line interface (CLI) for developing and
+ testing policies.
+layout: docs
+---
+
+# Sentinel CLI Commands
+
+The Sentinel command-line interface (CLI) allows for the developing and testing
+of policies outside of a particular Sentinel implementation. Having a standard
+workflow to develop policies is critical for our mission of [policy as
+code](/sentinel/concepts/policy-as-code).
+
+The CLI takes a subcommand to execute. The complete list of subcommands is in
+the navigation to the left.
+
+The Sentinel CLI is a well-behaved command line application. In erroneous cases,
+a non-zero exit status will be returned. It also responds to -h and --help as
+you'd expect. To view a list of the available commands at any time, just run
+`sentinel` with no arguments.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/commands/test.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/commands/test.mdx
new file mode 100644
index 0000000000..ed50aacc22
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/commands/test.mdx
@@ -0,0 +1,53 @@
+---
+page_title: 'Command: test'
+sidebar_current: docs-commands-test
+description: The `sentinel test` command runs policies against the Sentinel test framework.
+layout: docs
+---
+
+# Command: `test`
+
+The `sentinel test` command runs policies against the Sentinel test framework.
+
+To learn more about testing, see the [testing
+policies](/sentinel/writing/testing) page.
+
+## Usage
+
+Usage: `sentinel test [options] [PATH] ...`
+
+This command runs the defined test cases against a set of policies.
+
+The test cases are expected to live at the path `test//*.[hcl|json]` relative
+to the policy being tested. `` should be the name of the policy
+excluding the file extension. The files should be in the [configuration
+file structure](/sentinel/configuration). The `test` key is used for
+assertions. Test cases ignore the root level configuration file and must have
+all required configuration provided in each test case.
+
+The PATH arguments specified may be a list of zero or more files or directories.
+When no arguments are given, it defaults to the current directory. When a
+directory is given, all policies ending with the extension `.sentinel` are
+tested.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-json` - Suppress normal output and output test results as a JSON document.
+ See the [testing JSON output](/sentinel/writing/testing#test-json-output)
+ section for more details.
+
+- `-run=regexp` - Run only tests matching the regular expression pattern.
+ A `/` splits policy name and test case name.
+
+- `-verbose` - Show tracing and log output for tests that succeed in addition
+ to tests that fail. By default, traces and logs are only shown for tests that
+ fail.
+
+- `-maxConcurrency` - Allows users to specify the number of tests to be run concurrently.
+ Defaults to the number of logical CPUs if not provided. To run tests sequentially, use `-maxConcurrency=1`
+
+- `-timeout` - Allows users to specify a timeout after which the test command will stop running.
+Defaults to 5 minutes if not provided. The timeout needs to be specified as a Duration type, eg `100ms, 5s etc`
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/concepts/enforcement-levels.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/concepts/enforcement-levels.mdx
new file mode 100644
index 0000000000..fc4b8062d0
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/concepts/enforcement-levels.mdx
@@ -0,0 +1,43 @@
+---
+page_title: Enforcement Levels
+sidebar_current: docs-concepts-levels
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Enforcement Levels
+
+Enforcement levels are a first class concept in Sentinel allowing pass/fail
+behavior to be associated separately from the policy logic. This enables
+any policy to be a warning, allow overrides, or be absolutely mandatory.
+Because this level is not part of the policy body itself, different uses of
+the same policy can have different enforcement levels.
+
+Sentinel has three enforcement levels:
+
+- **Advisory:** The policy is allowed to fail. However, a warning should be
+ shown to the user or logged. Advisory is the default enforcement level.
+
+- **Soft Mandatory:** The policy must pass unless an override is specified.
+ The semantics of "override" are specific to each Sentinel-enabled application.
+ The purpose of this level is to provide a level of privilege separation
+ for a behavior. Additionally, the override provides non-repudiation since
+ at least the primary actor was explicitly overriding a failed policy.
+
+- **Hard Mandatory:** The policy must pass no matter what. The only way to
+ override a hard mandatory policy is to explicitly remove the policy.
+ It should be used in situations where an override is not possible.
+
+## Configuring Enforcement Levels
+
+Enforcement levels are configured when a policy is deployed to a
+Sentinel-enabled application. The exact mechanism that the level is specified
+is determined by each application. Please reference the documentation for
+your Sentinel-enabled application for more information.
+
+Enforcement levels are not configured and are not known by the policy body
+itself. All policies should be written to describe exactly the behavior
+they're attempting to control. For example, a policy that restricts deploys
+to business hours should be written exactly like so. When that policy is
+configured on an application, the operator may specify that it is advisory,
+soft, or hard mandatory.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/concepts/imports.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/concepts/imports.mdx
new file mode 100644
index 0000000000..1306e1512e
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/concepts/imports.mdx
@@ -0,0 +1,32 @@
+---
+page_title: Imports
+sidebar_current: docs-concepts-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom imports](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only
+local context for making policy decisions.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that the
+import calendar allows access to engineer calendars. This import only exists
+for the purpose of this example and at the time of writing is not a real
+available import.
+
+```json sentinel
+{
+ "policy": "import \"calendar\"\n// Get the calendar for Bob for today\nbob_calendar = calendar.of(\"bob\").today\n\n// Allow this policy to pass if Bob is not on vacation.\nmain = rule { not bob_calendar.has_event(\"vacation\") }",
+ "mocks": {
+ "calendar": "has_event = func(event) {\n\treturn true\n}\nof = func(name) {\n\treturn { \"today\": { \"has_event\": has_event } }\n}"
+ }
+}
+```
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/concepts/index.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/concepts/index.mdx
new file mode 100644
index 0000000000..ce7ac51454
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/concepts/index.mdx
@@ -0,0 +1,15 @@
+---
+page_title: Basic Concepts
+sidebar_current: docs-concepts
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Basic Concepts
+
+This section covers some high level basic concepts that are important
+to understand for day to day Sentinel usage. Every page in
+this section is recommended reading for anyone consuming
+or extending Sentinel.
+
+Please use the navigation to the left to learn more about a topic.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/concepts/language.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/concepts/language.mdx
new file mode 100644
index 0000000000..ce91f3eb8f
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/concepts/language.mdx
@@ -0,0 +1,91 @@
+---
+page_title: Policy Language
+sidebar_current: docs-concepts-language
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Policy Language
+
+Sentinel defines and uses its own [policy language](/sentinel/language).
+
+The language was designed to be approachable by non-programmers, since
+there are many use cases where the individual defining policy may not
+be a developer. However, the language includes constructs that
+are familiar to developers to enable powerful policies.
+
+To learn more about the language, please see the
+[writing policy section](/sentinel/writing)
+or the [language reference](/sentinel/language).
+
+An example of the policy language is shown below:
+
+```sentinel playground
+import "time"
+
+# Validate time is between 8 AM and 4 PM
+valid_time = rule { time.now.hour >= 8 and time.now.hour < 16 }
+
+# Validate day is M - Th
+valid_day = rule {
+ time.now.weekday_name in ["Monday", "Tuesday", "Wednesday", "Thursday"]
+}
+
+main = rule { valid_time and valid_day }
+```
+
+## Why?
+
+To explain why Sentinel defines and uses its own language, the question
+can be more easily split into roughly two types of languages: _configuration_ languages
+and _programming_ languages.
+
+### Configuration Languages
+
+Configuration languages are formats such as JSON, YAML, XML, etc. Many applications
+use configuration languages as their ACL format. Configuration languages
+are good for static, declarative information. ACL systems are a good fit
+for this. ACL systems are focused, providing the limiting options necessary
+to restrict access to a system.
+
+Configuration languages are not good for dynamic or logical rules. They
+typically only contain limited conditions, loops, or functions. Further
+configuration is often declarative, which can introduce complexities to
+logical statements that depend on ordering.
+
+The use cases that Sentinel was built for require the ability to perform
+complex behavior that didn't model well into a configuration format or a
+declarative form.
+
+### Programming Languages
+
+The Sentinel language was designed with the following goals:
+
+- **Non-programmer friendly.** Sentinel was built to be used by non-programmers.
+ For the use cases we discovered, non-programmers needed the ability to
+ enforce certain rules within a system. For example, a person responsible for
+ compliance may need to insert rules into a system.
+
+- **Programmer friendly.** At the same time, the language needed to support
+ programmer-friendly constructs such as conditionals, loops, and functions
+ for complex policies that a programmer may be writing.
+
+- **Embeddable.** Sentinel is embedded in existing software. The language
+ itself needs to be easily embeddedable. Further, Sentinel was designed
+ to be embedded in [HashiCorp](https://www.hashicorp.com) software
+ written in Go, so it needed to be easily embeddable in Go.
+
+- **Safe.** The language is used in highly security-sensitive environments.
+ It must not be able to crash its host system or access system resources
+ without explicit approval.
+
+Prior to building our own language, Sentinel evaluated other languages.
+Some languages worked quite well. As we continued to develop Sentinel, we
+found that the flexibility and control over designing our own language
+outweighed the costs.
+
+Because the language itself doesn't need to be general purpose, building
+a language focused on policy proved to be the best route for Sentinel.
+It allows us to build powerful tracing capabilities for debugging, make
+interesting performance choices, and extend the language as needed in the
+future.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/concepts/policy-as-code.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/concepts/policy-as-code.mdx
new file mode 100644
index 0000000000..6dac593e18
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/concepts/policy-as-code.mdx
@@ -0,0 +1,72 @@
+---
+page_title: Policy as Code
+sidebar_current: docs-concepts-pac
+description: >-
+ Policy as code is the idea of writing code in a high-level language to manage
+ and automate policies. By representing policies as code in text files, proven
+ software development best practices can be adopted such as version control,
+ automated testing, and automated deployment.
+layout: docs
+---
+
+# Policy as Code
+
+Policy as code is the idea of writing code in a high-level language to
+manage and automate policies. By representing policies as code in text files,
+proven software development best practices can be adopted such as version
+control, automated testing, and automated deployment.
+
+Many existing policy or ACL systems do not practice policy as code. Many
+policies are set by clicking in a GUI, which isn't easily repeatable nor
+versionable. They usually don't provide any system for testing policies
+other than testing an action that would violate the policy. This makes it
+difficult for automated testing. And the policy language itself varies by
+product.
+
+Sentinel is built around the idea and provides all the benefits of policy as code.
+
+## Benefits
+
+Policy as code provides a number of benefits:
+
+- **Sandboxing.** Policies provide the guardrails for other automated systems.
+ As the number of automated systems grow, there is also a growing need to
+ protect those automated systems from performing dangerous actions. Manual
+ verification is too slow; policies need to be represented as code to
+ keep up with other automated systems.
+
+- **Codification.** By representing policy logic as code, the information
+ and logic about a policy is directly represented in code and can be augmented
+ with comments rather than relying on oral tradition to learn about the
+ reason for policies.
+
+- **Version Control.** Policies are encouraged to be stored as simple text
+ files managed by a version control system. This lets you gain all the
+ benefits of a modern VCS such as history, diffs, pull requests, and more.
+
+- **Testing.** Policies are just code. Their syntax and behavior can be
+ [easily validated with Sentinel](/sentinel/commands/test). This also encourages
+ automated testing such as through a CI. Paired with a VCS system, this
+ allows a pull request workflow to verify that a policy keeps the system
+ behavior as expected before merging.
+
+- **Automation.** With all policies as code in simple text files, various
+ automation tools can be used. For example, it is trivial to create tools
+ to automatically deploy the policies into a system.
+
+## Sentinel and Policy as Code
+
+Sentinel fully embraces policy as code in a number of ways:
+
+- **Language.** All Sentinel policies are written using the
+ [Sentinel language](/sentinel/concepts/language). This language is
+ made to be inputted directly to text files. As an additional benefit,
+ all Sentinel-enabled applications share the same policy language.
+
+- **Development.** Sentinel provides a [CLI](/sentinel/commands)
+ for development and testing. This local CLI can be used to verify policies
+ before deploying them to a system.
+
+- **Testing.** Sentinel provides a [test framework](/sentinel/commands/test)
+ designed specifically for automation. This allows developers and CI systems
+ to further verify policies.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/configuration/index.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/configuration/index.mdx
new file mode 100644
index 0000000000..9c4b0c942e
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/configuration/index.mdx
@@ -0,0 +1,422 @@
+---
+page_title: Sentinel CLI Configuration File Syntax
+sidebar_current: docs-configuration
+description: >-
+ The Sentinel CLI's configuration file can be used to control the
+ behavior of the simulator during apply and test operations.
+layout: docs
+---
+
+# Sentinel CLI Configuration File Syntax
+
+The Sentinel CLI's configuration file can be used to control the behavior
+of the simulator during [`apply`](/sentinel/commands/apply) and
+[`test`](/sentinel/commands/test) operations.
+
+## Usage
+
+The configuration file is used in different ways depending on what operation you
+are trying to execute.
+
+### Apply
+
+When using `sentinel apply`, configuration files that have the `.hcl`
+extension are loaded into a single configuration. This allows for configuration
+to be split across multiple files, which is useful for large policy sets. To
+supply a single configuration file, the `-config=FILE` flag can be used, where
+`FILE` is the path to the configuration file. This argument also allows for a
+`.json` configuration file to be supplied. The default is `sentinel.[hcl|json]`.
+
+See the [apply command](/sentinel/commands/apply) reference for more details.
+
+### Test
+
+When using `sentinel test`, each file matching `test//*.[hcl|json]` is a
+configuration file representing a single test case, where `` is the name
+of the policy being tested. Configure assertions for each [test
+case](#test-cases) using the test section.
+
+See the [test command](/sentinel/commands/test) reference for more details.
+
+#### Remote sources
+
+When declaring a `policy` or `module` block within the configuration, a
+`source` attribute must be supplied. This `source` should declare the location
+of the resource, which can be either a local file or a URL pointing to a remote
+file. Examples of local `source` attributes are detailed both in the
+[Policies](#policies) and [Modules](#modules) sections of this page. Below
+are a few examples of remote `source` attributes.
+
+Example:
+
+```hcl
+policy "foo" {
+ source = "git::https://github.com/hashicorp/sentinel-example.git//main.sentinel"
+ enforcement_level = "hard-mandatory"
+}
+```
+
+The above example will fetch the policy from the provided URL and place it into
+the cache ready for use. Be sure to read the
+[Remote Sources](/sentinel/configuration/remote-sources) page for an
+in-depth overview.
+
+## Configuration File Reference
+
+The format of the configuration file is either JSON or HCL. The available
+blocks are:
+
+- [`mock`](#mock-imports) - A mock import specification.
+- [`policy`](#policies) - Configuration for a [policy](/sentinel/writing).
+- [`import`](#imports) - Configuration for a [module](/sentinel/extending/modules), [standard import](/sentinel/imports), [plugin](/sentinel/extending/plugins) or
+ [static import](/sentinel/extending/static-imports).
+- [`global`](#globals) - Data that is inserted into the global scope.
+- [`param`](#parameters) - Values for [parameters](/sentinel/language/parameters) defined in the policy.
+- [`test`](#test-cases) - Test cases for the [test command](/sentinel/commands/test).
+- [`sentinel`](#sentinel) - Configuration to manage the Sentinel runtime
+
+### Mock Imports
+
+Mock imports allow running a policy with an
+[import](/sentinel/concepts/imports) that you may not have access to or is
+too difficult to run. For example, it can be easier to mock data for [Terraform
+Enterprise](https://www.terraform.io/docs/enterprise/sentinel/index.html)
+locally instead of having to run a workspace through a plan/policy check cycle.
+
+Mock imports are specified using the `mock` block, labelled by the import
+name that you want to mock. For example, if you wanted to mock the `time`
+import, you could create an entry with the `time` label pointing to the data
+you want to mock.
+
+The data can take one of two forms:
+
+#### Mocking static data
+
+Static data can be mocked directly via HCl using the `data` attribute on the
+`mock` block.
+
+Example:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ hour = 9
+ minute = 42
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"time\"\n\nmain = rule { time.now.hour is 9 }",
+ "mocks": {
+ "time": "now = { \"hour\": 9, \"minute\": 42 }"
+ }
+}
+```
+
+#### Mocking with Sentinel code
+
+There are some Sentinel types that raw data cannot mock, such as functions,
+and maps that don't have string keys. To mock this data, you can use
+a Sentinel file itself. In this case, you can set the `module` attribute to
+a file with the Sentinel code in it, relative to the current working directory in
+the event of `apply`, or relative to the configuration file location in the
+event of `test`.
+
+Note that `main` is not required in a mock data file.
+
+Example:
+
+```hcl
+mock "foo" {
+ module {
+ source = "mock-foo.sentinel"
+ }
+}
+```
+
+`mock-foo.sentinel` would contain:
+
+```sentinel
+bar = func() {
+ return "baz"
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"foo\"\n\nmain = rule { foo.bar() is \"baz\" }",
+ "mocks": {
+ "foo": "bar = func() { return \"baz\" }"
+ }
+}
+```
+
+### Policies
+
+The `policy` block allows for the the configuration of policies to assist
+with integrating into other commands.
+
+Each `policy` block has a label to identify the policy, with the block
+providing configuration of the policy. The available configuration options for
+policy are `source`, `enforcement_level` and an optional `params` attribute. The
+`source` key provides the location of the policy, while `enforcement_level` is
+currently used by integrations such as [Terraform Cloud](https://www.terraform.io/docs/cloud/sentinel/manage-policies.html#enforcement-levels).
+For more information on the `source` value, see [Policy and Module Sources](#policy-and-module-sources).
+
+The optional `params` attribute is used to provide values to [parameters](/sentinel/language/parameters)
+defined within the policy file.
+
+```hcl
+policy "foo" {
+ source = "foo.sentinel"
+ enforcement_level = "hard-mandatory"
+ params = {
+ "name" = "Sample"
+ }
+}
+```
+
+If `enforcement_level` is not supplied, it will fallback to the `advisory` level.
+
+### Imports
+
+Import configuration allows you to configure [modules](/sentinel/extending/modules),
+[standard imports](/sentinel/imports) and [import plugins](/sentinel/extending/plugins).
+
+The `import` is a multi-label block, with the first label determining the kind of import
+being configured. Currently the supported values for this label are:
+
+* [`"module"`](#import-modules) for configuring import modules
+* [`"plugin"`](#import-plugins) for configuring standard imports and import plugins
+* [`"static"`](#static-imports) for configuring static data files as imports
+
+#### Import Modules
+
+The `import "module"` block allows you to map a Sentinel import to a
+[module](/sentinel/extending/modules). The Sentinel CLI will parse the module
+source and make it available for evaluation with the policy.
+
+Each block has a label aligning to the name of the import, with
+the block set to the configuration object for the module. Currently, the only
+value within the configuration object is `source`, which points to the
+location of the module. For more information on the `source` value, see
+[Policy and Module Sources](#policy-and-module-sources).
+
+```hcl
+import "module" "foo" {
+ source = "modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "modules/bar.sentinel"
+}
+```
+
+Note when using [`sentinel test`](/sentinel/commands/test), module paths must be
+specified relative to the location of the configuration file.
+
+```hcl
+import "module" "foo" {
+ source = "../../modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "../../modules/bar/sentinel"
+}
+```
+
+See [Writing and Using a
+Module](/sentinel/extending/modules#writing-and-using-a-module) for more details
+on using a module with this configuration.
+
+### Import Plugins
+
+Import `"plugin"` configuration allows you to configure both [standard imports](/sentinel/imports)
+as well as [import plugins](/sentinel/extending/plugins) that can be used within
+a policy. If defining a custom import plugin, the Sentinel CLI will launch the
+plugin, connect to it, configure it, and execute it as needed by the policy.
+
+The available configuration attributes for an `import "plugin"` are:
+
+- `source` (string, required) - Path to the import plugin executable.
+- `args` (list of string, optional) - A list of arguments to pass to the
+ executable when starting it.
+- `env` (map of string to string, optional) - A set of environmental variables
+ to set when launching the plugin.
+- `config` (map, optional) - Configuration for the plugin itself. This is
+ specific to each individual plugin. Please reference the documentation for
+ the plugin for more information.
+
+Standard import example:
+
+```hcl
+import "plugin" "time" {
+ config = { "timezone": "Australia/Brisbane" }
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"time\"\n\nmain = time.now.zone_string is \"+10:00\"",
+ "mocks": {
+ "time": "now = { \"zone_string\": \"+10:00\" }"
+ }
+}
+```
+
+Custom plugin example:
+
+```hcl
+# flipper receives a boolean and returns the opposite
+import "plugin" "flipper" {
+ source = "/path/to/flipper"
+}
+```
+
+```json sentinel
+{
+ "policy": "import \"flipper\"\n\nmain = flipper.flip(false)",
+ "mocks": {
+ "flipper": "flip = func(input) { return not input }"
+ }
+}
+```
+
+#### Static Imports
+
+Static import configuration allows you to supply a data file and make
+it available to a policy via an import statement.
+
+The available configuration attributes for an `import "static"` are:
+
+- `source` (string, required) - Path to the static data file.
+- `format` (string, required) - The format of the static data. Currently, only
+ the "json" value is supported.
+
+Static import example:
+
+```hcl
+import "static" "people" {
+ source = "./data/people.json"
+ format = "json"
+}
+```
+
+```json sentinel
+{
+ "policy": "import \"people\"\n\nmain = length(people.names) > 0",
+ "mocks": {
+ "people": "names = [\"John Smith\", \"Jane Smith\"]"
+ }
+}
+```
+
+### Globals
+
+Global data is injected directly into the global scope of the policy.
+This can be used to simulate global data that may be injected by a
+Sentinel-enabled application.
+
+Global data is specified by the `global` key. The value is a map of
+variables to inject directly into the running policy.
+
+Example:
+
+```hcl
+global "time" {
+ value = {
+ now = {
+ day = 31
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass. Notice
+that we don't have to do any `import`. The values of `global` are
+injected directly into the global scope.
+
+```json sentinel
+{
+ "policy": "main = time.now.day == 31",
+ "globals": {
+ "time": {
+ "now": {
+ "day": 31
+ }
+ }
+ }
+}
+```
+
+### Parameters
+
+The `param` section allows you to supply values for the
+[parameters](/sentinel/language/parameters) found in a policy. Values
+entered here will satisfy required parameters in a policy, in addition to
+override any defaults. Note that any of the manual parameter value methods
+supported by `sentinel apply` will override the values set here.
+
+```hcl
+param "foo" {
+ value = "bar"
+}
+```
+
+### Test Cases
+
+Test cases specify the test cases for the [test command](/sentinel/commands/test).
+
+Tests are specified by the `test` key. The value of this is a map of
+string to boolean. The key is the name of a rule and the value is the
+expected value of that rule. If a rule is not specified, than any value is
+allowed.
+
+Example:
+
+```hcl
+test {
+ rules = {
+ main = true
+ days = []
+ }
+}
+```
+
+For the policy:
+
+```json sentinel
+{
+ "policy": "valid_days = rule {\n\tfilter days as d {\n\td is \"monday\"\n\t}\n}\n\nmain = rule { valid_days is empty }",
+ "globals": {
+ "days": []
+ }
+}
+```
+
+For more information, read about the [test command](/sentinel/commands/test).
+
+### Sentinel
+
+The `sentinel` block provides configuration specific to the Sentinel runtime.
+
+An example of how the `sentinel` block can be used to enable the [terraform](/sentinel/features/terraform)
+feature is as follows:
+
+```hcl
+sentinel {
+ features = {
+ terraform = true
+ }
+}
+```
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/configuration/overrides.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/configuration/overrides.mdx
new file mode 100644
index 0000000000..ca1a6842b1
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/configuration/overrides.mdx
@@ -0,0 +1,80 @@
+---
+page_title: Override Files
+sidebar_current: docs-configuration-overrides
+description: >-
+ The Sentinel CLI's configuration can be overriden using override files.
+ Override files merge additional settings into existing configuration.
+layout: docs
+---
+
+# Override Files
+
+As outlined in the configuration [overview](/sentinel/configuration#Apply),
+the normal behavior of Sentinel is to load all `.hcl` files into a single
+configuration object.
+
+In addition to appending multiple configuration files, Sentinel allows for the
+rare case of overriding configuration through override files. Override files
+are any files that match either `_override.{hcl,json}` or `override.{hcl,json}`.
+
+Sentinel will parse override files after it has loaded the primary
+configuration, and then process each override file in turn (in lexicographical
+order). All top level blocks other than [test](/sentinel/configuration#test-cases)
+require the block to already be defined in the primary configuration. It will
+then attempt to merge the override block into the existing configuration.
+
+## Example
+
+If you had a Sentinel configuration `sentinel.hcl` that contained the following:
+
+```hcl
+policy "main" {
+ source = "./main.sentinel"
+ enforcement_level = "advisory"
+}
+```
+
+And you created a file `override.hcl` that contained the following:
+
+```hcl
+policy "main" {
+ enforcement_level = "soft-mandatory"
+}
+```
+
+Sentinel will merge the contents of the override into the former, providing the
+following configuration:
+
+```hcl
+policy "main" {
+ source = "./main.sentinel"
+ enforcement_level = "soft-mandatory"
+}
+```
+
+## Merging Behavior
+
+The general rule, which applies in most cases, is:
+
+- A top-level block in an override file merges with a block in a normal
+ configuration file that has the same block header. The block header is the
+ block type and any quoted labels that follow it.
+- Within a top-level block, an attribute argument within an override block
+ replaces any argument of the same name in the original block.
+- Within a top-level block, any nested blocks within an override block replace
+ all blocks of the same type in the original block. Any block types that do not
+ appear in the override block remain from the original block.
+- The contents of nested configuration blocks are not merged.
+- The resulting merged block must still comply with any validation rules that
+ apply to the given block type.
+
+If more than one override file defines the same top-level block, the overriding
+effect is compounded, with later blocks taking precedence over earlier blocks.
+Overrides are processed in order first by filename (in lexicographical order)
+and then by position in each file.
+
+## Required Attributes
+
+It is important to note that all attributes inside an override file are
+considered to be optional, meaning that an override file can itself fail
+validation rules for block types.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/configuration/remote-sources.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/configuration/remote-sources.mdx
new file mode 100644
index 0000000000..b64e4d3abc
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/configuration/remote-sources.mdx
@@ -0,0 +1,165 @@
+---
+page_title: Remote Sources
+sidebar_current: docs-configuration-remote-sources
+description: >-
+ There are a number of options for formatting the URL provided to the
+ source attribute on policy and module blocks.
+layout: docs
+---
+
+# Remote Sources
+
+There are a number of options for formatting the URL provided to the
+`source` attribute on `policy` and `import "module"` blocks. This flexibility
+should solve most use cases and allow for reusable policies and modules.
+
+### Supported Protocols
+
+-> **NOTE:** All protocols are supported on the CLI, however each production
+Sentinel integration may not. Be sure to read the appropriate integration
+documentation to check the supported protocols.
+
+- Local filesystem
+- Git
+- Mercurial
+- HTTP
+- Amazon S3
+- Google GCP
+
+### Forced Protocols
+
+Due to the ambiguous nature of URLs, it is possible to force a particular
+protocol to be used during the fetch. To achieve this, simply prefix the url
+with the appropriate protocol as listed below:
+
+- `file` - Local filesystem
+- `git` - Git
+- `hg` - Mercurial
+- `http` or `https` - HTTP
+- `s3` - Amazon S3
+- `gcp` - Google GCP
+
+Example:
+
+```
+git::http://github.com/hashicorp/sentinel.git
+```
+
+The above would download the provided HTTP URL using the Git protocol.
+
+### Protocol Options
+
+In addition to some global options, there are options available to specific
+protocols to perform features such as authentication.
+
+#### Subdirectories / File Pathing
+
+When supplying URLs that point to a directory (eg. `git`), a subdirectory and
+file can be provided by suffixing the URL with `//` and the path. For example,
+if we have a git repository that has a policy, `main.sentinel` in the root, we
+could directly fetch the file by using the following:
+
+```
+git::https://github.com/hashicorp/sentinel-docs-example.git//main.sentinel
+```
+
+Or, if the file was nested in the `policies` folder:
+
+```
+git::https://github.com/hashicorp/sentinel-docs-example.git//policies/main.sentinel
+```
+
+#### Checksumming
+
+For downloads of any protocol, you can automatically verify a checksum. To
+checksum a file, append a `checksum` query parameter to the URL. The parameter
+value can be in the format of type:value or just value, where type is "md5",
+"sha1", "sha256", "sha512" or "file" . The "value" should be the actual
+checksum value or download URL for "file". When type part is omitted, type will
+be guessed based on the length of the checksum string.
+
+```
+./foo.sentinel?checksum=md5:b7d96c89d09d9e204f5fedc4d5d55b21
+
+./foo.sentinel?checksum=b7d96c89d09d9e204f5fedc4d5d55b21
+
+./foo.sentinel?checksum=file:./foo.txt.sha256sum
+```
+
+#### Unarchiving
+
+An `archive` query parameter can be supplied to explicitly state what format
+to attempt to extract, if required. The supported formats are:
+
+- `tar.gz` and `tgz`
+- `tar.bz2` and `tbz2`
+- `tar.xz` and `txz`
+- `zip`
+- `gz`
+- `bz2`
+- `xz`
+
+If a URL has an extension that matches one of the supported formats, it will
+use that format to unarchive.
+
+```
+./file.zip
+```
+
+The above would use the `zip` format to unarchive.
+
+```
+./path/to/file?archive=zip
+```
+
+Would force the `zip` format. If for some reason you required archiving to be
+disabled, this can also be achieved by using the `false` value.
+
+```
+./path/to/file?archive=false
+```
+
+#### Git (`git`)
+
+- `ref` - The Git ref to checkout. This is a ref, so it can point to a commit
+ SHA, a branch name, etc. If it is a named ref such as a branch name, it will
+ be updated to the latest on each get.
+- `sshkey` - An SSH private key to use during clones. The provided key must be
+ a base64-encoded string. For example, to generate a suitable sshkey from a
+ private key file on disk, you would run base64 -w0 \.
+ **Note:** Git 2.3+ is required to use this feature.
+- `depth` - The Git clone depth. The provided number specifies the last `n`
+ revisions to clone from the repository.
+
+The git getter accepts both URL-style SSH addresses like
+`git::ssh://git@example.com/foo/bar`, and "scp-style" addresses like
+`git::git@example.com/foo/bar`. In the latter case, omitting the `git::` forced
+prefix is allowed if the username prefix is exactly git@.
+
+The "scp-style" addresses cannot be used in conjunction with the `ssh://`
+scheme prefix, because in that case the colon is used to mark an optional port
+number to connect on, rather than to delimit the path from the host.
+
+#### Mercurial (`hg`)
+
+- `rev` - The Mercurial revision to checkout.
+
+#### HTTP (`http` or `https`)
+
+Basic Authentication
+
+To use HTTP basic authentication, simply prepend `username:password@` to the
+hostname in the URL such as `https://Aladdin:OpenSesame@www.example.com/index.html`.
+All special characters, including the username and password, must be URL
+encoded.
+
+#### S3 (`s3`)
+
+The following query parameters are available and will take priority when
+authenticating against an S3 bucket.
+
+- `aws_access_key_id` - AWS access key.
+- `aws_access_key_secret` - AWS access key secret.
+- `aws_access_token` - AWS access token if this is being used.
+- `aws_profile` - Use this profile from local ~/.aws/ config. Takes priority
+ over the other three.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/consul.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/consul.mdx
new file mode 100644
index 0000000000..7a3aaaa697
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/consul.mdx
@@ -0,0 +1,47 @@
+---
+page_title: Consul and Sentinel
+sidebar_current: docs-app-consul
+description: >-
+ Consul Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies are applied during writes to
+ the KV Store.
+layout: docs
+---
+
+# Consul
+
+[Consul Enterprise](https://www.hashicorp.com/products/consul/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies are applied during writes to the KV Store.
+
+Sentinel policies have access to the key/value being written. They can be used to
+allow or deny the modification. The information that Sentinel policies have access
+to will expand over time.
+
+The Consul integration with Sentinel is documented in depth in the
+[Consul Enterprise documentation](https://www.consul.io/docs/guides/sentinel.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Input validation depending on the name of the key.**
+
+```sentinel
+main = rule { valid_key() }
+
+required = [
+ ["port", "\\d+"], # ports must be integers
+ ["name", "\\w+"], # name must be a word
+]
+
+valid_key = func() {
+ for required as v {
+ if key is v[0] {
+ return value matches v[1]
+ }
+ }
+
+ return false
+}
+```
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/extending/index.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/extending/index.mdx
new file mode 100644
index 0000000000..a39cb0384d
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/extending/index.mdx
@@ -0,0 +1,52 @@
+---
+page_title: Extending Sentinel
+sidebar_current: docs-extending
+description: Learn how to create your own Sentinel imports to extend Sentinel's functionality.
+layout: docs
+---
+
+# Extending Sentinel
+
+-> **NOTE:** Sentinel's extensibility is currently undergoing large
+improvements - watch this space for future updates!
+
+Sentinel can be extended by writing additional
+[imports](/sentinel/language/imports). These imports can bring new functionality
+to Sentinel by allowing access to new data or by the addition of new functions.
+This section is designed to show you how to accomplish this extensibility and
+describe how it works.
+
+Currently, the only usable way to add additional imports is via
+[modules](#modules). While [plugins](#plugins) currently work under the Sentinel
+CLI, no Sentinel integration currently supports their use.
+
+-> You may also be interested in advanced details on [import
+internals](/sentinel/extending/internals).
+
+## Modules
+
+Modules allow you to re-use Sentinel code as an import. Modules are loaded
+external of policies and mapped to imports. This is usually configured via a
+file such as the [CLI configuration file](/sentinel/configuration).
+
+See the [modules](/sentinel/extending/modules) section for more details.
+
+## Plugins
+
+-> **NOTE:** Plugins are currently only supported on the CLI and not in any
+production Sentinel integration, with the exception of existing configuration
+in [Nomad](https://www.nomadproject.io/docs/configuration/sentinel).
+
+Imports can also be implemented as plugins when Sentinel code itself is too
+restrictive to represent the import, or if you need access to features not
+normally available to the Sentinel runtime.
+
+See the [plugins](/sentinel/extending/plugins) section for more details.
+
+## Static Imports
+
+Static imports provide support for loading data files into the Sentinel runtime,
+making them available as an import.
+
+See the [static imports](/sentinel/extending/static-imports) section for more
+details.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/extending/internals.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/extending/internals.mdx
new file mode 100644
index 0000000000..11be9d3d67
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/extending/internals.mdx
@@ -0,0 +1,252 @@
+---
+page_title: >-
+ Extending Sentinel: Internals
+sidebar_current: docs-extending-internals
+description: This Section covers some details about Sentinel's import internals.
+layout: docs
+---
+
+# Extending Sentinel: Internals
+
+-> **Advanced Topic!** This page covers low-level technical details of Sentinel.
+You don't need to understand these details to effectively use Sentinel. The
+details are documented here for those who wish to learn about them.
+
+This section covers some of the internal details of Sentinel's import system.
+The goal of this section is to help remove notion of "magic" from Sentinel, to
+allow you to trust and understand what Sentinel is doing with imports, and also
+to help practitioners and developers alike understand the differences between
+the ways that imports can be loaded into Sentinel.
+
+## Language and Runtime
+
+Understanding the import system in Sentinel generally requires understanding of
+two key concepts within Sentinel: the _language_, and the _runtime
+implementation_ of the language.
+
+The Sentinel language and the rules governing it are detailed in the [Sentinel
+Language Specification](/sentinel/language/spec). A runtime implementation must
+conform to the rules laid out in the specification at a minimum. However, to
+meet the design goals of a particular implementation, the runtime may implement
+features on top of the language. The subtle implementation details within the
+runtime may also vary while still being compliant with the specification.
+
+The core runtime included within the [Sentinel CLI](/sentinel/commands) is
+sometimes referred to as the _reference implementation_ of the Sentinel
+language. This runtime is also the main runtime embedded within each
+Sentinel-enabled HashiCorp product such as Terraform Cloud and Enterprise, Vault
+Enterprise, Nomad Enterprise, and Consul Enterprise. The runtime includes an API
+to allow these integrations implement Sentinel in as robust or as restrictive of
+a way as possible, so depending on the implementation, your experience may vary.
+
+## Sentinel's Import Model
+
+The concept of Imports within Sentinel is a _language_ feature. You can see the
+exact rules governing imports within the
+[Imports](/sentinel/language/spec#imports) section of the specification.
+
+Within the runtime, there are currently two major classifications of imports:
+
+- **Modules:** The mapping of Sentinel code to an import, essentially mapping a
+ scope of values to a particular package.
+- **Binary Imports:** A grouping of embedded and external plugins written using
+ the Sentinel SDK. These are comprised of internal or embedded imports (usually
+ the [standard imports](/sentinel/imports) and imports included by an
+ integration) and import plugins.
+
+Below is a diagram explaining in brief the relationship between the langauge
+and runtime features.
+
+
+
+This kind of topology ultimately minimizes the visibility of implementation
+internals to the actual policy language. This allows us to keep the Sentinel
+language itself simple and keep concerns such as module or plugin management,
+validation, and configuration out of the language. These kinds of things would
+impact the readability of a policy, possibly present security challenges, and
+would ultimately be of no use to more minimal embedded applications.
+
+### Implementation Differences Between Import Types
+
+As one would imagine, both modules and binary imports are loaded in much
+different ways, and the methods that they expose data to Sentinel differ as
+well. The effect is generally the same however across both, and we take care to
+ensure that both modules and binary imports behave as close as possible to each
+other, however there are some subtle differences:
+
+- Modules currently support the exporting of rules, but do not support function
+ calls with object receiver data (as seen in some standard imports such as
+ [`decimal`](/sentinel/imports/decimal), [`http`](/sentinel/imports/http), and
+ [`time`](/sentinel/imports/time)). This will be coming in a later release.
+- Binary imports cannot export rules. However, they do support object receiver
+ data (see [`framework.New`](/sentinel/extending/plugins#framework-new) in
+ [Extending Sentinel: Plugins](/sentinel/extending/plugins)).
+
+## Modules
+
+_Modules_ are essentially Sentinel code that is intended to be re-used in multiple
+policies as an import.
+
+The mechanism of module loading is fairly straightforward: during initialization
+of the Sentinel runtime, modules are parsed along with policies. The parsed
+_abstract syntax tree_ (AST) for each module is loaded into the runtime under
+the specified _import path_. These are then passed to the lower-level
+interpreter during the evaluation of each policy.
+
+As with policy code, during evaluation, a module's AST is walked to execute the
+code within that specific module. The module data is then stored within an
+specific scope mapped to the import name. The module data can then be accessed
+through the import via selector expressions and function calls (e.g.,
+`foo.some_map` or `foo.some_func()` for a module loaded via `import "foo"`).
+
+The AST for a module is only walked if a policy imports it, and it is _only
+walked once_. That means if a policy and a module import the same module, or a
+policy imports the same module twice (using [import
+aliases](/sentinel/language/imports#aliases)), those instances will share state.
+If you call a function that alters a singleton variable within the module, that
+singleton will be modified for all instances of the import.
+
+-> **Fun fact!** When you [mock an import with Sentinel
+code](/sentinel/configuration#mocking-with-sentinel-code), you are actually
+using a module. The module is loaded with a flag that allows it to override an
+existing import, which would normally give a runtime error.
+
+### Map and List Cloning for Module Calls
+
+It's also worthwhile noting that map and list values are cloned for modules.
+
+Consider the case where you have a module with a map singleton.
+
+```sentinel
+// modules/foo.sentinel
+a_map = {"a": "b"}
+```
+
+Normally, assignment to the singleton, even when using an index expression, is
+blocked, so `foo.a_map["c"] = "d"` would not pass semantic checking.
+
+However, even when you try to do assignment via an intermediary, you will still
+be only modifying the copy, and the original will not be affected:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+a_map_copy = foo.a_map
+a_map_copy["c"] = "d" // Only modifies copy, does not modify module singleton
+print(foo.a_map) // Still only prints {"a": "b"}
+```
+
+This is to ensure that modules are consistent with binary imports in how return
+data is handled, and the expectation that most non-object data within an import
+is read-only, with the exception of modification of singleton data via
+functions. As return of complex object and collection data in a binary import is
+always via copy, it's not possible to modify any value elements within the
+import in a similar fashion.
+
+There are also some other implications of this cloning that are of note:
+
+- Rules are not cloned, either within a list or map, or by themselves. This is
+ to ensure that [memoization](/sentinel/language/rules#lazy-and-memoized)
+ functions correctly. As only a module can export rules at this time, there is no
+ parity for this in binary imports.
+- Functions are _not_ cloned. This mainly has implications for scope - for
+ example, if you assign a function to another value, or assign an object to a
+ value that has a method that utilizes a global module variable, those calls will
+ reference and/or modify those values.
+
+Functions, while represented differently in binary imports, generally follow the
+same logic here - as they would be invoked in the same package within the binary
+import, any singleton values they accessed there would be affected in a similar
+fashion. As rules are pseudo-functions, this generally makes sense here too.
+
+## Binary Imports
+
+_Binary imports_ is a grouping referring to all imports that are designed with
+the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). These can be
+either internally embedded, or executed via a plugin.
+
+All imports found in the [standard library](/sentinel/imports) are binary
+imports, embedded internally.
+
+The main difference between internally embedded imports and external plugins is
+whether or not the runtime needs to execute a plugin binary as part of
+[lifecycle management](#lifecycle), and whether or not it needs to
+[communicate](#communication) over RPC. Internal binary imports do not require
+these steps, so their execution model is somewhat simpler; however, technically,
+they still are the same as external plugins in terms of design model and value
+conversion. Most standard imports are even tested using the [SDK test
+suite](/sentinel/extending/plugins#testing), which builds the import as an
+external plugin.
+
+The remainder of this section discusses topics mostly relevant to external
+plugins. A large amount of technical detail regarding binary import development
+(also applicable to embedded binary imports) can be seen on the
+[Plugins](/sentinel/extending/plugins) page.
+
+-> **NOTE:** At this time, plugins can only be written for the Sentinel CLI, and
+cannot be used with any of Sentinel's integrations.
+
+### Plugin Basics
+
+Sentinel plugins are built on top of the HashiCorp [go-plugin
+system](https://github.com/hashicorp/go-plugin). This is the same plugin system
+powering all pluggable HashiCorp tools such as
+[Terraform](https://www.terraform.io), [Vault](https://www.vaultproject.io), and
+more. go-plugin is a system that has been used in production for millions of
+users for over 5 years.
+
+Plugins are executable binaries. Sentinel is responsible for launching and
+managing the lifecycle of plugins. Once a plugin is running, Sentinel
+communicates with the plugin via [gRPC](https://grpc.io/). The [protocol is open
+source](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/import.proto)
+to allow anyone to write a Sentinel plugin.
+
+### Lifecycle
+
+Sentinel launches plugins and is responsible for plugin lifecycle.
+
+The runtime optimizes for latency by launching the plugin as soon as it is
+configured, rather than when a policy requires it. This ensures that the plugin
+is ready to be used immediately. A plugin is only closed when Sentinel is
+reconfigured to no longer allow that plugin or if the Sentinel-enabled
+application is closing.
+
+When a plugin is removed from the configuration, Sentinel may wait to shut down
+the plugin until all currently executing policies that are using that plugin
+complete. New policy executions will not be allowed to use the old plugins.
+
+Plugins are automatically restarted if they shut down unexpectedly. Policies
+that were executing while this happens may fail. The Sentinel system is
+improving to more gracefully understand locations where it is safe to retry an
+execution.
+
+### Communication
+
+An [initial
+handshake](https://github.com/hashicorp/go-plugin/blob/master/docs/internals.md)
+is done via stdout to determine the main communication location. Following the
+handshake, communication will either occur on a local Unix domain socket or via
+a local-only TCP socket. This communication is done mostly over the low-level
+[`Get`](https://github.com/hashicorp/sentinel-sdk/blob/2b4db07dd12b55142f0c016f301af80aa4422520/proto/import.proto#L12)
+RPC call.
+
+#### Value Conversion
+
+As can generally be seen in [Developing an Import
+Plugin](/sentinel/extending/plugins#developing-an-import-plugin), the Sentinel
+type system is not exposed to binary imports. While explicit values for null and
+undefined exist, values are mostly converted over the wire from their Go values
+to their Sentinel equivalents.
+
+This has a few implications:
+
+- As discussed in [Map and List Cloning for Module
+ Calls](#map-and-list-cloning-for-module-calls), Map and list data returned from
+ binary import value lookups or function calls is always cloned. These can be
+ freely modified without affecting anything within the binary import.
+- It's currently impossible to represent certain Sentinel types such as
+ [rules](/sentinel/language/rules) within a binary import. This is not a major
+ issue at this point in time as practitioners generally do not look to binary
+ imports for rules; we may examine this as a later time if this situation
+ changes.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/extending/modules.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/extending/modules.mdx
new file mode 100644
index 0000000000..9846d263e5
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/extending/modules.mdx
@@ -0,0 +1,135 @@
+---
+page_title: >-
+ Extending Sentinel: Modules
+sidebar_current: docs-extending-modules
+description: >-
+ Modules allow you to re-use Sentinel code as an import.
+layout: docs
+---
+
+# Extending Sentinel: Modules
+
+Modules allow you to re-use Sentinel code as an import. This allows for the
+export of any general construct that Sentinel supports, including values,
+functions, and even rules. As such, it's a great way to package code that is
+useful across multiple policies, reducing boilerplate and making final policy
+code simpler.
+
+You may want to use modules for:
+
+- **Writing a simple re-usable helper library.** If you mostly know Sentinel or
+ have helpers that you have written in Sentinel, moving these helpers to a module
+ will offer a low-friction way of facilitating their re-use.
+- **Writing re-usable rules.** If you have a set of
+ [rules](/sentinel/language/rules) you know you want to use across multiple
+ policies, bundling them into a module is a great way of abstracting the logic
+ away.
+- **Abstracting existing Sentinel imports.** Using a module is a great way to
+ extend existing Sentinel imports by abstracting the common functionality that
+ you use within an import to your own workflow.
+
+This page describes how you can use modules within the context of the Sentinel
+CLI. For instructions for a particular integration, see the documentation for
+that product.
+
+-> **Not all Sentinel-enabled applications support modules.** Please refer
+to the documentation of the specific Sentinel-enabled application. Some
+applications disable modules for security reasons.
+
+## Configuring a Module
+
+A module is configured within the Sentinel CLI by adding an entry for it in
+within the `imports` section of the [CLI configuration
+file](/sentinel/configuration). The key for each entry specifies the path that
+the module is imported as.
+
+```hcl
+import "module" "foo" {
+ source = "modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "modules/bar.sentinel"
+}
+```
+
+In this example, we have two modules:
+
+- Import path `foo`, being loaded from the code within `modules/foo.sentinel`.
+- Import path `bar`, being loaded from the code within `modules/bar.sentinel`.
+
+See the [Import Modules](/sentinel/configuration#import-modules) section within
+the CLI configuration file syntax page for more details.
+
+### Remote Modules
+
+In addition to loading from a local source, modules can be loaded from a remote
+location. This can increase reusability and allow for sharing modules across
+multiple configurations.
+
+### Testing Using Modules
+
+As with [mocks](/sentinel/configuration#mocking-with-sentinel-code), modules
+are loaded relative to the configuration file location when using `sentinel test`. As such, you need to account for this in your test configuration files:
+
+```hcl
+import "module" "foo" {
+ source = "../../modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "../../modules/bar.sentinel"
+}
+```
+
+This could be a test file for a policy (example: `policy.sentinel`), residing
+under the correct test file directory for this policy (example:
+`test/policy/pass.json`).
+
+## Writing and Using a Module
+
+Writing a module is nearly the same as writing a Sentinel policy, except for the
+following two exceptions:
+
+- Modules do not require [`main`](/sentinel/writing/basic#the-simplest-policy)
+ to be present. It can be, but it will not carry any extra significance as it
+ does within a policy.
+- [`param`](/sentinel/language/parameters) declarations cannot be present in a module. If they are encountered,
+ a runtime error is given.
+
+Once you have a complete module ready for use and configured, using the module
+is as simple as importing it.
+
+Building on the above [configuration example](#configuring-a-module), we can
+export a simple test function in the module `foo` by adding this code to
+`modules/foo.sentinel`:
+
+```sentinel
+// modules/foo.sentinel
+hello = func() {
+ print("hello world!")
+ return undefined
+}
+```
+
+We can then import it within our policy and use it:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+// prints "hello world" in the trace
+foo.hello()
+
+main = true
+```
+
+Note that modules are considered [imports](/sentinel/language/spec#imports) by
+the Sentinel runtime and as such conform to the specification. This means that
+you cannot directly assign values to anything behind a module scope. You can,
+however, use functions to change state.
+
+-> **NOTE:** At this time, modules do not support object receiver data in the
+way that some imports do, like [`time`](/sentinel/imports/time),
+[`decimal`](/sentinel/imports/decimal), and [`http`](/sentinel/imports/http).
+Support for this will be coming in later versions of Sentinel.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/extending/plugins.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/extending/plugins.mdx
new file mode 100644
index 0000000000..e80de5d4b1
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/extending/plugins.mdx
@@ -0,0 +1,521 @@
+---
+page_title: >-
+ Extending Sentinel: Plugins
+sidebar_current: docs-extending-plugins
+description: >-
+ Plugins allow you to write imports as standalone executables, allowing you to
+ extend Sentinel in ways not normally possible with policy code alone.
+layout: docs
+---
+
+# Extending Sentinel: Plugins
+
+-> **NOTE:** Plugins are currently only supported on the CLI and not in any
+production Sentinel integration, with the exception of existing configuration
+in [Nomad](https://www.nomadproject.io/docs/configuration/sentinel).
+
+Plugins allow you to write imports as standalone executables, allowing you to
+extend Sentinel in ways not normally possible with policy code alone.
+
+You might want to write or use a plugin when you need to:
+
+- **Use a provider or API integration for Sentinel.** In this case, you will
+ probably be working with a provider SDK, or other libraries that will be making
+ complex network requests to external services. Sentinel only provides limited
+ capabilities for these kinds of operations in the standard library, so writing
+ this kind of import as a module is not optimal.
+- **Introduce novel functionality not currently supported by Sentinel.**
+ Sentinel's policy language is limited by design to encourage simple policies,
+ reduce its runtime footprint, and to keep it efficient and safe. This will
+ mean that some functionality may be difficult or impossible to express as
+ Sentinel code, and would require a plugin to write.
+- **Extend a complex module.** Additionally, modules are restricted to a single
+ file of Sentinel code, so these will naturally become more and more unwieldy as
+ you continue to add to them. Eventually, there might be a time where a plugin is
+ better suited for the functionality, especially if it's a general-purpose
+ library.
+
+This section details how import plugins are currently configured in the Sentinel
+CLI, and some detail on how they are written. As we continue to build on the
+ability to use import plugins, we will extend this section with more details.
+
+-> **Not all Sentinel-enabled applications will support plugins.** Some
+applications will disable plugins for security reasons. For those that do enable
+plugins, the method to configure plugins will vary.
+
+## Installing Plugins
+
+Sentinel plugins are standalone executables. Sentinel-enabled applications such
+as the CLI launch these plugins and communicate with them via
+[RPC](https://en.wikipedia.org/wiki/Remote_procedure_call). To install a plugin,
+the first step is to download the plugin executable.
+
+After downloading the plugin, you must add it in the CLI configuration file,
+setting its name, path, and any arguments, environment variables, or
+configuration. An example is below:
+
+```hcl
+import "plugin" "custom_time" {
+ source = "/path/to/sentinel-import-time"
+ config = { "fixed_time": 1504155600 }
+}
+```
+
+-> **NOTE:** We are using "custom_time" as standard import paths cannot be
+overriden. See [Configuration File Syntax](/sentinel/configuration#imports) for
+more details.
+
+After configuring the plugin, it will be available on the next `sentinel apply`
+or `sentinel test`.
+
+For more details on configuration, see the
+[plugins](/sentinel/configuration#plugins) section of the [CLI configuration
+file syntax](/sentinel/configuration).
+
+## Debugging Plugins
+
+The Sentinel CLI logs when it launches, configures, and closes a plugin. The
+plugin may also log additional information when it is running.
+
+To debug issues with a plugin, you can usually refer to these logs. Depending on
+the plugin configuration, the logs you see may vary - refer to the plugin
+documentation on how to enable logs for a particular plugin.
+
+## Developing an Import Plugin
+
+Anyone can develop a Sentinel import plugin using the [Sentinel
+SDK](https://github.com/hashicorp/sentinel-sdk). The SDK contains a high-level
+framework for writing plugins in [Go](https://golang.org) including a test
+framework. Additionally, the SDK contains the low-level [gRPC protocol buffers
+definition](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/plugin.proto)
+for writing plugins in other languages.
+
+The rest of this page will document how to write a new Sentinel plugin in Go
+using the Sentinel SDK and the high-level framework provided. You are expected
+to already know the basics of Go and have a properly configured Go installation.
+
+Throughout this page, we'll be developing a simple plugin to access time values.
+
+-> **NOTE:** The example here is not reflective of the actual implementation of
+the [`time`](/sentinel/imports/time) standard import, so make sure to not
+get the two confused.
+
+### Plugin Framework
+
+The Sentinel SDK provides a high-level
+[framework](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework)
+for writing plugins. We recommend using this framework.
+
+#### Basic Concepts
+
+This framework works by implementing the correct Go interfaces.
+
+As a general overview:
+[`framework.Plugin`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Plugin)
+implements the
+[`sdk.Plugin`](https://godoc.org/github.com/hashicorp/sentinel-sdk#Plugin)
+interface and therefore is a valid import plugin. You must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface to configure the plugin. The root may then delegate nested access to
+various
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+implementations. Other interfaces are implemented to provide functionality past
+what is provided by the basic `framework.Namespace` implementation - these are
+documented below.
+
+This all may sound like a lot of interfaces, but each interface typically only
+requires a single function implementation and you're only required to implement
+a single namespace (`framework.Namespace`).
+
+As we move on, we'll use real examples to make this clear.
+
+#### Implementing framework.Root
+
+To begin, you must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface. This is the interface representing the root of your plugin. The root
+itself must be implement
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace),
+which allows values to be accessed. Note that the root can optionally implement
+other interfaces, but they're more advanced and omitted here for simplicity.
+
+For our custom time example, our root may look like this:
+
+```sentinel
+type root struct {
+ time time.Time
+}
+
+// framework.Root impl.
+func (m *root) Configure(raw map[string]interface{}) error {
+ if _, ok := raw["timestamp"]; !ok {
+ raw["timestamp"] = time.Now().Unix()
+ }
+
+ v := raw["timestamp"]
+ timestamp, ok := v.(int)
+ if !ok {
+ return fmt.Errorf("invalid timestamp type %T", v)
+ }
+
+ m.time = time.Unix(timestamp, 0).UTC()
+ return nil
+}
+
+// framework.Namespace impl.
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "minute":
+ return m.time.Minute(), nil
+ }
+
+ return nil, nil
+}
+```
+
+This example implements `framework.Root` and `framework.Namespace` and would
+enable the following within a Sentinel policy once the completed plugin is
+installed.
+
+```sentinel
+import "custom_time"
+
+print(custom_time.minute)
+main = true
+```
+
+#### framework.Namespace
+
+The
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+interface implements a namespace of values. You saw above that
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+must itself be a namespace. However, a namespace `Get` implementation may
+further return namespaces to access nested values.
+
+This enables behavior such as `custom_time.month.string` vs. `custom_time.month.index`.
+Notice each of these examples accesses a nested value within `month`. This can
+be modeled as namespaces within the framework.
+
+In the example below, we return a new namespace for `month` to do just this:
+
+```sentinel
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "month":
+ return &namespaceMonth{Month: m.time.Month()}, nil
+ }
+
+ // ...
+}
+
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil nil
+}
+```
+
+#### Primitive Types and Structs
+
+A namespace may also return any primitive Go type as well as structs. The
+framework automatically exposes primitive values as you would expect. For
+structs, exported fields are lowercased and exposed to the policies. The
+`sentinel` struct tag can be used to control how Sentinel can access the field.
+
+In the example below, we expose a struct directly from the root namespace:
+
+```sentinel
+type Location struct {
+ Name string
+ TimeZone string `sentinel:"time_zone"`
+}
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "location":
+ return &Location{Name: "somewhere", TimeZone: "some zone"}, nil
+ }
+
+ // ...
+}
+```
+
+This makes the following values work within a Sentinel policy:
+`time.location.name`, `time.location.time_zone`.
+
+If a field has an empty `sentinel` struct tag (example: `sentinel:""`),
+then that field will not be accessible from a policy.
+
+#### Optional Interfaces
+
+The following are optional interfaces that can be implemented on top of
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace).
+All of these interfaces can be implemented at any level, except for
+[`framework.New`](#framework-new), which only works at the root level.
+
+##### `framework.Call`
+
+The
+[`framework.Call`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Call)
+interface can be implemented to support function calls.
+
+Implementing this interface is very similar to attribute access, except instead
+of returning the attribute value, you return a function. The framework uses Go
+reflection to determine the argument and result types and calls it. If the
+argument types do not match or the signature is otherwise invalid, an error is
+returned.
+
+For example, let's implement a function to add months to the current month:
+
+```sentinel
+func (m *root) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *root) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.time.AddDate(0, n, 0).Month()}
+}
+```
+
+You can now call the function. If today was in the month of September,
+the policy below would pass:
+
+```sentinel
+import "custom_time"
+
+main = custom_time.add_month(4).string == "January"
+```
+
+##### `framework.Map`
+
+The optional
+[`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+interface allows an alternative method to to present a namespace back to a
+Sentinel policy as a map.
+
+`framework.Map` is best paired with
+[`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys),
+which allows you to quickly fetch the necessary data via `Get` calls on the
+namespace:
+
+```
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil, nil
+}
+
+func (m *namespaceMonth) Map() (map[string]interface{}, error) {
+ return framework.MapFromKeys(m, []string{"string", "index"})
+}
+```
+
+##### `framework.New`
+
+The optional
+[`framework.New`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#New)
+interface can be implemented on a root namespace to add _methods_ to your
+namespaces.
+
+Data returned from a namespace is memoized and returned as a map, and is
+normally not callable - to call a function to operate on the data, you would
+need to create a new namespace from the top-level of the plugin, and then make a
+function call on the result within the same expression.
+
+Let's consider the [root example](#implementing-framework-root). Let's say,
+instead of hard-coding the time value at configuration, we wanted to load it
+on-demand using a `time.now` key, and allow `add_month` to be callable on that
+value. Our root and de-coupled time namespace would now look like:
+
+```
+type root struct{}
+
+func (m *root) Configure(raw map[string]interface{}) error { return nil }
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "now":
+ return &namespaceTime{Time: time.Now()}
+ }
+
+ return nil
+}
+
+func (m *root) New(data map[string]interface{}) (framework.Namespace, error) {
+ if v, ok := data["unix"]; ok {
+ if t, ok := v.(int64); !ok {
+ return &namespaceTime{Time: time.Unix(t, 0)}
+ }
+
+ return nil, fmt.Errorf("expected timestamp to be int64, got %T", v)
+ }
+
+ return nil, nil
+}
+
+type namespaceTime struct {
+ Time time.Time
+}
+
+func (m *namespaceTime) Get(key string) interface{} {
+ switch key {
+ case "unix":
+ return m.Time.Unix()
+
+ // case ...
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) Get(key string) (interface{}, error) {
+ return framework.MapFromKeys(m, []string{"unix", "..."})
+}
+
+func (m *namespaceTime) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.Time.AddDate(0, n, 0).Month()}
+}
+```
+
+Via this example, we can now create and assign a `namespaceTime` using `t = custom_time.now`, and then call `t.add_month(months_to_add)` to return a
+`namespaceMonth` for the corresponding month.
+
+Methods on a namespace can also _modfiy_ the receiver data. So you can, for
+example, add a method named `increment_month` that takes no arguments, but
+increments the time stored in `namespaceTime.Time` by a month. Subsequent
+statements using the receiver would see the new value.
+
+Note that there are some restrictions and guidelines that you should take into
+account when using `framework.New`:
+
+- Only data that makes it back to a policy can be used as receiver data to
+ instantiate new namespaces. As such it's recommended to make use of
+ [`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+ and
+ [`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys)
+ to return all of the attribute data you need to construct new objects.
+- `framework.New` is only supported on the root namespace and has no effect on
+ namespaces below the root. Check all possible cases of receiver data within
+ the top-level `New` function and return the appropriate namespace based on the
+ input data.
+- Do not return general errors from your `New` method. When an unknown lookup is
+ made off of memoized data, it will hit your `New` method for possible
+ instantiation and key calls. This will ensure `undefined` is correctly
+ returned for these cases.
+- `framework.New` is designed to add utility to plugins where calling methods on
+ a value is more intuitive than just making a function call, or just returning
+ all of a data set as a memoized map. Use it sparingly and avoid using it on
+ recursively complex data sets.
+
+### Testing
+
+The SDK exposes a testing framework to verify your plugin works as
+expected. This test framework integrates directly into `go test` so it
+is part of a familiar workflow.
+
+The test framework works by dynamically building your plugin and
+running it against the `sentinel` CLI to verify it behaves as expected.
+This ensures that your plugin builds, your plugin communicates via RPC
+correctly, and that the behavior within a policy is also correct.
+
+The example below shows a complete ready table-driven test for our time plugin.
+You can run this with a normal `go test`.
+
+```sentinel
+package main
+
+import (
+ "os"
+ "testing"
+
+ "github.com/hashicorp/sentinel-sdk"
+ plugintesting "github.com/hashicorp/sentinel-sdk/testing"
+)
+
+func TestMain(m *testing.M) {
+ exitCode := m.Run()
+ plugintesting.Clean()
+ os.Exit(exitCode)
+}
+
+func TestPlugin(t *testing.T) {
+ cases := []struct {
+ Name string
+ Data map[string]interface{}
+ Source string
+ }{
+ {
+ "month",
+ map[string]interface{}{"timestamp": 1495483674},
+ `main = subject.month.string == "September"`,
+ },
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.Name, func(t *testing.T) {
+ plugintesting.TestPlugin(t, plugintesting.TestPluginCase{
+ Config: tc.Data,
+ Source: tc.Source,
+ })
+ })
+ }
+}
+```
+
+### Building Your Plugin
+
+To build your plugin, you must first implement the `main` function.
+The main function should just use the `rpc.Serve` method to serve the
+plugin over the Sentinel RPC layer:
+
+```sentinel
+package main
+
+import (
+ "github.com/hashicorp/sentinel-sdk"
+ "github.com/hashicorp/sentinel-sdk/rpc"
+)
+
+func main() {
+ rpc.Serve(&rpc.ServeOpts{
+ PluginFunc: func() sdk.Plugin {
+ return &framework.Plugin{Root: &root{}}
+ },
+ })
+}
+```
+
+You can then build this using `go build`. The output can be named
+anything. Once your plugin is built, [install it](#installing-plugins)
+and try it out!
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/extending/static-imports.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/extending/static-imports.mdx
new file mode 100644
index 0000000000..cb51beb5d8
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/extending/static-imports.mdx
@@ -0,0 +1,48 @@
+---
+page_title: >-
+ Extending Sentinel: Static Imports
+sidebar_current: docs-extending-static-imports
+description: >-
+ Static imports allow you to write imports that use static data files.
+layout: docs
+---
+
+# Extending Sentinel: Static Imports
+
+Static imports allow you to write imports that use static data files. This can
+provide methods of consuming data in ways that are not available via
+[modules](./modules) or [plugins](./plugins).
+
+### Configuring a Static Import
+
+The [configuration page](/sentinel/configuration#static-imports) details how to
+configure static imports.
+
+### Usage
+
+Using a static import is not that different to using a plugin or module. The
+import must first be introduced to the policy via an import statement.
+
+```sentinel
+import "people"
+```
+
+From here, the static data can be used throughout the policy as you would any
+normal Sentinel value.
+
+```sentinel
+admins = filter people as person {
+ person.role is "Admin"
+}
+```
+
+One differentiator for static imports is that you can directly reference the
+import without the use of selectors. For example, to print the value;
+
+```sentinel
+print(people)
+```
+
+This is due to the static data being directly loaded and converted into a
+Sentinel value, ready for use. Static imports, like modules and plugins, cannot
+be assigned a new value.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/features/index.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/features/index.mdx
new file mode 100644
index 0000000000..e035be978c
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/features/index.mdx
@@ -0,0 +1,28 @@
+---
+page_title: Features
+sidebar_current: docs-features
+description: Learn how features can enhance the Sentinel runtime by enabling further capabilities.
+layout: docs
+---
+
+# Features
+
+Features are a set of capabilities that enhance the runtime experience. They
+are enabled through the [`sentinel`](/sentinel/configuration#sentinel) block of the
+configuration, using the features attribute. An example of how to enable features
+is as follows:
+
+```hcl
+sentinel {
+ features = {
+ apply-all = true
+ terraform = true
+ }
+}
+```
+
+The current set of features are:
+
+- [terraform](/sentinel/features/terraform) - Allowing the Sentinel runtime
+ to interact with Terraform data.
+- apply-all - Force Sentinel to evaluate all policies within a policy set without prematurely terminating on failures.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/features/terraform/index.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/features/terraform/index.mdx
new file mode 100644
index 0000000000..a810dbe478
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/features/terraform/index.mdx
@@ -0,0 +1,24 @@
+---
+page_title: terraform
+sidebar_current: docs-features-terraform
+description: Learn about the terraform feature and its capabilities.
+layout: docs
+---
+
+# terraform feature
+
+The `terraform` feature enhances the Sentinel runtime to work with Terraform
+data. It will add the following imports to the standard library:
+
+- [tfplan/v1](/sentinel/features/terraform/tfplan-v1)
+- [tfplan/v2](/sentinel/features/terraform/tfplan-v2)
+- [tfconfig/v1](/sentinel/features/terraform/tfconfig-v1)
+- [tfconfig/v2](/sentinel/features/terraform/tfconfig-v2)
+- [tfstate/v1](/sentinel/features/terraform/tfstate-v1)
+- [tfstate/v2](/sentinel/features/terraform/tfstate-v2)
+
+-> **NOTE:** The above imports will only work with Terraform 0.12 and above,
+as they rely on the output from the `terraform show -json` command.
+
+It is recommended that the `/v2` suffixed imports are used, as they provide
+the best experience when interacting with the underlying data structures.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/features/terraform/tfconfig-v1.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/features/terraform/tfconfig-v1.mdx
new file mode 100644
index 0000000000..90179d7525
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/features/terraform/tfconfig-v1.mdx
@@ -0,0 +1,945 @@
+---
+page_title: tfconfig/v1 - terraform - Features
+sidebar_current: docs-features-terraform-config-v1
+description: The tfconfig/v1 import provides access to a Terraform configuration.
+layout: docs
+---
+
+# Import: tfconfig/v1
+
+The `tfconfig/v1` import provides access to a Terraform configuration.
+
+The Terraform configuration is the set of `*.tf` files that are used to
+describe the desired infrastructure state. Policies using the `tfconfig/v1`
+import can access all aspects of the configuration: providers, resources,
+data sources, modules, and variables.
+
+Some use cases for `tfconfig/v1` include:
+
+* **Organizational naming conventions**: requiring that configuration elements
+ are named in a way that conforms to some organization-wide standard.
+* **Required inputs and outputs**: organizations may require a particular set
+ of input variable names across all workspaces or may require a particular
+ set of outputs for asset management purposes.
+* **Enforcing particular modules**: organizations may provide a number of
+ "building block" modules and require that each workspace be built only from
+ combinations of these modules.
+* **Enforcing particular providers or resources**: an organization may wish to
+ require or prevent the use of providers and/or resources so that configuration
+ authors cannot use alternative approaches to work around policy
+ restrictions.
+
+Note with these use cases that this import is concerned with object _names_
+in the configuration. Since this is the configuration and not an invocation
+of Terraform, you can't see values for variables, the state, or the diff for
+a pending plan. If you want to write policy around expressions used
+within configuration blocks, you likely want to use the
+[`tfplan/v1`](/sentinel/features/terraform/tfplan-v1) import.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfconfig/v1" {
+ config = {
+ "plan": "./path/to/plan.json"
+ }
+}
+```
+
+## Namespace Overview
+
+The following is a tree view of the import namespace. For more detail on a
+particular part of the namespace, see below.
+
+-> **Note:** The root-level alias keys shown here (`data`, `modules`,
+`providers`, `resources`, and `variables`) are shortcuts to a [module
+namespace](#namespace-module) scoped to the root module. For more details, see
+the section on [root namespace aliases](#root-namespace-aliases).
+
+```
+tfconfig/v1
+├── module() (function)
+│ └── (module namespace)
+│ ├── data
+│ │ └── TYPE.NAME
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── provisioners
+│ │ └── NUMBER
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── type (string)
+│ ├── modules
+│ │ └── NAME
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ ├── source (string)
+│ │ └── version (string)
+│ ├──outputs
+│ │ └── NAME
+│ │ ├── depends_on (list of strings)
+│ │ ├── description (string)
+│ │ ├── sensitive (boolean)
+│ │ ├── references (list of strings)
+│ │ └── value (value)
+│ ├── providers
+│ │ └── TYPE
+│ │ ├── alias
+│ │ │ └── ALIAS
+│ │ │ ├── config (map of keys)
+│ │ | ├── references (map of keys)
+│ │ │ └── version (string)
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── version (string)
+│ ├── resources
+│ │ └── TYPE.NAME
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── provisioners
+│ │ └── NUMBER
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── type (string)
+│ └── variables
+│ └── NAME
+│ ├── default (value)
+│ └── description (string)
+├── module_paths ([][]string)
+│
+├── data (root module alias)
+├── modules (root module alias)
+├── outputs (root module alias)
+├── providers (root module alias)
+├── resources (root module alias)
+└── variables (root module alias)
+```
+
+### `references` Overview
+
+As an example, consider the following resource block:
+
+```hcl
+resource "local_file" "accounts" {
+ content = "some text"
+ filename = "${var.subdomain}.${var.domain}/accounts.txt"
+}
+```
+
+In this example, one might want to ensure `domain` and `subdomain` input
+variables are used within `filename` in this configuration.
+
+-> Any non-static values (such as interpolated strings) are not present within the
+configuration value and `references` should be used instead:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+# filename_references is a list of string values containing the references used in the expression
+filename_references = tfconfig.resources.local_file.accounts.references.filename
+
+main = rule {
+ filename_references contains "var.domain" and
+ filename_references contains "var.subdomain"
+}
+```
+
+The `references` value is present in any namespace where non-constant
+configuration values can be expressed. This is essentially every namespace
+which has a `config` value as well as the `outputs` namespace.
+
+-> **Note:** Remember, this import enforces policy around the literal Terraform
+configuration and not the final values as a result of invoking Terraform. If
+you want to write policy around the _result_ of expressions used within
+configuration blocks (for example, if you wanted to ensure the final value of
+`filename` above includes `accounts.txt`), you likely want to use the
+[`tfplan/v1`](/sentinel/features/terraform/tfplan-v1) import.
+
+## Namespace: Root
+
+The root-level namespace consists of the values and functions documented below.
+
+In addition to this, the root-level `data`, `modules`, `providers`, `resources`,
+and `variables` keys all alias to their corresponding namespaces within the
+[module namespace](#namespace-module).
+
+
+
+### Function: `module()`
+
+```
+module = func(ADDR)
+```
+
+* **Return Type:** A [module namespace](#namespace-module).
+
+The `module()` function in the [root namespace](#namespace-root) returns the
+[module namespace](#namespace-module) for a particular module address.
+
+The address must be a list and is the module address, split on the period (`.`),
+excluding the root module.
+
+Hence, a module with an address of simply `foo` (or `root.foo`) would be
+`["foo"]`, and a module within that (so address `foo.bar`) would be read as
+`["foo", "bar"]`.
+
+[`null`][ref-null] is returned if a module address is invalid, or if the module
+is not present in the configuration.
+
+[ref-null]: /sentinel/language/spec#null
+
+As an example, given the following module block:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+If the module contained the following content:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { subject.module(["foo"]).resources.null_resource.foo.config.triggers[0].foo is "bar" }
+```
+
+
+
+### Value: `module_paths`
+
+* **Value Type:** List of a list of strings.
+
+The `module_paths` value within the [root namespace](#namespace-root) is a list
+of all of the modules within the Terraform configuration.
+
+Modules not present in the configuration will not be present here, even if they
+are present in the diff or state.
+
+This data is represented as a list of a list of strings, with the inner list
+being the module address, split on the period (`.`).
+
+The root module is included in this list, represented as an empty inner list.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The value of `module_paths` would be:
+
+```
+[
+ [],
+ ["foo"],
+]
+```
+
+And the following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.module_paths contains ["foo"] }
+```
+
+#### Iterating Through Modules
+
+Iterating through all modules to find particular resources can be useful. This
+[example][iterate-over-modules] shows how to use `module_paths` with the
+[`module()` function](#function-module-) to find all resources of a
+particular type from all modules using the `tfplan` import. By changing `tfplan`
+in this function to `tfconfig`, you could make a similar function find all
+resources of a specific type in the Terraform configuration.
+
+[iterate-over-modules]: https://developer.hashicorp.com/terraform/cloud-docs/policy-enforcement/sentinel#sentinel-imports
+
+## Namespace: Module
+
+The **module namespace** can be loaded by calling [`module()`](#root-function-module)
+for a particular module.
+
+It can be used to load the following child namespaces:
+
+* `data` - Loads the [resource namespace](#namespace-resources-data-sources),
+ filtered against data sources.
+* `modules` - Loads the [module configuration
+ namespace](#namespace-module-configuration).
+* `outputs` - Loads the [output namespace](#namespace-outputs).
+* `providers` - Loads the [provider namespace](#namespace-providers).
+* `resources` - Loads the [resource
+ namespace](#namespace-resources-data-sources), filtered against resources.
+* `variables` - Loads the [variable namespace](#namespace-variables).
+
+### Root Namespace Aliases
+
+The root-level `data`, `modules`, `providers`, `resources`, and `variables` keys
+all alias to their corresponding namespaces within the module namespace, loaded
+for the root module. They are the equivalent of running `module([]).KEY`.
+
+
+
+## Namespace: Resources/Data Sources
+
+The **resource namespace** is a namespace _type_ that applies to both resources
+(accessed by using the `resources` namespace key) and data sources (accessed
+using the `data` namespace key).
+
+Accessing an individual resource or data source within each respective namespace
+can be accomplished by specifying the type and name, in the syntax
+`[resources|data].TYPE.NAME`.
+
+In addition, each of these namespace levels is a map, allowing you to filter
+based on type and name. Some examples of multi-level access are below:
+
+* To fetch all `aws_instance` resources within the root module, you can specify
+ `tfconfig.resources.aws_instance`. This would give you a map of resource
+ namespaces indexed from the names of each resource (`foo`, `bar`, and so
+ on).
+* To fetch all resources within the root module, irrespective of type, use
+ `tfconfig.resources`. This is indexed by type, as shown above with
+ `tfconfig.resources.aws_instance`, with names being the next level down.
+
+As an example, perhaps you wish to deny use of the `local_file` resource
+in your configuration. Consider the following resource block:
+
+```hcl
+resource "local_file" "foo" {
+ content = "foo!"
+ filename = "${path.module}/foo.bar"
+}
+```
+
+The following policy would fail:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.resources not contains "local_file" }
+```
+
+Further explanation of the namespace will be in the context of resources. As
+mentioned, when operating on data sources, use the same syntax, except with
+`data` in place of `resources`.
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+The `config` value within the [resource
+namespace](#namespace-resources-data-sources) is a map of key-value pairs that
+directly map to Terraform config keys and values.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#resources-value-references) should be used instead.
+
+As an example, consider the following resource block:
+
+```hcl
+resource "local_file" "accounts" {
+ content = "some text"
+ filename = "accounts.txt"
+}
+```
+
+In this example, one might want to access `filename` to validate that the correct
+file name is used. Given the above example, the following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule {
+ tfconfig.resources.local_file.accounts.config.filename is "accounts.txt"
+}
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [resource namespace](#namespace-resources-data-sources)
+contains the identifiers within non-constant expressions found in [`config`](#resources-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+
+
+### Value: `provisioners`
+
+* **Value Type:** List of [provisioner namespaces](#namespace-provisioners).
+
+The `provisioners` value within the [resource namespace](#namespace-resources)
+represents the [provisioners][ref-tf-provisioners] within a specific resource.
+
+Provisioners are listed in the order they were provided in the configuration
+file.
+
+While the `provisioners` value will be present within data sources, it will
+always be an empty map `null` (in Terraform 0.12) since data sources cannot
+actually have provisioners.
+
+The data within a provisioner can be inspected via the returned [provisioner
+namespace](#namespace-provisioners).
+
+[ref-tf-provisioners]: https://developer.hashicorp.com/terraform/language/resources/provisioners/syntax
+
+## Namespace: Provisioners
+
+The **provisioner namespace** represents the configuration for a particular
+[provisioner][ref-tf-provisioners] within a specific resource.
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+The `config` value within the [provisioner namespace](#namespace-provisioners)
+represents the values of the keys within the provisioner.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#provisioners-value-references) should be used instead.
+
+As an example, given the following resource block:
+
+```hcl
+resource "null_resource" "foo" {
+ # ...
+
+ provisioner "local-exec" {
+ command = "echo ${self.private_ip} > file.txt"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule {
+ tfconfig.resources.null_resource.foo.provisioners[0].config.command is "echo ${self.private_ip} > file.txt"
+}
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [provisioner namespace](#namespace-provisioners)
+contains the identifiers within non-constant expressions found in [`config`](#provisioners-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+
+
+### Value: `type`
+
+* **Value Type:** String.
+
+The `type` value within the [provisioner namespace](#namespace-provisioners)
+represents the type of the specific provisioner.
+
+As an example, in the following resource block:
+
+```hcl
+resource "null_resource" "foo" {
+ # ...
+
+ provisioner "local-exec" {
+ command = "echo ${self.private_ip} > file.txt"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.resources.null_resource.foo.provisioners[0].type is "local-exec" }
+```
+
+## Namespace: Module Configuration
+
+The **module configuration** namespace displays data on _module configuration_
+as it is given within a `module` block. This means that the namespace concerns
+itself with the contents of the declaration block (example: the `source`
+parameter and variable assignment keys), not the data within the module
+(example: any contained resources or data sources). For the latter, the module
+instance would need to be looked up with the [`module()`
+function](#root-function-module).
+
+
+
+### Value: `source`
+
+* **Value Type:** String.
+
+The `source` value within the [module configuration
+namespace](#namespace-module-configuration) represents the module source path as
+supplied to the module configuration.
+
+As an example, given the module declaration block:
+
+```hcl
+module "foo" {
+ source = "./foo"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.modules.foo.source is "./foo" }
+```
+
+
+
+### Value: `version`
+
+* **Value Type:** String.
+
+The `version` value within the [module configuration
+namespace](#namespace-module-configuration) represents the [version
+constraint][module-version-constraint] for modules that support it, such as
+modules within the [Terraform Module Registry][terraform-module-registry] or the
+[Terraform Cloud private module registry][tfe-private-registry].
+
+[module-version-constraint]: https://developer.hashicorp.com/terraform/language/modules#module-versions
+
+[terraform-module-registry]: https://registry.terraform.io/
+
+[tfe-private-registry]: https://developer.hashicorp.com/terraform/cloud-docs/registry
+
+As an example, given the module declaration block:
+
+```hcl
+module "foo" {
+ source = "foo/bar"
+ version = "~> 1.2"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.modules.foo.version is "~> 1.2" }
+```
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#modules-value-references) should be used instead.
+
+The `config` value within the [module configuration
+namespace](#namespace-module-configuration) represents the values of the keys
+within the module configuration. This is every key within a module declaration
+block except [`source`](#modules-value-source) and [`version`](#modules-value-version), which
+have their own values.
+
+As an example, given the module declaration block:
+
+```hcl
+module "foo" {
+ source = "./foo"
+
+ bar = "baz"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.modules.foo.config.bar is "baz" }
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [module configuration namespace](#namespace-module-configuration)
+contains the identifiers within non-constant expressions found in [`config`](#modules-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+## Namespace: Outputs
+
+The **output namespace** represents _declared_ output data within a
+configuration. As such, configuration for the [`value`](#outputs-value-value) attribute
+will be in its raw form, and not yet interpolated. For fully interpolated output
+values, see the [`tfstate` import][ref-tfe-sentinel-tfstate].
+
+[ref-tfe-sentinel-tfstate]: /sentinel/features/terraform/tfstate-v1
+
+This namespace is indexed by output name.
+
+
+
+### Value: `depends_on`
+
+* **Value Type:** A list of strings.
+
+The `depends_on` value within the [output namespace](#namespace-outputs)
+represents any _explicit_ dependencies for this output. For more information,
+see the [depends_on output setting][ref-depends_on] within the general Terraform
+documentation.
+
+[ref-depends_on]: https://developer.hashicorp.com/terraform/language/values/outputs#depends_on
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ depends_on = ["null_resource.bar"]
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.depends_on[0] is "null_resource.bar" }
+```
+
+
+
+### Value: `description`
+
+* **Value Type:** String.
+
+The `description` value within the [output namespace](#namespace-outputs)
+represents the defined description for this output.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ description = "foobar"
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.description is "foobar" }
+```
+
+
+
+### Value: `sensitive`
+
+* **Value Type:** Boolean.
+
+The `sensitive` value within the [output namespace](#namespace-outputs)
+represents if this value has been marked as sensitive or not.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ sensitive = true
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { subject.outputs.id.sensitive }
+```
+
+
+
+### Value: `value`
+
+* **Value Type:** Any primitive type, list or map.
+
+The `value` value within the [output namespace](#namespace-outputs) represents
+the defined value for the output as declared in the configuration. Primitives
+will bear the implicit type of their declaration (string, int, float, or bool),
+and maps and lists will be represented as such.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#outputs-value-references) should be used instead.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ value = "foo"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.value is "foo" }
+```
+
+
+
+### Value: `references`
+
+* **Value Type:**. List of strings.
+
+The `references` value within the [output namespace](#namespace-outputs)
+contains the names of any referenced identifiers when [`value`](#outputs-value-value)
+is a non-constant expression.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.references contains "null_resource.foo.id" }
+```
+
+## Namespace: Providers
+
+The **provider namespace** represents data on the declared providers within a
+namespace.
+
+This namespace is indexed by provider type and _only_ contains data about
+providers when actually declared. If you are using a completely implicit
+provider configuration, this namespace will be empty.
+
+This namespace is populated based on the following criteria:
+
+* The top-level namespace [`config`](#providers-value-config) and
+ [`version`](#providers-value-version) values are populated with the configuration and
+ version information from the default provider (the provider declaration that
+ lacks an alias).
+* Any aliased providers are added as namespaces within the
+ [`alias`](#providers-value-alias) value.
+* If a module lacks a default provider configuration, the top-level `config` and
+ `version` values will be empty.
+
+
+
+### Value: `alias`
+
+* **Value Type:** A map of [provider namespaces](#namespace-providers), indexed
+ by alias.
+
+The `alias` value within the [provider namespace](#namespace-providers)
+represents all declared [non-default provider
+instances][ref-tf-provider-instances] for a specific provider type, indexed by
+their specific alias.
+
+[ref-tf-provider-instances]: https://developer.hashicorp.com/terraform/language/providers/configuration#alias-multiple-provider-configurations
+
+The return type is a provider namespace with the data for the instance in
+question loaded. The `alias` key will not be available within this namespace.
+
+As an example, given the following provider declaration block:
+
+```hcl
+provider "aws" {
+ alias = "east"
+ region = "us-east-1"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.providers.aws.alias.east.config.region is "us-east-1" }
+```
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#providers-value-references) should be used instead.
+
+The `config` value within the [provider namespace](#namespace-providers)
+represents the values of the keys within the provider's configuration, with the
+exception of the provider version, which is represented by the
+[`version`](#providers-value-version) value. [`alias`](#providers-value-alias) is also not included
+when the provider is aliased.
+
+As an example, given the following provider declaration block:
+
+```hcl
+provider "aws" {
+ region = "us-east-1"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.providers.aws.config.region is "us-east-1" }
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [provider namespace](#namespace-providers)
+contains the identifiers within non-constant expressions found in [`config`](#providers-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+
+
+### Value: `version`
+
+* **Value Type:** String.
+
+The `version` value within the [provider namespace](#namespace-providers)
+represents the explicit expected version of the supplied provider. This includes
+the pessimistic operator.
+
+As an example, given the following provider declaration block:
+
+```hcl
+provider "aws" {
+ version = "~> 1.34"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.providers.aws.version is "~> 1.34" }
+```
+
+## Namespace: Variables
+
+The **variable namespace** represents _declared_ variable data within a
+configuration. As such, static data can be extracted, such as defaults, but not
+dynamic data, such as the current value of a variable within a plan (although
+this can be extracted within the [`tfplan` import][ref-tfe-sentinel-tfplan]).
+
+[ref-tfe-sentinel-tfplan]: /sentinel/features/terraform/tfplan-v1
+
+This namespace is indexed by variable name.
+
+
+
+### Value: `default`
+
+* **Value Type:** Any primitive type, list, map, or `null`.
+
+The `default` value within the [variable namespace](#namespace-variables)
+represents the default for the variable as declared in the configuration.
+
+The actual value will be as configured. Primitives will bear the implicit type
+of their declaration (string, int, float, or bool), and maps and lists will be
+represented as such.
+
+If no default is present, the value will be [`null`][ref-sentinel-null] (not to
+be confused with [`undefined`][ref-sentinel-undefined]).
+
+[ref-sentinel-null]: /sentinel/language/spec#null
+
+[ref-sentinel-undefined]: /sentinel/language/undefined
+
+As an example, given the following variable blocks:
+
+```hcl
+variable "foo" {
+ default = "bar"
+}
+
+variable "number" {
+ default = 42
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+default_foo = rule { tfconfig.variables.foo.default is "bar" }
+default_number = rule { tfconfig.variables.number.default is 42 }
+
+main = rule { default_foo and default_number }
+```
+
+
+
+### Value: `description`
+
+* **Value Type:** String.
+
+The `description` value within the [variable namespace](#namespace-variables)
+represents the description of the variable, as provided in configuration.
+
+As an example, given the following variable block:
+
+```hcl
+variable "foo" {
+ description = "foobar"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.variables.foo.description is "foobar" }
+```
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/features/terraform/tfconfig-v2.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/features/terraform/tfconfig-v2.mdx
new file mode 100644
index 0000000000..ddc282316b
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/features/terraform/tfconfig-v2.mdx
@@ -0,0 +1,441 @@
+---
+page_title: tfconfig/v2 - terraform - Features
+sidebar_current: docs-features-terraform-tfconfig-v2
+description: The tfconfig/v2 import provides access to a Terraform configuration.
+layout: docs
+---
+
+# Import: tfconfig/v2
+
+The `tfconfig/v2` import provides access to a Terraform configuration.
+
+The Terraform configuration is the set of `*.tf` files that are used to
+describe the desired infrastructure state. Policies using the `tfconfig`
+import can access all aspects of the configuration: providers, resources,
+data sources, modules, and variables.
+
+Some use cases for `tfconfig` include:
+
+* **Organizational naming conventions**: requiring that configuration elements
+ are named in a way that conforms to some organization-wide standard.
+* **Required inputs and outputs**: organizations may require a particular set
+ of input variable names across all workspaces or may require a particular
+ set of outputs for asset management purposes.
+* **Enforcing particular modules**: organizations may provide a number of
+ "building block" modules and require that each workspace be built only from
+ combinations of these modules.
+* **Enforcing particular providers or resources**: an organization may wish to
+ require or prevent the use of providers and/or resources so that configuration
+ authors cannot use alternative approaches to work around policy
+ restrictions.
+
+The data in the `tfconfig/v2` import is sourced from the JSON configuration file
+that is generated by the [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) command. For more information on
+the file format, see the [JSON Output Format](https://developer.hashicorp.com/terraform/internals/json-format)
+page.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfconfig/v2" {
+ config = {
+ "plan": "./path/to/plan.json"
+ }
+}
+```
+
+## Import Overview
+
+The `tfconfig/v2` import is structured as a series of _collections_, keyed as a
+specific format, such as resource address, module address, or a
+specifically-formatted provider key.
+
+```
+tfconfig/v2
+├── strip_index() (function)
+├── providers
+│ └── (indexed by [module_address:]provider[.alias])
+│ ├── provider_config_key (string)
+│ ├── name (string)
+│ ├── full_name (string)
+│ ├── alias (string)
+│ ├── module_address (string)
+│ ├── config (block expression representation)
+│ └── version_constraint (string)
+├── resources
+│ └── (indexed by address)
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── provider_config_key (string)
+│ ├── provisioners (list)
+│ │ └── (ordered provisioners for this resource only)
+│ ├── config (block expression representation)
+│ ├── count (expression representation)
+│ ├── for_each (expression representation)
+│ └── depends_on (list of strings)
+├── provisioners
+│ └── (indexed by resource_address:index)
+│ ├── resource_address (string)
+│ ├── type (string)
+│ ├── index (string)
+│ └── config (block expression representation)
+├── variables
+│ └── (indexed by module_address:name)
+│ ├── module_address (string)
+│ ├── name (string)
+│ ├── default (value)
+│ └── description (string)
+├── outputs
+│ └── (indexed by module_address:name)
+│ ├── module_address (string)
+│ ├── name (string)
+│ ├── sensitive (boolean)
+│ ├── value (expression representation)
+│ ├── description (string)
+│ └── depends_on (list of strings)
+└── module_calls
+ └── (indexed by module_address:name)
+ ├── module_address (string)
+ ├── name (string)
+ ├── source (string)
+ ├── config (block expression representation)
+ ├── count (expression representation)
+ ├── depends_on (expression representation)
+ ├── for_each (expression representation)
+ └── version_constraint (string)
+```
+
+The collections are:
+
+* [`providers`](#the-providers-collection) - The configuration for all provider
+ instances across all modules in the configuration.
+* [`resources`](#the-resources-collection) - The configuration of all resources
+ across all modules in the configuration.
+* [`variables`](#the-variables-collection) - The configuration of all variable
+ definitions across all modules in the configuration.
+* [`outputs`](#the-outputs-collection) - The configuration of all output
+ definitions across all modules in the configuration.
+* [`module_calls`](#the-module_calls-collection) - The configuration of all module
+ calls (individual [`module`](/terraform/language/modules) blocks) across
+ all modules in the configuration.
+
+These collections are specifically designed to be used with the
+[`filter`](/sentinel/language/collection-operations#filter-expression)
+quantifier expression in Sentinel, so that one can collect a list of resources
+to perform policy checks on without having to write complex module or
+configuration traversal. As an example, the following code will return all
+`aws_instance` resource types within the configuration, regardless of what
+module they are in:
+
+```
+all_aws_instances = filter tfconfig.resources as _, r {
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+You can add specific attributes to the filter to narrow the search, such as the
+module address. The following code would return resources in a module named
+`foo` only:
+
+```
+all_aws_instances = filter tfconfig.resources as _, r {
+ r.module_address is "module.foo" and
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+### Address Differences Between `tfconfig`, `tfplan`, and `tfstate`
+
+This import deals with configuration before it is expanded into a
+resource graph by Terraform. As such, it is not possible to compute an index as
+the import is building its collections and computing addresses for resources and
+modules.
+
+As such, addresses found here may not always match the expanded addresses found
+in the [`tfplan/v2`](/sentinel/features/terraform/tfplan-v2) and [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2)
+imports, specifically when
+[`count`](https://developer.hashicorp.com/terraform/language/resources#count-multiple-resource-instances-by-count)
+and
+[`for_each`](https://developer.hashicorp.com/terraform/language/resources#for_each-multiple-resource-instances-defined-by-a-map-or-set-of-strings),
+are used.
+
+As an example, consider a resource named `null_resource.foo` with a count of `2`
+located in a module named `bar`. While there will possibly be entries in the
+other imports for `module.bar.null_resource.foo[0]` and
+`module.bar.null_resource.foo[1]`, in `tfconfig/v2`, there will only be a
+`module.bar.null_resource.foo`. As mentioned in the start of this section, this
+is because configuration actually _defines_ this scaling, whereas _expansion_
+actually happens when the resource graph is built, which happens as a natural
+part of the refresh and planning process.
+
+The `strip_index` helper function, found in this import, can assist in
+removing the indexes from addresses found in the `tfplan/v2` and `tfstate/v2`
+imports so that data from those imports can be used to reference data in this
+one.
+
+## The `strip_index` Function
+
+The `strip_index` helper function can be used to remove indexes from addresses
+found in [`tfplan/v2`](/sentinel/features/terraform/tfplan-v2) and [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2),
+by removing the indexes from each resource.
+
+This can be used to help facilitate cross-import lookups for data between plan,
+state, and config.
+
+```
+import "tfconfig/v2" as tfconfig
+import "tfplan/v2" as tfplan
+
+main = rule {
+ all filter tfplan.resource_changes as _, rc {
+ rc.mode is "managed" and
+ rc.type is "aws_instance"
+ } as _, rc {
+ tfconfig.resources[tfconfig.strip_index(rc.address)].config.ami.constant_value is "ami-abcdefgh012345"
+ }
+}
+```
+
+## Expression Representations
+
+Most collections in this import will have one of two kinds of _expression
+representations_. This is a verbose format for expressing a (parsed)
+configuration value independent of the configuration source code, which is not
+100% available to a policy check in Terraform Cloud.
+
+```
+(expression representation)
+├── constant_value (value)
+└── references (list of strings)
+```
+
+There are two major parts to an expression representation:
+
+* Any _strictly constant value_ is expressed as an expression with a
+ `constant_value` field.
+* Any expression that requires some degree of evaluation to generate the final
+ value - even if that value is known at plan time - is not expressed in
+ configuration. Instead, any particular references that are made are added to
+ the `references` field. More details on this field can be found in the
+ [expression
+ representation](https://developer.hashicorp.com/terraform/internals/json-format#expression-representation)
+ section of the JSON output format documentation.
+
+For example, to determine if an output is based on a particular
+resource value, one could do:
+
+```
+import "tfconfig/v2" as tfconfig
+
+main = rule {
+ tfconfig.outputs["instance_id"].value.references is ["aws_instance.foo"]
+}
+```
+
+-> **Note:** The representation does not account for
+complex interpolations or other expressions that combine constants with other
+expression data. For example, the partially constant data in `"foo${var.bar}"` would be lost.
+
+### Block Expression Representation
+
+Expanding on the above, a multi-value expression representation (such as the
+kind found in a [`resources`](#the-resources-collection) collection element) is
+similar, but the root value is a keyed map of expression representations. This
+is repeated until a "scalar" expression value is encountered, ie: a field that
+is not a block in the resource's schema.
+
+```
+(block expression representation)
+└── (attribute key)
+ ├── (child block expression representation)
+ │ └── (...)
+ ├── constant_value (value)
+ └── references (list of strings)
+```
+
+As an example, one can validate expressions in an
+[`aws_instance`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance) resource using the
+following:
+
+```
+import "tfconfig/v2" as tfconfig
+
+main = rule {
+ tfconfig.resources["aws_instance.foo"].config.ami.constant_value is "ami-abcdefgh012345"
+}
+```
+
+Note that _nested blocks_, sometimes known as _sub-resources_, will be nested in
+configuration as as list of blocks (reflecting their ultimate nature as a list
+of objects). An example would be the `aws_instance` resource's
+[`ebs_block_device`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#ebs-ephemeral-and-root-block-devices) block:
+
+```
+import "tfconfig/v2" as tfconfig
+
+main = rule {
+ tfconfig.resources["aws_instance.foo"].config.ebs_block_device[0].volume_size < 10
+}
+```
+
+## The `providers` Collection
+
+The `providers` collection is a collection representing the configurations of
+all provider instances across all modules in the configuration.
+
+This collection is indexed by an opaque key. This is currently
+`module_address:provider.alias`, the same value as found in the
+`provider_config_key` field. `module_address` and the colon delimiter are
+omitted for the root module.
+
+The `provider_config_key` field is also found in the `resources` collection and
+can be used to locate a provider that belongs to a configured resource.
+
+The fields in this collection are as follows:
+
+* `provider_config_key` - The opaque configuration key, used as the index key.
+* `name` - The name of the provider, ie: `aws`.
+* `full_name` - The fully-qualified name of the provider, e.g. `registry.terraform.io/hashicorp/aws`.
+* `alias` - The alias of the provider, ie: `east`. Empty for a default provider.
+* `module_address` - The address of the module this provider appears in.
+* `config` - A [block expression
+ representation](#block-expression-representation) with provider configuration
+ values.
+* `version_constraint` - The defined version constraint for this provider.
+
+## The `resources` Collection
+
+The `resources` collection is a collection representing all of the resources
+found in all modules in the configuration.
+
+This collection is indexed by the resource address.
+
+The fields in this collection are as follows:
+
+* `address` - The resource address. This is the index of the collection.
+* `module_address` - The module address that this resource was found in.
+* `mode` - The resource mode, either `managed` (resources) or `data` (data
+ sources).
+* `type` - The type of resource, ie: `null_resource` in `null_resource.foo`.
+* `name` - The name of the resource, ie: `foo` in `null_resource.foo`.
+* `provider_config_key` - The opaque configuration key that serves as the index
+ of the [`providers`](#the-providers-collection) collection.
+* `provisioners` - The ordered list of provisioners for this resource. The
+ syntax of the provisioners matches those found in the
+ [`provisioners`](#the-provisioners-collection) collection, but is a list
+ indexed by the order the provisioners show up in the resource.
+* `config` - The [block expression
+ representation](#block-expression-representation) of the configuration values
+ found in the resource.
+* `count` - The [expression data](#expression-representations) for the `count`
+ value in the resource.
+* `for_each` - The [expression data](#expression-representations) for the
+ `for_each` value in the resource.
+* `depends_on` - The contents of the `depends_on` config directive, which
+ declares explicit dependencies for this resource.
+
+## The `provisioners` Collection
+
+The `provisioners` collection is a collection of all of the provisioners found
+across all resources in the configuration.
+
+While normally bound to a resource in an ordered fashion, this collection allows
+for the filtering of provisioners within a single expression.
+
+This collection is indexed with a key following the format
+`resource_address:index`, with each field matching their respective field in the
+particular element below:
+
+* `resource_address`: The address of the resource that the provisioner was found
+ in. This can be found in the [`resources`](#the-resources-collection)
+ collection.
+* `type`: The provisioner type, ie: `local_exec`.
+* `index`: The provisioner index as it shows up in the resource provisioner
+ order.
+* `config`: The [block expression
+ representation](#block-expression-representation) of the configuration values
+ in the provisioner.
+
+## The `variables` Collection
+
+The `variables` collection is a collection of all variables across all modules
+in the configuration.
+
+Note that this tracks variable definitions, not values. See the [`tfplan/v2`
+`variables` collection](/sentinel/features/terraform/tfplan-v2#the-variables-collection) for variable
+values set within a plan.
+
+This collection is indexed by the key format `module_address:name`, with each
+field matching their respective name below. `module_address` and the colon
+delimiter are omitted for the root module.
+
+* `module_address` - The address of the module the variable was found in.
+* `name` - The name of the variable.
+* `default` - The defined default value of the variable.
+* `description` - The description of the variable.
+
+## The `outputs` Collection
+
+The `outputs` collection is a collection of all outputs across all modules in
+the configuration.
+
+Note that this tracks variable definitions, not values. See the [`tfstate/v2`
+`outputs` collection](/sentinel/features/terraform/tfstate-v2#the-outputs-collection) for the final
+values of outputs set within a state. The [`tfplan/v2` `output_changes`
+collection](/sentinel/features/terraform/tfplan-v2#the-output_changes-collection) also contains a more
+complex collection of planned output changes.
+
+This collection is indexed by the key format `module_address:name`, with each
+field matching their respective name below. `module_address` and the colon
+delimiter are omitted for the root module.
+
+* `module_address` - The address of the module the output was found in.
+* `name` - The name of the output.
+* `sensitive` - Indicates whether or not the output was marked as
+ [`sensitive`](https://developer.hashicorp.com/terraform/language/values/outputs#sensitive-suppressing-values-in-cli-output).
+* `value` - An [expression representation](#expression-representations) for the output.
+* `description` - The description of the output.
+* `depends_on` - A list of resource names that the output depends on. These are
+ the hard-defined output dependencies as defined in the
+ [`depends_on`](https://developer.hashicorp.com/terraform/language/values/outputs#depends_on-explicit-output-dependencies)
+ field in an output declaration, not the dependencies that get derived from
+ natural evaluation of the output expression (these can be found in the
+ `references` field of the expression representation).
+
+## The `module_calls` Collection
+
+The `module_calls` collection is a collection of all module declarations at all
+levels within the configuration.
+
+Note that this is the
+[`module`](https://developer.hashicorp.com/terraform/language/modules#calling-a-child-module) stanza in
+any particular configuration, and not the module itself. Hence, a declaration
+for `module.foo` would actually be declared in the root module, which would be
+represented by a blank field in `module_address`.
+
+This collection is indexed by the key format `module_address:name`, with each
+field matching their respective name below. `module_address` and the colon
+delimiter are omitted for the root module.
+
+* `module_address` - The address of the module the declaration was found in.
+* `name` - The name of the module.
+* `source` - The contents of the `source` field.
+* `config` - A [block expression
+ representation](#block-expression-representation) for all parameter values
+ sent to the module.
+* `count` - An [expression representation](#expression-representations) for the
+ `count` field.
+* `depends_on`: An [expression representation](#expression-representations) for the
+ `depends_on` field.
+* `for_each` - An [expression representation](#expression-representations) for
+ the `for_each` field.
+* `version_constraint` - The string value found in the `version` field of the
+ module declaration.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/features/terraform/tfplan-v1.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/features/terraform/tfplan-v1.mdx
new file mode 100644
index 0000000000..0e0f9436a5
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/features/terraform/tfplan-v1.mdx
@@ -0,0 +1,610 @@
+---
+page_title: tfplan/v1 - terraform - Features
+sidebar_current: docs-features-terraform-tfplan-v1
+description: >-
+ The tfplan/v1 import provides access to a Terraform plan. A Terraform plan is the
+ file created as a result of `terraform plan` and is the input to `terraform
+ apply`. The plan represents the changes that Terraform needs to make to
+ infrastructure to reach the desired state represented by the configuration.
+layout: docs
+---
+
+# Import: tfplan/v1
+
+The `tfplan/v1` import provides access to a Terraform plan. A Terraform plan is the
+file created as a result of `terraform plan` and is the input to `terraform
+apply`. The plan represents the changes that Terraform needs to make to
+infrastructure to reach the desired state represented by the configuration.
+
+In addition to the diff data available in the plan, there is an
+[`applied`](#value-applied) state available that merges the plan with the state
+to create the planned state after apply.
+
+Finally, this import also allows you to access the configuration files and the
+Terraform state at the time the plan was run. See the section on [accessing a
+plan's state and configuration
+data](#accessing-a-plan-39-s-state-and-configuration-data) for more information.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan and schemas file.
+
+```hcl
+import "plugin" "tfplan/v1" {
+ config = {
+ "plan_path": "./path/to/plan.json",
+ "schemas_path": "./path/to/schemas.json"
+ }
+}
+```
+
+## Namespace Overview
+
+The following is a tree view of the import namespace. For more detail on a
+particular part of the namespace, see below.
+
+-> Note that the root-level alias keys shown here (`data`, `path`, and
+`resources`) are shortcuts to a [module namespace](#namespace-module) scoped to
+the root module. For more details, see the section on [root namespace
+aliases](#root-namespace-aliases).
+
+```
+tfplan/v1
+├── module() (function)
+│ └── (module namespace)
+│ ├── path ([]string)
+│ ├── data
+│ │ └── TYPE.NAME[NUMBER]
+│ │ ├── applied (map of keys)
+│ │ └── diff
+│ │ └── KEY
+│ │ ├── computed (bool)
+│ │ ├── new (string)
+│ │ └── old (string)
+│ └── resources
+│ └── TYPE.NAME[NUMBER]
+│ ├── applied (map of keys)
+│ ├── destroy (bool)
+│ ├── requires_new (bool)
+│ └── diff
+│ └── KEY
+│ ├── computed (bool)
+│ ├── new (string)
+│ └── old (string)
+├── module_paths ([][]string)
+├── terraform_version (string)
+├── variables (map of keys)
+│
+├── data (root module alias)
+├── path (root module alias)
+├── resources (root module alias)
+│
+├── config (tfconfig namespace alias)
+└── state (tfstate import alias)
+```
+
+## Namespace: Root
+
+The root-level namespace consists of the values and functions documented below.
+
+In addition to this, the root-level `data`, `path`, and `resources` keys alias
+to their corresponding namespaces or values within the [module
+namespace](#namespace-module).
+
+### Accessing a Plan's State and Configuration Data
+
+The `config` and `state` keys alias to the [`tfconfig`](/sentinel/features/terraform/tfconfig-v1) and
+[`tfstate`](/sentinel/features/terraform/tfstate-v1) namespaces, respectively, with the data sourced from
+the Terraform _plan_ (as opposed to actual configuration and state).
+
+-> Note that these aliases are not represented as maps. While they will appear
+empty when viewed as maps, the specific import namespace keys will still be
+accessible.
+
+### Function: `module()`
+
+```
+module = func(ADDR)
+```
+
+* **Return Type:** A [module namespace](#namespace-module).
+
+The `module()` function in the [root namespace](#namespace-root) returns the
+[module namespace](#namespace-module) for a particular module address.
+
+The address must be a list and is the module address, split on the period (`.`),
+excluding the root module.
+
+Hence, a module with an address of simply `foo` (or `root.foo`) would be
+`["foo"]`, and a module within that (so address `foo.bar`) would be read as
+`["foo", "bar"]`.
+
+[`null`](/sentinel/language/spec#null) is returned if a module address is
+invalid, or if the module is not present in the diff.
+
+As an example, given the following module block:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+If the module contained the following content:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.module(["foo"]).resources.null_resource.foo[0].applied.triggers.foo is "bar" }
+```
+
+### Value: `module_paths`
+
+* **Value Type:** List of a list of strings.
+
+The `module_paths` value within the [root namespace](#namespace-root) is a list
+of all of the modules within the Terraform diff for the current plan.
+
+Modules not present in the diff will not be present here, even if they are
+present in the configuration or state.
+
+This data is represented as a list of a list of strings, with the inner list
+being the module address, split on the period (`.`).
+
+The root module is included in this list, represented as an empty inner list, as
+long as there are changes.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The value of `module_paths` would be:
+
+```
+[
+ [],
+ ["foo"],
+]
+```
+
+And the following policy would evaluate to `true`:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.module_paths contains ["foo"] }
+```
+
+-> Note the above example only applies if the module is present in the diff.
+
+#### Iterating Through Modules
+
+Iterating through all modules to find particular resources can be useful. This
+[example][iterate-over-modules] shows how to use `module_paths` with the
+[`module()` function](#function-module-) to find all resources of a
+particular type from all modules that have pending changes using the `tfplan/v1`
+import.
+
+[iterate-over-modules]: https://developer.hashicorp.com/terraform/cloud-docs/policy-enforcement/sentinel#iterate-over-modules-and-find-resources
+
+### Value: `terraform_version`
+
+* **Value Type:** String.
+
+The `terraform_version` value within the [root namespace](#namespace-root)
+represents the version of Terraform used to create the plan. This can be used to
+enforce a specific version of Terraform in a policy check.
+
+As an example, the following policy would evaluate to `true`, as long as the
+plan was made with a version of Terraform in the 0.11.x series, excluding any
+pre-release versions (example: `-beta1` or `-rc1`):
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.terraform_version matches "^0\\.11\\.\\d+$" }
+```
+
+### Value: `variables`
+
+* **Value Type:** A string-keyed map of values.
+
+The `variables` value within the [root namespace](#namespace-root) represents
+all of the variables that were set when creating the plan. This will only
+contain variables set for the root module.
+
+Note that unlike the [`default`][import-tfconfig-variables-default] value in the
+[`tfconfig` variables namespace][import-tfconfig-variables], primitive values
+here are stringified, and type conversion will need to be performed to perform
+comparison for int, float, or boolean values. This only applies to variables
+that are primitives themselves and not primitives within maps and lists, which
+will be their original types.
+
+[import-tfconfig-variables-default]: /sentinel/features/terraform/tfconfig-v1#value-default
+
+[import-tfconfig-variables]: /sentinel/features/terraform/tfconfig-v1#namespace-variables
+
+If a default was accepted for the particular variable, the default value will be
+populated here.
+
+As an example, given the following variable blocks:
+
+```hcl
+variable "foo" {
+ default = "bar"
+}
+
+variable "number" {
+ default = 42
+}
+
+variable "map" {
+ default = {
+ foo = "bar"
+ number = 42
+ }
+}
+```
+
+The following policy would evaluate to `true`, if no values were entered to
+change these variables:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+default_foo = rule { tfplan.variables.foo is "bar" }
+default_number = rule { tfplan.variables.number is "42" }
+default_map_string = rule { tfplan.variables.map["foo"] is "bar" }
+default_map_int = rule { tfplan.variables.map["number"] is 42 }
+
+main = rule { default_foo and default_number and default_map_string and default_map_int }
+```
+
+## Namespace: Module
+
+The **module namespace** can be loaded by calling
+[`module()`](#function-module-) for a particular module.
+
+It can be used to load the following child namespaces, in addition to the values
+documented below:
+
+* `data` - Loads the [resource namespace](#namespace-resources-data-sources),
+ filtered against data sources.
+* `resources` - Loads the [resource
+ namespace](#namespace-resources-data-sources), filtered against resources.
+
+### Root Namespace Aliases
+
+The root-level `data` and `resources` keys both alias to their corresponding
+namespaces within the module namespace, loaded for the root module. They are the
+equivalent of running `module([]).KEY`.
+
+### Value: `path`
+
+* **Value Type:** List of strings.
+
+The `path` value within the [module namespace](#namespace-module) contains the
+path of the module that the namespace represents. This is represented as a list
+of strings.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The following policy would evaluate to `true` _only_ if the diff had changes for
+that module:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.module(["foo"]).path contains "foo" }
+```
+
+## Namespace: Resources/Data Sources
+
+The **resource namespace** is a namespace _type_ that applies to both resources
+(accessed by using the `resources` namespace key) and data sources (accessed
+using the `data` namespace key).
+
+Accessing an individual resource or data source within each respective namespace
+can be accomplished by specifying the type, name, and resource number (as if the
+resource or data source had a `count` value in it) in the syntax
+`[resources|data].TYPE.NAME[NUMBER]`. Note that NUMBER is always needed, even if
+you did not use `count` in the resource.
+
+In addition, each of these namespace levels is a map, allowing you to filter
+based on type and name.
+
+-> The (somewhat strange) notation here of `TYPE.NAME[NUMBER]` may imply that
+the inner resource index map is actually a list, but it's not - using the square
+bracket notation over the dotted notation (`TYPE.NAME.NUMBER`) is required here
+as an identifier cannot start with a number.
+
+Some examples of multi-level access are below:
+
+* To fetch all `aws_instance.foo` resource instances within the root module, you
+ can specify `tfplan.resources.aws_instance.foo`. This would then be indexed by
+ resource count index (`0`, `1`, `2`, and so on). Note that as mentioned above,
+ these elements must be accessed using square-bracket map notation (so `[0]`,
+ `[1]`, `[2]`, and so on) instead of dotted notation.
+* To fetch all `aws_instance` resources within the root module, you can specify
+ `tfplan.resources.aws_instance`. This would be indexed from the names of
+ each resource (`foo`, `bar`, and so on), with each of those maps containing
+ instances indexed by resource count index as per above.
+* To fetch all resources within the root module, irrespective of type, use
+ `tfplan.resources`. This is indexed by type, as shown above with
+ `tfplan.resources.aws_instance`, with names being the next level down, and so
+ on.
+
+~> When [resource targeting](https://developer.hashicorp.com/terraform/cli/commands/plan#resource-targeting) is
+ in effect, `tfplan.resources` will only include the resources specified as
+ targets for the run. This may lead to unexpected outcomes if a policy expects
+ a resource to be present in the plan.
+
+Further explanation of the namespace will be in the context of resources. As
+mentioned, when operating on data sources, use the same syntax, except with
+`data` in place of `resources`.
+
+### Value: `applied`
+
+* **Value Type:** A string-keyed map of values.
+
+The `applied` value within the [resource
+namespace](#namespace-resources-data-sources) contains a "predicted"
+representation of the resource's state post-apply. It's created by merging the
+pending resource's diff on top of the existing data from the resource's state
+(if any). The map is a complex representation of these values with data going
+as far down as needed to represent any state values such as maps, lists, and
+sets.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if the resource was in the diff:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].applied.triggers.foo is "bar" }
+```
+
+-> Note that some values will not be available in the `applied` state because
+they cannot be known until the plan is actually applied. In Terraform 0.11 or
+earlier, these values are represented by a placeholder (the UUID value
+`74D93920-ED26-11E3-AC10-0800200C9A66`) and in Terraform 0.12 or later they
+are `undefined`. **In either case**, you should instead use the
+[`computed`](#value-computed) key within the [diff
+namespace](#namespace-resource-diff) to determine that a computed value will
+exist.
+
+-> If a resource is being destroyed, its `applied` value is omitted from the
+namespace and trying to fetch it will return undefined.
+
+### Value: `diff`
+
+* **Value Type:** A map of [diff namespaces](#namespace-resource-diff).
+
+The `diff` value within the [resource
+namespace](#namespace-resources-data-sources) contains the diff for a particular
+resource. Each key within the map links to a [diff
+namespace](#namespace-resource-diff) for that particular key.
+
+Note that unlike the [`applied`](#value-applied) value, this map is not complex;
+the map is only 1 level deep with each key possibly representing a diff for a
+particular complex value within the resource.
+
+See the below section for more details on the diff namespace, in addition to
+usage examples.
+
+### Value: `destroy`
+
+* **Value Type:** Boolean.
+
+The `destroy` value within the [resource
+namespace](#namespace-resources-data-sources) is `true` if a resource is being
+destroyed for _any_ reason, including cases where it's being deleted as part of
+a resource re-creation, in which case [`requires_new`](#value-requires_new) will
+also be set.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` when `null_resource.foo` is being
+destroyed:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].destroy }
+```
+
+### Value: `requires_new`
+
+* **Value Type:** Boolean.
+
+The `requires_new` value within the [resource
+namespace](#namespace-resources-data-sources) is `true` if the resource is still
+present in the configuration, but must be replaced to satisfy its current diff.
+Whenever `requires_new` is `true`, [`destroy`](#value-destroy) is also `true`.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if one of the `triggers` in
+`null_resource.foo` was being changed:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].requires_new }
+```
+
+## Namespace: Resource Diff
+
+The **diff namespace** is a namespace that represents the diff for a specific
+attribute within a resource. For details on reading a particular attribute,
+see the [`diff`](#value-diff) value in the [resource
+namespace](#namespace-resources-data-sources).
+
+### Value: `computed`
+
+* **Value Type:** Boolean.
+
+The `computed` value within the [diff namespace](#namespace-resource-diff) is
+`true` if the resource key in question depends on another value that isn't yet
+known. Typically, that means the value it depends on belongs to a resource that
+either doesn't exist yet, or is changing state in such a way as to affect the
+dependent value so that it can't be known until the apply is complete.
+
+-> Keep in mind that when using `computed` with complex structures such as maps,
+lists, and sets, it's sometimes necessary to test the count attribute for the
+structure, versus a key within it, depending on whether or not the diff has
+marked the whole structure as computed. This is demonstrated in the example
+below. Count keys are `%` for maps, and `#` for lists and sets. If you are
+having trouble determining the type of specific field within a resource, contact
+the support team.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+
+resource "null_resource" "bar" {
+ triggers = {
+ foo_id = "${null_resource.foo.id}"
+ }
+}
+```
+
+The following policy would evaluate to `true`, if the `id` of
+`null_resource.foo` was currently not known, such as when the resource is
+pending creation, or is being deleted and re-created:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.bar[0].diff["triggers.%"].computed }
+```
+
+### Value: `new`
+
+* **Value Type:** String.
+
+The `new` value within the [diff namespace](#namespace-resource-diff) contains
+the new value of a changing attribute, _if_ the value is known at plan time.
+
+-> `new` will be an empty string if the attribute's value is currently unknown.
+For more details on detecting unknown values, see [`computed`](#value-computed).
+
+Note that this value is _always_ a string, regardless of the actual type of the
+value changing. [Type conversion][ref-sentinel-type-conversion] within policy
+may be necessary to achieve the comparison needed.
+
+[ref-sentinel-type-conversion]: https://docs.hashicorp.com/sentinel/language/values#type-conversion
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`, if the resource was in the diff
+and each of the concerned keys were changing to new values:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].diff["triggers.foo"].new is "bar" }
+```
+
+### Value: `old`
+
+* **Value Type:** String.
+
+The `old` value within the [diff namespace](#namespace-resource-diff) contains
+the old value of a changing attribute.
+
+Note that this value is _always_ a string, regardless of the actual type of the
+value changing. [Type conversion][ref-sentinel-type-conversion] within policy
+may be necessary to achieve the comparison needed.
+
+If the value did not exist in the previous state, `old` will always be an empty
+string.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "baz"
+ }
+}
+```
+
+If that resource was previously in config as:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].diff["triggers.foo"].old is "bar" }
+```
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/features/terraform/tfplan-v2.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/features/terraform/tfplan-v2.mdx
new file mode 100644
index 0000000000..61fb7b2bf2
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/features/terraform/tfplan-v2.mdx
@@ -0,0 +1,402 @@
+---
+page_title: tfplan/v2 - terraform - Features
+sidebar_current: docs-features-terraform-tfplan-v2
+description: >-
+ The tfplan import provides access to a Terraform plan. A Terraform plan is the
+ file created as a result of `terraform plan` and is the input to `terraform
+ apply`. The plan represents the changes that Terraform needs to make to
+ infrastructure to reach the desired state represented by the configuration.
+layout: docs
+---
+
+# Import: tfplan/v2
+
+The `tfplan/v2` import provides access to a Terraform plan.
+
+A Terraform plan is the file created as a result of `terraform plan` and is the
+input to `terraform apply`. The plan represents the changes that Terraform needs
+to make to infrastructure to reach the desired state represented by the
+configuration.
+
+In addition to the diff data available in the plan, there is a "planned state"
+that is available through this import, via the
+[`planned_values`](#the-planned_values-collection) collection. This collection
+presents the Terraform state as how it might look after the plan data is
+applied, but is not guaranteed to be the final state.
+
+The data in the `tfplan/v2` import is sourced from the JSON configuration file
+that is generated by the [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) command. For more information on
+the file format, see the [JSON Output Format](https://developer.hashicorp.com/terraform/internals/json-format)
+page.
+
+The entirety of the JSON output file is exposed as a Sentinel map via the
+[`raw`](#the-raw-collection) collection. This allows direct, low-level access to
+the JSON data, but should only be used in complex situations where the
+higher-level collections do not serve the purpose.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfplan/v2" {
+ config = {
+ "plan_path": "./path/to/plan.json"
+ }
+}
+```
+
+## Import Overview
+
+The `tfplan/v2` import is structured as a series of _collections_, keyed as a
+specific format depending on the collection.
+
+```
+tfplan/v2
+├── terraform_version (string)
+├── variables
+│ └── (indexed by name)
+│ ├── name (string)
+│ └── value (value)
+├── planned_values
+│ ├── outputs (tfstate/v2 outputs representation)
+│ └── resources (tfstate/v2 resources representation)
+├── resource_changes
+│ └── (indexed by address[:deposed])
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── index (float (number) or string)
+│ ├── provider_name (string)
+│ ├── deposed (string)
+│ └── change (change representation)
+├── resource_drift
+│ └── (indexed by address[:deposed])
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── index (float (number) or string)
+│ ├── provider_name (string)
+│ ├── deposed (string)
+│ └── change (change representation)
+├── output_changes
+│ └── (indexed by name)
+│ ├── name (string)
+│ └── change (change representation)
+└── raw (map)
+```
+
+The collections are:
+
+* [`variables`](#the-variables-collection) - The values of variables that have
+ been set in the plan itself. This collection only contains variables set in
+ the root module.
+* [`planned_values`](#the-planned_values-collection) - The state representation
+ of _planned values_, or an estimation of what the state will look like after
+ the plan is applied.
+* [`resource_changes`](#the-resource_changes-and-resource_drift-collections) - The set of change
+ operations for resources and data sources within this plan.
+* [`resource_drift`](#the-resource_changes-and-resource_drift-collections) - A description of the
+ changes Terraform detected when it compared the most recent state to the prior saved state.
+* [`output_changes`](#the-output_changes-collection) - The changes to outputs
+ within this plan. This collection only contains outputs set in the root
+ module.
+* [`raw`](#the-raw-collection) - Access to the raw plan data.
+
+These collections are specifically designed to be used with the
+[`filter`](/sentinel/language/collection-operations#filter-expression)
+quantifier expression in Sentinel, so that one can collect a list of resources
+to perform policy checks on without having to write complex discovery code. As
+an example, the following code will return all `aws_instance` resource changes,
+across all modules in the plan:
+
+```
+all_aws_instances = filter tfplan.resource_changes as _, rc {
+ rc.mode is "managed" and
+ rc.type is "aws_instance"
+}
+```
+
+You can add specific attributes to the filter to narrow the search, such as the
+module address, or the operation being performed. The following code would
+return resources in a module named `foo` only, and further narrow the search
+down to only resources that were being created:
+
+```
+all_aws_instances = filter tfplan.resource_changes as _, rc {
+ rc.module_address is "module.foo" and
+ rc.mode is "managed" and
+ rc.type is "aws_instance" and
+ rc.change.actions is ["create"]
+}
+```
+
+### Change Representation
+
+Certain collections in this import contain a _change representation_, an object
+with details about changes to a particular entity, such as a resource (within
+the [`resource_changes`](#the-resource_changes-collection) collection), or
+output (within the [`output_changes`](#the-output_changes-collection)
+collection).
+
+```
+(change representation)
+├── actions (list)
+├── before (value, or map)
+├── after (value, or map)
+└── after_unknown (boolean, or map of booleans)
+```
+
+This change representation contains the following fields:
+
+* `actions` - A list of actions being carried out for this change. The order is
+ important, for example a regular replace operation is denoted by `["delete",
+ "create"]`, but a
+ [`create_before_destroy`](https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle#create_before_destroy)
+ resource will have an operation order of `["create", "delete"]`.
+* `before` - The representation of the resource data object value before the
+ action. For create-only actions, this is unset. For no-op actions, this value
+ will be identical with `after`.
+* `after` - The representation of the resource data object value after the
+ action. For delete-only actions, this is unset. For no-op actions, this value
+ will be identical with `before`. Note that unknown values will not show up in
+ this field.
+* `after_unknown` - A deep object of booleans that denotes any values that are
+ unknown in a resource. These values were previously referred to as "computed"
+ values. If the value cannot be found in this map, then its value should be
+ available within `after`, so long as the operation supports it.
+
+#### Actions
+
+As mentioned above, actions show up within the `actions` field of a change
+representation and indicate the type of actions being performed as part of the
+change, and the order that they are being performed in.
+
+The current list of actions are as follows:
+
+* `create` - The action will create the associated entity. Depending on the
+ order this appears in, the entity may be created alongside a copy of the
+ entity before replacing it.
+* `read` - The action will read the associated entity. In practice, seeing this
+ change type should be rare, as reads generally happen before a plan is
+ executed (usually during a refresh).
+* `update` - The action will update the associated entity in a way that alters its state
+ in some way.
+* `delete` - The action will remove the associated entity, deleting any
+ applicable state and associated real resources or infrastructure.
+* `no-op` - No action will be performed on the associated entity.
+
+The `actions` field is a list, as some real-world actions are actually a
+composite of more than one primitive action. At this point in time, this
+is generally only applicable to resource replacement, in which the following
+action orders apply:
+
+* **Normal replacement:** `["delete", "create"]` - Applies to default lifecycle
+ configurations.
+* **Create-before-destroy:** `["create", "delete"]` - Applies when
+ [`create_before_destroy`](https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle#create_before_destroy)
+ is used in a lifecycle configuration.
+
+Note that, in most situations, the plan will list all "changes", including no-op
+changes. This makes filtering on change type crucial to the accurate selection
+of data if you are concerned with the state change of a particular resource.
+
+To filter on a change type, use exact list comparison. For example, the
+following example from the [Import Overview](#import-overview) filters on
+exactly the resources being created _only_:
+
+```
+all_aws_instances = filter tfplan.resource_changes as _, rc {
+ rc.module_address is "module.foo" and
+ rc.mode is "managed" and
+ rc.type is "aws_instance" and
+ rc.change.actions is ["create"]
+}
+```
+
+#### `before`, `after`, and `after_unknown`
+
+The exact attribute changes for a particular operation are outlined in the
+`before` and `after` attributes. Depending on the entity being operated on, this
+will either be a map (as with
+[`resource_changes`](#the-resource_changes-collection)) or a singular value (as
+with [`output_changes`](#the-output_changes-collection)).
+
+What you can expect in these fields varies depending on the operation:
+
+* For fresh create operations, `before` will generally be `null`, and `after`
+ will contain the data you can expect to see after the change.
+* For full delete operations, this will be reversed - `before` will contain
+ data, and `after` will be `null`.
+* Update or replace operations will have data in both fields relevant to their
+ states before and after the operation.
+* No-op operations should have identical data in `before` and `after`.
+
+For resources, if a field cannot be found in `after`, it generally means one of
+two things:
+
+* The attribute does not exist in the resource schema. Generally, known
+ attributes that do not have a value will show up as `null` or otherwise empty
+ in `after`.
+* The attribute is _unknown_, that is, it was unable to be determined at plan
+ time and will only be available after apply-time values have been able to be
+ calculated.
+
+In the latter case, there should be a value for the particular attribute in
+`after_unknown`, which can be checked to assert that the value is indeed
+unknown, versus invalid:
+
+```
+import "tfplan/v2" as tfplan
+
+no_unknown_amis = rule {
+ all filter tfplan.resource_changes as _, rc {
+ rc.module_address is "module.foo" and
+ rc.mode is "managed" and
+ rc.type is "aws_instance" and
+ rc.change.actions is ["create"]
+ } as _, rc {
+ rc.change.after_unknown.ami else false is false
+ }
+}
+```
+
+For output changes, `after_unknown` will simply be `true` if the value won't be
+known until the plan is applied.
+
+## The `terraform_version` Value
+
+The top-level `terraform_version` value in this import gives the Terraform
+version that made the plan. This can be used to do version validation.
+
+```
+import "tfplan/v2" as tfplan
+import "strings"
+
+v = strings.split(tfplan.terraform_version, ".")
+version_major = int(v[1])
+version_minor = int(v[2])
+
+main = rule {
+ version_major is 12 and version_minor >= 19
+}
+```
+
+## The `variables` Collection
+
+The `variables` collection is a collection of the variables set in the root
+module when creating the plan.
+
+This collection is indexed on the name of the variable.
+
+The valid values are:
+
+* `name` - The name of the variable, also used as the collection key.
+* `value` - The value of the variable assigned during the plan.
+
+## The `planned_values` Collection
+
+The `planned_values` collection is a special collection in that it contains two
+fields that alias to state collections with the _planned_ state set. This is the
+best prediction of what the state will look like after the plan is executed.
+
+The two fields are:
+
+* `outputs` - The prediction of what output values will look like after the
+ state is applied. For more details on the structure of this collection, see
+ the [`outputs`](/sentinel/features/terraform/tfstate-v2#the-outputs-collection) collection in the
+ [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2) documentation.
+* `resources` - The prediction of what resource values will look like after the
+ state is applied. For more details on the structure of this collection, see
+ the [`resources`](/sentinel/features/terraform/tfstate-v2#the-resources-collection) collection in the
+ [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2) documentation.
+
+-> **NOTE:** Unknown values are omitted from the `planned_values` state
+representations, regardless of whether or not they existed before. Use
+[`resource_changes`](#the-resource_changes-collection) if awareness of unknown
+data is important.
+
+## The `resource_changes` and `resource_drift` Collections
+
+The `resource_changes` and `resource_drift` collections are a set of change operations for resources
+and data sources within this plan.
+
+The `resource_drift` collection provides a description of the changes Terraform detected
+when it compared the most recent state to the prior saved state.
+
+The `resource_changes` collection includes all resources that have been found in the configuration and state,
+regardless of whether or not they are changing.
+
+~> When [resource targeting](/terraform/cli/commands/plan#resource-targeting) is in effect, the `resource_changes` collection will only include the resources specified as targets for the run. This may lead to unexpected outcomes if a policy expects a resource to be present in the plan. To prohibit targeted runs altogether, ensure [`tfrun.target_addrs`](/terraform/cloud-docs/policy-enforcement/sentinel/import/tfrun#value-target_addrs) is undefined or empty.
+
+This collection is indexed on the complete resource address as the key. If
+`deposed` is non-empty, it is appended to the end, and may look something like
+`aws_instance.foo:deposed-abc123`.
+
+An element contains the following fields:
+
+* `address` - The absolute resource address - also the key for the collection's
+ index, if `deposed` is empty.
+
+* `module_address` - The module portion of the absolute resource address.
+
+* `mode` - The resource mode, either `managed` (resources) or `data` (data
+ sources).
+
+* `type` - The resource type, example: `aws_instance` for `aws_instance.foo`.
+
+* `name` - The resource name, example: `foo` for `aws_instance.foo`.
+
+* `index` - The resource index. Can be either a number or a string.
+
+* `provider_name` - The name of the provider this resource belongs to. This
+ allows the provider to be interpreted unambiguously in the unusual situation
+ where a provider offers a resource type whose name does not start with its own
+ name, such as the `googlebeta` provider offering `google_compute_instance`.
+
+ -> **Note:** Starting with Terraform 0.13, the `provider_name` field contains the
+ _full_ source address to the provider in the Terraform Registry. Example:
+ `registry.terraform.io/hashicorp/null` for the null provider.
+
+* `deposed` - An identifier used during replacement operations, and can be used
+ to identify the exact resource being replaced in state.
+
+* `change` - The data describing the change that will be made to this resource.
+ For more details, see [Change Representation](#change-representation).
+
+## The `output_changes` Collection
+
+The `output_changes` collection is a collection of the change operations for
+outputs within this plan.
+
+Only outputs for the root module are included.
+
+This collection is indexed by the name of the output. The fields in a collection
+value are below:
+
+* `name` - The name of the output, also the index key.
+* `change` - The data describing the change that will be made to this output.
+ For more details, see [Change Representation](#change-representation).
+
+## The `raw` Collection
+
+The `raw` collection exposes the raw, unprocessed plan data.
+
+This is the same data that is produced by [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) on the plan file for the run this
+policy check is attached to.
+
+Use of this data is only recommended in expert situations where the data the
+collections present may not exactly serve the needs of the policy. For more
+information on the file format, see the [JSON Output
+Format](https://developer.hashicorp.com/terraform/internals/json-format) page.
+
+-> **NOTE:** Although designed to be relatively stable, the actual makeup for
+the JSON output format is a Terraform CLI concern and as such not managed by
+Sentinel. Use at your own risk, follow the [Terraform CLI
+project](https://github.com/hashicorp/terraform), and watch the file format
+documentation for any changes.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/features/terraform/tfstate-v1.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/features/terraform/tfstate-v1.mdx
new file mode 100644
index 0000000000..9f3487ad08
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/features/terraform/tfstate-v1.mdx
@@ -0,0 +1,552 @@
+---
+page_title: tfstate/v1 - terraform - Features
+sidebar_current: docs-features-terraform-tfstate-v1
+description: The tfstate/v1 import provides access to a Terraform state.
+layout: docs
+---
+
+# Import: tfstate/v1
+
+The `tfstate/v1` import provides access to the Terraform state.
+
+The _state_ is the data that Terraform has recorded about a workspace at a
+particular point in its lifecycle, usually after an apply. You can read more
+general information about how Terraform uses state [here][ref-tf-state].
+
+[ref-tf-state]: https://developer.hashicorp.com/terraform/language/state
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfstate/v1" {
+ config = {
+ "path": "./path/to/plan.json"
+ }
+}
+```
+
+## Namespace Overview
+
+The following is a tree view of the import namespace. For more detail on a
+particular part of the namespace, see below.
+
+-> Note that the root-level alias keys shown here (`data`, `outputs`, `path`,
+and `resources`) are shortcuts to a [module namespace](#namespace-module) scoped
+to the root module. For more details, see the section on [root namespace
+aliases](#root-namespace-aliases).
+
+```
+tfstate/v1
+├── module() (function)
+│ └── (module namespace)
+│ ├── path ([]string)
+│ ├── data
+│ │ └── TYPE.NAME[NUMBER]
+│ │ ├── attr (map of keys)
+│ │ ├── depends_on ([]string)
+│ │ ├── id (string)
+│ │ └── tainted (boolean)
+│ ├── outputs (root module only in TF 0.12 or later)
+│ │ └── NAME
+│ │ ├── sensitive (bool)
+│ │ ├── type (string)
+│ │ └── value (value)
+│ └── resources
+│ └── TYPE.NAME[NUMBER]
+│ ├── attr (map of keys)
+│ ├── depends_on ([]string)
+│ ├── id (string)
+│ └── tainted (boolean)
+│
+├── module_paths ([][]string)
+├── terraform_version (string)
+│
+├── data (root module alias)
+├── outputs (root module alias)
+├── path (root module alias)
+└── resources (root module alias)
+```
+
+## Namespace: Root
+
+The root-level namespace consists of the values and functions documented below.
+
+In addition to this, the root-level `data`, `outputs`, `path`, and `resources`
+keys alias to their corresponding namespaces or values within the [module
+namespace](#namespace-module).
+
+### Function: `module()`
+
+```
+module = func(ADDR)
+```
+
+* **Return Type:** A [module namespace](#namespace-module).
+
+The `module()` function in the [root namespace](#namespace-root) returns the
+[module namespace](#namespace-module) for a particular module address.
+
+The address must be a list and is the module address, split on the period (`.`),
+excluding the root module.
+
+Hence, a module with an address of simply `foo` (or `root.foo`) would be
+`["foo"]`, and a module within that (so address `foo.bar`) would be read as
+`["foo", "bar"]`.
+
+[`null`][ref-null] is returned if a module address is invalid, or if the module
+is not present in the state.
+
+[ref-null]: /sentinel/language/spec#null
+
+As an example, given the following module block:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+If the module contained the following content:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if the resource was present in
+the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.module(["foo"]).resources.null_resource.foo[0].attr.triggers.foo is "bar" }
+```
+
+### Value: `module_paths`
+
+* **Value Type:** List of a list of strings.
+
+The `module_paths` value within the [root namespace](#namespace-root) is a list
+of all of the modules within the Terraform state at plan-time.
+
+Modules not present in the state will not be present here, even if they are
+present in the configuration or the diff.
+
+This data is represented as a list of a list of strings, with the inner list
+being the module address, split on the period (`.`).
+
+The root module is included in this list, represented as an empty inner list, as
+long as it is present in state.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The value of `module_paths` would be:
+
+```
+[
+ [],
+ ["foo"],
+]
+```
+
+And the following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.module_paths contains ["foo"] }
+```
+
+-> Note the above example only applies if the module is present in the state.
+
+#### Iterating Through Modules
+
+Iterating through all modules to find particular resources can be useful. This
+[example][iterate-over-modules] shows how to use `module_paths` with the
+[`module()` function](#function-module-) to find all resources of a
+particular type from all modules using the `tfplan` import. By changing `tfplan`
+in this function to `tfstate`, you could make a similar function find all
+resources of a specific type in the current state.
+
+[iterate-over-modules]: https://developer.hashicorp.com/terraform/cloud-docs/policy-enforcement/sentinel#sentinel-imports
+
+### Value: `terraform_version`
+
+* **Value Type:** String.
+
+The `terraform_version` value within the [root namespace](#namespace-root)
+represents the version of Terraform in use when the state was saved. This can be
+used to enforce a specific version of Terraform in a policy check.
+
+As an example, the following policy would evaluate to `true` as long as the
+state was made with a version of Terraform in the 0.11.x series, excluding any
+pre-release versions (example: `-beta1` or `-rc1`):
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.terraform_version matches "^0\\.11\\.\\d+$" }
+```
+
+-> **NOTE:** This value is also available via the [`tfplan`](/sentinel/features/terraform/tfplan-v1)
+import, which will be more current when a policy check is run against a plan.
+It's recommended you use the value in `tfplan` until Terraform Cloud
+supports policy checks in other stages of the workspace lifecycle. See the
+[`terraform_version`][import-tfplan-terraform-version] reference within the
+`tfplan` import for more details.
+
+[import-tfplan-terraform-version]: /sentinel/features/terraform/tfplan-v1#value-terraform_version
+
+## Namespace: Module
+
+The **module namespace** can be loaded by calling
+[`module()`](#function-module-) for a particular module.
+
+It can be used to load the following child namespaces, in addition to the values
+documented below:
+
+* `data` - Loads the [resource namespace](#namespace-resources-data-sources),
+ filtered against data sources.
+* `outputs` - Loads the [output namespace](#namespace-outputs), which supply the
+ outputs present in this module's state. Note that with Terraform 0.12 or
+ later, this value is only available for the root namespace.
+* `resources` - Loads the [resource
+ namespace](#namespace-resources-data-sources), filtered against resources.
+
+### Root Namespace Aliases
+
+The root-level `data`, `outputs`, and `resources` keys both alias to their
+corresponding namespaces within the module namespace, loaded for the root
+module. They are the equivalent of running `module([]).KEY`.
+
+### Value: `path`
+
+* **Value Type:** List of strings.
+
+The `path` value within the [module namespace](#namespace-module) contains the
+path of the module that the namespace represents. This is represented as a list
+of strings.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The following policy would evaluate to `true`, _only_ if the module was present
+in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.module(["foo"]).path contains "foo" }
+```
+
+## Namespace: Resources/Data Sources
+
+The **resource namespace** is a namespace _type_ that applies to both resources
+(accessed by using the `resources` namespace key) and data sources (accessed
+using the `data` namespace key).
+
+Accessing an individual resource or data source within each respective namespace
+can be accomplished by specifying the type, name, and resource number (as if the
+resource or data source had a `count` value in it) in the syntax
+`[resources|data].TYPE.NAME[NUMBER]`. Note that NUMBER is always needed, even if
+you did not use `count` in the resource.
+
+In addition, each of these namespace levels is a map, allowing you to filter
+based on type and name.
+
+-> The (somewhat strange) notation here of `TYPE.NAME[NUMBER]` may imply that
+the inner resource index map is actually a list, but it's not - using the square
+bracket notation over the dotted notation (`TYPE.NAME.NUMBER`) is required here
+as an identifier cannot start with number.
+
+Some examples of multi-level access are below:
+
+* To fetch all `aws_instance.foo` resource instances within the root module, you
+ can specify `tfstate.resources.aws_instance.foo`. This would then be indexed
+ by resource count index (`0`, `1`, `2`, and so on). Note that as mentioned
+ above, these elements must be accessed using square-bracket map notation (so
+ `[0]`, `[1]`, `[2]`, and so on) instead of dotted notation.
+* To fetch all `aws_instance` resources within the root module, you can specify
+ `tfstate.resources.aws_instance`. This would be indexed from the names of
+ each resource (`foo`, `bar`, and so on), with each of those maps containing
+ instances indexed by resource count index as per above.
+* To fetch all resources within the root module, irrespective of type, use
+ `tfstate.resources`. This is indexed by type, as shown above with
+ `tfstate.resources.aws_instance`, with names being the next level down, and so
+ on.
+
+Further explanation of the namespace will be in the context of resources. As
+mentioned, when operating on data sources, use the same syntax, except with
+`data` in place of `resources`.
+
+### Value: `attr`
+
+* **Value Type:** A string-keyed map of values.
+
+The `attr` value within the [resource
+namespace](#namespace-resources-data-sources) is a direct mapping to the state
+of the resource.
+
+The map is a complex representation of these values with data going as far down
+as needed to represent any state values such as maps, lists, and sets.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if the resource was in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.resources.null_resource.foo[0].attr.triggers.foo is "bar" }
+```
+
+### Value: `depends_on`
+
+* **Value Type:** A list of strings.
+
+The `depends_on` value within the [resource
+namespace](#namespace-resources-data-sources) contains the dependencies for the
+resource.
+
+This is a list of full resource addresses, relative to the module (example:
+`null_resource.foo`).
+
+As an example, given the following resources:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+
+resource "null_resource" "bar" {
+ # ...
+
+ depends_on = [
+ "null_resource.foo",
+ ]
+}
+```
+
+The following policy would evaluate to `true` if the resource was in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.resources.null_resource.bar[0].depends_on contains "null_resource.foo" }
+```
+
+### Value: `id`
+
+* **Value Type:** String.
+
+The `id` value within the [resource
+namespace](#namespace-resources-data-sources) contains the id of the resource.
+
+-> **NOTE:** The example below uses a _data source_ here because the
+[`null_data_source`][ref-tf-null-data-source] data source gives a static ID,
+which makes documenting the example easier. As previously mentioned, data
+sources share the same namespace as resources, but need to be loaded with the
+`data` key. For more information, see the
+[synopsis](#namespace-resources-data-sources) for the namespace itself.
+
+[ref-tf-null-data-source]: https://registry.terraform.io/providers/hashicorp/null/latest/docs/data-sources/data_source
+
+As an example, given the following data source:
+
+```hcl
+data "null_data_source" "foo" {
+ # ...
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.data.null_data_source.foo[0].id is "static" }
+```
+
+### Value: `tainted`
+
+* **Value Type:** Boolean.
+
+The `tainted` value within the [resource
+namespace](#namespace-resources-data-sources) is `true` if the resource is
+marked as tainted in Terraform state.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`, if the resource was marked as
+tainted in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.resources.null_resource.foo[0].tainted }
+```
+
+## Namespace: Outputs
+
+The **output namespace** represents all of the outputs present within a
+[module](#namespace-module). Outputs are present in a state if they were saved
+during a previous apply, or if they were updated with known values during the
+pre-plan refresh.
+
+**With Terraform 0.11 or earlier** this can be used to fetch both the outputs
+of the root module, and the outputs of any module in the state below the root.
+This makes it possible to see outputs that have not been threaded to the root
+module.
+
+**With Terraform 0.12 or later** outputs are available in the top-level (root
+module) namespace only and not accessible within submodules.
+
+This namespace is indexed by output name.
+
+### Value: `sensitive`
+
+* **Value Type:** Boolean.
+
+The `sensitive` value within the [output namespace](#namespace-outputs) is
+`true` when the output has been [marked as sensitive][ref-tf-sensitive-outputs].
+
+[ref-tf-sensitive-outputs]: https://developer.hashicorp.com/terraform/language/values/outputs#sensitive-suppressing-values-in-cli-output
+
+As an example, given the following output:
+
+```hcl
+output "foo" {
+ sensitive = true
+ value = "bar"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.outputs.foo.sensitive }
+```
+
+### Value: `type`
+
+* **Value Type:** String.
+
+The `type` value within the [output namespace](#namespace-outputs) gives the
+output's type. This will be one of `string`, `list`, or `map`. These are
+currently the only types available for outputs in Terraform.
+
+As an example, given the following output:
+
+```hcl
+output "string" {
+ value = "foo"
+}
+
+output "list" {
+ value = [
+ "foo",
+ "bar",
+ ]
+}
+
+output "map" {
+ value = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+type_string = rule { tfstate.outputs.string.type is "string" }
+type_list = rule { tfstate.outputs.list.type is "list" }
+type_map = rule { tfstate.outputs.map.type is "map" }
+
+main = rule { type_string and type_list and type_map }
+```
+
+### Value: `value`
+
+* **Value Type:** String, list, or map.
+
+The `value` value within the [output namespace](#namespace-outputs) is the value
+of the output in question.
+
+Note that the only valid primitive output type in Terraform is currently a
+string, which means that any int, float, or boolean value will need to be
+converted before it can be used in comparison. This does not apply to primitives
+within maps and lists, which will be their original types.
+
+As an example, given the following output blocks:
+
+```hcl
+output "foo" {
+ value = "bar"
+}
+
+output "number" {
+ value = "42"
+}
+
+output "map" {
+ value = {
+ foo = "bar"
+ number = 42
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+value_foo = rule { tfstate.outputs.foo.value is "bar" }
+value_number = rule { int(tfstate.outputs.number.value) is 42 }
+value_map_string = rule { tfstate.outputs.map.value["foo"] is "bar" }
+value_map_int = rule { tfstate.outputs.map.value["number"] is 42 }
+
+main = rule { value_foo and value_number and value_map_string and value_map_int }
+```
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/features/terraform/tfstate-v2.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/features/terraform/tfstate-v2.mdx
new file mode 100644
index 0000000000..9b29aa2c51
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/features/terraform/tfstate-v2.mdx
@@ -0,0 +1,176 @@
+---
+page_title: tfstate/v2 - terraform - Features
+sidebar_current: docs-features-terraform-tfstate-v2
+description: The tfstate/v2 import provides access to a Terraform state.
+layout: docs
+---
+
+# Import: tfstate/v2
+
+The `tfstate/v2` import provides access to a Terraform state.
+
+The _state_ is the data that Terraform has recorded about a workspace at a
+particular point in its lifecycle, usually after an apply. You can read more
+general information about how Terraform uses state
+[here](https://developer.hashicorp.com/terraform/language/state).
+
+The data in the `tfstate/v2` import is sourced from the JSON configuration file
+that is generated by the [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) command. For more information on
+the file format, see the [JSON Output Format](https://developer.hashicorp.com/terraform/internals/json-format)
+page.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfstate/v2" {
+ config = {
+ "path": "./path/to/plan.json"
+ }
+}
+```
+
+## Import Overview
+
+The `tfstate/v2` import is structured as currently two _collections_, keyed in
+resource address and output name, respectively.
+
+```
+(tfstate/v2)
+├── terraform_version (string)
+├── resources
+│ └── (indexed by address)
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── index (float (number) or string)
+│ ├── provider_name (string)
+│ ├── values (map)
+│ ├── depends_on (list of strings)
+│ ├── tainted (boolean)
+│ └── deposed_key (string)
+└── outputs
+ └── (indexed by name)
+ ├── name (string)
+ ├── sensitive (boolean)
+ └── value (value)
+```
+
+The collections are:
+
+* [`resources`](#the-resources-collection) - The state of all resources across
+ all modules in the state.
+* [`outputs`](#the-outputs-collection) - The state of all outputs from the root module in the state.
+
+These collections are specifically designed to be used with the
+[`filter`](/sentinel/language/collection-operations#filter-expression)
+quantifier expression in Sentinel, so that one can collect a list of resources
+to perform policy checks on without having to write complex module traversal. As
+an example, the following code will return all `aws_instance` resource types
+within the state, regardless of what module they are in:
+
+```
+all_aws_instances = filter tfstate.resources as _, r {
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+You can add specific attributes to the filter to narrow the search, such as the
+module address. The following code would return resources in a module named
+`foo` only:
+
+```
+all_aws_instances = filter tfstate.resources as _, r {
+ r.module_address is "module.foo" and
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+## The `terraform_version` Value
+
+The top-level `terraform_version` value in this import gives the Terraform
+version that recorded the state. This can be used to do version validation.
+
+```
+import "tfstate/v2" as tfstate
+import "strings"
+
+v = strings.split(tfstate.terraform_version, ".")
+version_major = int(v[1])
+version_minor = int(v[2])
+
+main = rule {
+ version_major is 12 and version_minor >= 19
+}
+```
+
+## The `resources` Collection
+
+The `resources` collection is a collection representing all of the resources in
+the state, across all modules.
+
+This collection is indexed on the complete resource address as the key.
+
+An element in the collection has the following values:
+
+* `address` - The absolute resource address - also the key for the collection's
+ index.
+
+* `module_address` - The address portion of the absolute resource address.
+
+* `mode` - The resource mode, either `managed` (resources) or `data` (data
+ sources).
+
+* `type` - The resource type, example: `aws_instance` for `aws_instance.foo`.
+
+* `name` - The resource name, example: `foo` for `aws_instance.foo`.
+
+* `index` - The resource index. Can be either a number or a string.
+
+* `provider_name` - The name of the provider this resource belongs to. This
+ allows the provider to be interpreted unambiguously in the unusual situation
+ where a provider offers a resource type whose name does not start with its own
+ name, such as the `googlebeta` provider offering `google_compute_instance`.
+
+ -> **Note:** Starting with Terraform 0.13, the `provider_name` field contains the
+ _full_ source address to the provider in the Terraform Registry. Example:
+ `registry.terraform.io/hashicorp/null` for the null provider.
+
+* `values` - An object (map) representation of the attribute values of the
+ resource, whose structure depends on the resource type schema. When accessing
+ proposed state through the [`planned_values`](/sentinel/features/terraform/tfplan-v2#the-planned_values-collection)
+ collection of the tfplan/v2 import, unknown values will be omitted.
+
+* `depends_on` - The addresses of the resources that this resource depends on.
+
+* `tainted` - `true` if the resource has been explicitly marked as
+ [tainted](https://developer.hashicorp.com/terraform/cli/commands/taint) in the state.
+
+* `deposed_key` - Set if the resource has been marked deposed and will be
+ destroyed on the next apply. This matches the deposed field in the
+ [`resource_changes`](/sentinel/features/terraform/tfplan-v2#the-resource_changes-collection)
+ collection in the [`tfplan/v2`](/sentinel/features/terraform/tfplan-v2) import.
+
+## The `outputs` Collection
+
+The `outputs` collection is a collection of outputs from the root module of the
+state.
+
+Note that no child modules are included in this output set, and there is no way
+to fetch child module output values. This is to encourage the correct flow of
+outputs to the recommended root consumption level.
+
+The collection is indexed on the output name, with the following fields:
+
+* `name`: The name of the output, also the collection key.
+* `sensitive`: Whether or not the value was marked as
+ [sensitive](https://developer.hashicorp.com/terraform/language/values/outputs#sensitive-suppressing-values-in-cli-output)
+ in
+ configuration.
+* `value`: The value of the output.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/functions/append.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/functions/append.mdx
new file mode 100644
index 0000000000..81395154f6
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/functions/append.mdx
@@ -0,0 +1,46 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Append'
+sidebar_current: docs-funcs-append
+description: The built-in function `append` appends a value to the end of a list.
+layout: docs
+---
+
+# Builtin Function: append
+
+The built-in function `append` appends a value to the end of a list. The list
+is modified in-place. The return value will always be [undefined](/sentinel/language/undefined).
+
+`append` called on any value other than a list or undefined will result
+in an immediate error.
+
+Examples:
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+### Why does this function return undefined?
+
+This function returns `undefined` to avoid unintended side effects of the function in the context
+of a [rule](/sentinel/language/rules).
+
+For example, if `append` returned the list value as a result, the following policy would depend
+on the order in which rules are referenced:
+
+```sentinel
+list = []
+a = rule { append(list, 1) contains 1 }
+b = rule { append(list, 2) contains 2 }
+
+// Scenario 1: list becomes [1, 2]
+main = rule { a and b }
+
+// Scenario 2: list becomes [2, 1]
+main = rule { b and a }
+```
+
+This sort of side effect behavior introduces complex and difficult to track logical errors in programs.
+As a result, functions like this return `undefined` and modify values in-place.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/functions/delete.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/functions/delete.mdx
new file mode 100644
index 0000000000..3b60803997
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/functions/delete.mdx
@@ -0,0 +1,25 @@
+---
+page_title: 'Sentinel Language - Builtin Function: delete'
+sidebar_current: docs-funcs-delete
+description: The built-in function `delete` deletes an element from a map.
+layout: docs
+---
+
+# Builtin Function: delete
+
+The built-in function `delete` deletes an element from a map.
+
+If the element to delete does not exist, the map is left unchanged.
+Calling `delete` on a value that is not a map or
+[undefined](/sentinel/language/undefined)
+is an immediate error. The result of `delete` is always `undefined`.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/functions/error.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/functions/error.mdx
new file mode 100644
index 0000000000..96d253b662
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/functions/error.mdx
@@ -0,0 +1,15 @@
+---
+page_title: 'Sentinel Language - Builtin Function: error'
+sidebar_current: docs-funcs-error
+description: >-
+ The built-in function `error` is used to exit immediately with an error
+ message.
+layout: docs
+---
+
+# Builtin Function: error
+
+The built-in function `error` is used to exit immediately with an error message.
+
+Please see the [page on errors](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/functions/index.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/functions/index.mdx
new file mode 100644
index 0000000000..4e12143ad0
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/functions/index.mdx
@@ -0,0 +1,13 @@
+---
+page_title: Sentinel Language - Builtin Functions
+sidebar_current: docs-funcs
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Language: Builtin Functions
+
+Builtin functions are
+[functions](/sentinel/language/functions)
+that are available in all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in function.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/functions/keys.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/functions/keys.mdx
new file mode 100644
index 0000000000..a96cafe897
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/functions/keys.mdx
@@ -0,0 +1,22 @@
+---
+page_title: 'Sentinel Language - Builtin Function: keys'
+sidebar_current: docs-funcs-keys
+description: The built-in function `keys` returns the keys of a map.
+layout: docs
+---
+
+# Builtin Function: keys
+
+The built-in function `keys` returns the keys of a map.
+
+`keys` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned keys are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+```
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/functions/length.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/functions/length.mdx
new file mode 100644
index 0000000000..8b874b14c4
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/functions/length.mdx
@@ -0,0 +1,23 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Length'
+sidebar_current: docs-funcs-length
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Builtin Function: length
+
+The built-in function `length` returns the length of a collection or string.
+
+The length of a string is the number of bytes in the string. It is not
+necessarilly the number of characters. The length of a collection is the
+number of elements in that collection. The length of
+[undefined](/sentinel/language/undefined) is `undefined`.
+
+Examples:
+
+```sentinel
+length("foo") // 3
+length([1, 2, 3, 4]) // 4
+length({ "key": "value" }) // 1
+```
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/functions/print.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/functions/print.mdx
new file mode 100644
index 0000000000..bcf441d09d
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/functions/print.mdx
@@ -0,0 +1,13 @@
+---
+page_title: 'Sentinel Language - Builtin Function: print'
+sidebar_current: docs-funcs-print
+description: The built-in function `print` is used for logging and debugging.
+layout: docs
+---
+
+# Builtin Function: print
+
+The built-in function `print` is used for logging and debugging.
+
+Please see the [page on logging](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/functions/range.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/functions/range.mdx
new file mode 100644
index 0000000000..0f86bfc402
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/functions/range.mdx
@@ -0,0 +1,49 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Range'
+sidebar_current: docs-funcs-range
+description: The built-in function `range` returns a list of numbers in a range.
+layout: docs
+---
+
+# Builtin Function: range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it
+defaults to `1`.
+
+Negative steps are permitted and count down from `start` towards `end`, instead
+of up.
+
+The range ends when the next value given by the sum of the last value and `step`
+would exceed `end`, regardless of if it has been reached.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4] - starts at 1 instead of 0
+range(1, 5, 2) // [1,3] - 3+2 does not satisfy r[-1] < end
+range(0, -3, -1) // [0,-1,-2] - example of negative range
+```
+
+Impossible ranges, or ranges where `start` will never reach `end` given `step`,
+yield an empty list.
+
+```
+range(1, 1) // [] - already starting at 1
+range(0, 1, -1) // [] - negative step will never reach 1
+```
+
+Supplying an undefined value to any argument of `range` yields an undefined
+value back.
+
+A range with a zero step is a runtime error.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/functions/values.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/functions/values.mdx
new file mode 100644
index 0000000000..8f98d3ab56
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/functions/values.mdx
@@ -0,0 +1,22 @@
+---
+page_title: 'Sentinel Language - Builtin Function: values'
+sidebar_current: docs-funcs-values
+description: The built-in function `values` returns the values of a map.
+layout: docs
+---
+
+# Builtin Function: values
+
+The built-in function `values` returns the values of a map.
+
+`values` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned values are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+values(data) // [3, 2]
+```
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/imports/base64.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/imports/base64.mdx
new file mode 100644
index 0000000000..4e58429268
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/imports/base64.mdx
@@ -0,0 +1,46 @@
+---
+page_title: 'Import: base64'
+sidebar_current: docs-imports-base64
+description: The base64 import enables a Sentinel policy to encode and decode Base64 values.
+layout: docs
+---
+
+# Import: base64
+
+The base64 import enables a Sentinel policy to encode and decode Base64 values.
+
+### base64.encode(str)
+
+Encodes the string `str` into a Base64 encoded string, using the standard
+encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-4).
+
+```sentinel
+enc = base64.encode("foo") // "Zm9v"
+```
+
+### base64.decode(str)
+
+Decodes the Base64 encoded string `str`, returning the string result,
+using the standard encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-4).
+
+```sentinel
+dec = base64.decode("Zm9v") // "foo"
+```
+
+### base64.urlencode(str)
+
+Encodes the string `str` into a Base64 encoded string, using the alternate
+encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-5).
+
+```sentinel
+enc = base64.urlencode("foo") // "Zm9v"
+```
+
+### base64.urldecode(str)
+
+Decodes the Base64 encoded string `str`, returning the string result, using the
+alternate encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-5).
+
+```sentinel
+dec = base64.urldecode("Zm9v") // "foo"
+```
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/imports/decimal.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/imports/decimal.mdx
new file mode 100644
index 0000000000..a6633a38e0
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/imports/decimal.mdx
@@ -0,0 +1,318 @@
+---
+page_title: 'Import: decimal'
+sidebar_current: docs-imports-decimal
+description: |-
+ The decimal import provides functions for operating on numbers represented
+ as decimals.
+layout: docs
+---
+
+# Import: decimal
+
+The decimal import provides functions for operating on numbers represented
+as decimals.
+
+Binary floating-point numbers provided by the `float` type are unable to
+fully represent numbers like `1.1` and `2.2`. The decimal import can
+represent these numbers exactly, and so should be used when exactness is
+required.
+
+Decimals are created through the `new` function, which takes any type that
+can represent a number. Arguments to the decimal operators can also take
+a value of any of these types. The full list is:
+
+- float
+- int
+- string
+- existing decimal value
+
+### decimal.infinity(sign)
+
+Creates an infinite-value decimal. Infinite-value decimals are always larger
+or smaller, depending on sign, than any non-infinite decimals.
+
+```sentinel
+decimal.infinity(1) // +Infinity
+decimal.infinity(-1) // -Infinity
+```
+
+Also available as `decimal.inf`.
+
+### decimal.nan
+
+A decimal representing a "not-a-number" value. NaNs are not equal to any
+other number, even themselves.
+
+```sentinel
+decimal.new(-1).sqrt() // NaN
+decimal.new(0).mul(decimal.inf(1)) // NaN
+decimal.nan.is(decimal.nan) // false
+```
+
+### decimal.is_infinite(d)
+
+Evaluates to true if the decimal is infinite.
+
+```sentinel
+decimal.is_infinite(100) // false
+decimal.is_infinite("Infinity") // true
+```
+
+Also available as `decimal.is_inf`, `decimal.isinf` and `decimal.isinfinite`.
+
+### decimal.is_nan(d)
+
+Evaluates to true if the decimal is not-a-number.
+
+```sentinel
+decimal.is_nan("NaN") // true
+```
+
+Also available as `decimal.isnan`.
+
+### decimal.new(v)
+
+Constructs a decimal from another value.
+
+```sentinel
+decimal.new("1.1") // Constructs a decimal from a string.
+decimal.new(2.2) // Constructs a decimal from a float.
+decimal.new(3) // Constructs a decimal from an integer.
+decimal.new("1.1234E+400") // Constructs a decimal from a string using E-notation.
+```
+
+## Type: number
+
+Numbers are represented internally by a sign, coefficient, and exponent.
+The value is calculated with the formula `sign * coefficient * 10^exponent`.
+Exponent is limited to 32 bits, and the coefficient is limited by the total
+precision.
+
+The maximum precision a number can represent is 100 digits. This includes
+both before and after the decimal place.
+
+### number.string
+
+A string representation of the decimal.
+
+```sentinel
+decimal.new(1.5).string // 1.5
+```
+
+### number.sign
+
+A number between `-1` and `+1` representing the sign of the decimal.
+
+```sentinel
+decimal.new(1.5).sign // 1
+```
+
+### number.coefficient
+
+The coefficient component of the decimal.
+
+```sentinel
+decimal.new(1.5).coefficient // 15
+```
+
+### number.exponent
+
+The exponent component of the decimal.
+
+```sentinel
+decimal.new(1.5).exponent // -1
+```
+
+### number.float
+
+A floating-point representation of the decimal.
+
+```sentinel
+decimal.new(1.5).float // 1.500000
+```
+
+### number.int
+
+An integer representation of the decimal. This always rounds down to the nearest integer.
+
+```sentinel
+decimal.new(1.5).int // 1
+```
+
+### number.is(v)
+
+Test for equality with another value.
+
+```sentinel
+decimal.new(1.5).is(1) // false
+```
+
+### number.is_not(v)
+
+Test for inequality with another value.
+
+```sentinel
+decimal.new(1.5).is_not(1) // true
+```
+
+### number.less_than(v)
+
+Test that the decimal is less than another value.
+Can also be called as `number.lt(v)`
+
+```sentinel
+decimal.new(1.5).less_than(1) // false
+```
+
+### number.less_than_or_equals(v)
+
+Test that the decimal is less than or equals to another value.
+Can also be called as `number.lte(v)`
+
+```sentinel
+decimal.new(1.5).less_than_or_equals(1) // false
+```
+
+### number.greater_than(v)
+
+Test that the decimal is greater than another value.
+Can also be called as `number.gt(v)`
+
+```sentinel
+decimal.new(1.5).greater_than(1) // true
+```
+
+### number.greater_than_or_equals(v)
+
+Test that the decimal is greater than or equals to another value.
+Can also be called as `number.gte(v)`
+
+```sentinel
+decimal.new(1.5).greater_than_or_equals(1) // true
+```
+
+### number.add(v)
+
+Add another number to the decimal.
+
+```sentinel
+decimal.new(1.5).add(1) // 2.5
+```
+
+### number.subtract(v)
+
+Subtract another number from the decimal.
+Can also be called as `number.sub(v)`
+
+```sentinel
+decimal.new(1.5).subtract(1) // 0.5
+```
+
+### number.multiply(v)
+
+Multiply the decimal by a number.
+Can also be called as `number.mul(v)`
+
+```sentinel
+decimal.new(1.5).multiply(2) // 3.0
+```
+
+### number.divide(v)
+
+Divide the decimal by a number.
+Can also be called as `number.div(v)`
+
+```sentinel
+decimal.new(3.0).divide(1.5) // 2
+```
+
+### number.modulo(v)
+
+Find the remainder after dividing the decimal by a number.
+Can also be called as `mod`, `remainder`, or `rem`.
+
+```sentinel
+decimal.new(1.5).modulo(1) // 0.5
+```
+
+### number.power(v)
+
+Raise the decimal to a power.
+Can also be called as `number.pow(v)`
+
+```sentinel
+decimal.new(1.5).power(2) // 2.25
+```
+
+### number.exp()
+
+The natural exponent of the decimal. This calculates `e^number`.
+
+```sentinel
+decimal.new(1.5).exp() // 4.4816...
+```
+
+### number.loge()
+
+The natural logarithm of the decimal.
+Can also be called as `number.ln()`
+
+```sentinel
+decimal.new(1.5).loge() // 0.4054...
+```
+
+### number.log10()
+
+The base-10 logarithm of the decimal.
+Can also be called as `number.log()`
+
+```sentinel
+decimal.new(1.5).log10() // 0.1760...
+```
+
+### number.square_root()
+
+The square root of the decimal.
+Can also be called as `number.sqrt()`
+
+```sentinel
+decimal.new(1.5).square_root() // 1.2247...
+```
+
+### number.ceiling()
+
+Round the decimal to the smallest integer greater or equal to itself.
+Can also be called as `number.ceil()`
+
+```sentinel
+decimal.new(1.5).ceiling() // 2
+```
+
+### number.floor()
+
+Round the decimal to the largest integer less than or equal to itself.
+
+```sentinel
+decimal.new(1.5).floor() // 1
+```
+
+### number.absolute()
+
+The absolute value of the decimal.
+Can also be called as `number.abs()`
+
+```sentinel
+decimal.new(1.5).absolute() // 1.5
+decimal.new(-1.5).absolute() // 1.5
+```
+
+### number.negate()
+
+The negated value of the decimal. A positive decimal will become negative,
+and a negative decimal will become positive.
+Can also be called as `number.neg()`
+
+```sentinel
+decimal.new(1.5).negate() // -1.5
+decimal.new(-1.5).negate() // 1.5
+```
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/imports/http.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/imports/http.mdx
new file mode 100644
index 0000000000..f94ae98c27
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/imports/http.mdx
@@ -0,0 +1,370 @@
+---
+page_title: 'Import: http'
+sidebar_current: docs-imports-http
+description: The http import enables the use of HTTP-accessible data from outside the runtime in Sentinel policy rules.
+layout: docs
+---
+
+# Import: http
+
+The http import enables the use of HTTP-accessible data from outside
+the runtime in Sentinel policy rules.
+
+The simplest example of the import in use would be:
+
+```sentinel
+import "http"
+
+resp = http.get("https://example.hashicorp.com")
+main = rule { resp.body contains "something" }
+```
+
+By default, any request response that is not a successful "200 OK" HTTP
+response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information and
+how to customize this behavior.
+
+For more advanced requests which require setting request headers, use a
+[`request` type](#type-request):
+
+```sentinel
+import "http"
+import "json"
+
+param token
+
+req = http.request("https://example.hashicorp.com").with_header("Authorization", "token "+token)
+resp = json.unmarshal(http.get(req).body)
+main = rule { resp["some_key"] is true }
+```
+
+The `with_header` function above returns the `request` object with the
+`Authorization` HTTP header set to value of the variable `some_value`. Many
+of the methods within the http import API are designed around this concept
+of method chaining, allowing for complex building of HTTP requests
+encapsulated in functions. The following is an idiomatic example further
+demonstrating this pattern:
+
+```sentinel
+import "http"
+import "json"
+
+param github_user
+param github_token
+
+client = func(req) {
+ return http.with_timeout(5).with_retries(3)
+}
+
+github_request = func(path) {
+ full_url = "https://api.github.com" + path
+ return http.request(full_url).
+ with_basic_auth(github_user, github_token).
+ with_header("Accept", "application/vnd.github.v3+json").
+ with_header("Custom", "foo"),
+}
+
+default_response = client.get(github_request("/example"))
+
+# Override the Custom header for this single request
+custom_header_response = json.unmarshal(
+ client.get(
+ github_request("/example").with_header("Custom", "bar"),
+ ),
+)
+
+# Override the timeout value for a known slower request
+slow_response = json.unmarshal(
+ client.with_timeout(15).get(github_request("/slow-endpoint")),
+)
+
+some_key_is_true = rule { json.unmarshal(default_response.body)["some_key"] is true }
+body_contains_text = rule { custom_header_response.body contains "something" }
+response_is_ok = rule { slow_response.status_code is 200 }
+
+main = rule { some_key_is_true and body_contains_text and response_is_ok }
+```
+
+### http.client
+
+Retrieve the base HTTP client with default settings. The returned client may be
+configured by calling configuration functions on it; all of these configuration
+functions on are also aliased as top-level functions in this namespace. See
+[`client`](#type-client) below.
+
+### http.request(url)
+
+Create a new [`request`](#type-request) to the specified `url` (string). A
+`request` type enables customization of headers and other concerns before then
+providing the constructed `request` to [`get()`](#client-get-url_or_request),
+[`post()`](#client-post-url_or_request) or [`send()`](#client-send-request).
+
+```sentinel
+req = http.request("example.hashicorp.com/foo").with_header("Foo", "bar")
+resp = http.get(req)
+```
+
+### http.methodPost
+
+Returns a string containing the accepted verb for POST requests, `"POST"`.
+
+### http.methodGet
+
+Returns a string containing the accepted verb for GET requests, `"GET"`.
+
+## Type: client
+
+All of the following configuration functions on the default client are also
+aliased as top-level functions in the import. This allows a short-hand (and
+idiomatic) way of configuring a new client without having to directly access
+`http.client` each time:
+
+```sentinel
+# The following two lines are semantically the same:
+resp = http.client.with_timeout(5).get(url)
+resp = http.with_timeout(5).get(url)
+```
+
+### url_or_request
+
+The [`get()`](#client-get-url_or_request) and [`post()`](#client-post-url_or_request)
+functions accept either a URL string or a request object, noted as
+`url_or_request` throughout the documentation.
+
+When `url_or_request` is a string, a request is made with the URL
+specified by the string. To customize HTTP headers and other settings, pass
+a request. By default, a request has a timeout value of 10 seconds and 1
+retry. These settings can be adjusted with [`with_timeout()`](#clientwith_timeoutinteger) and
+[`with_retries()`](#clientwith_retriesinteger), respectively.
+
+### Redirects
+
+If the response is one of the following redirect codes, the client follows the
+redirect, up to a maximum of 10 redirects:
+
+- 301 (Moved Permanently)
+- 302 (Found)
+- 303 (See Other)
+- 307 (Temporary Redirect)
+- 308 (Permanent Redirect)
+
+If the redirect limit is exceeded, the result is a policy error.
+
+By default, after any redirects are followed (up to the maximum), any request
+response that is not a successful "200 OK" response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information
+and how to customize this behavior.
+
+### client.get(url_or_request)
+
+Issue a GET request with a URL string or a `request` type. Returns a `response`
+type.
+
+```sentinel
+import "http"
+
+resp = http.get("https://example.hashicorp.com")
+main = rule { resp.body contains "something" }
+```
+
+### client.post(url_or_request)
+
+Issue a POST request with a URL string or a `request` type. Returns a `response`
+type.
+
+```sentinel
+import "http"
+
+req = http.request("https://example.hashicorp.com")
+ .with_body(json.marshal({"foo": "bar"}))
+resp = http.post(req)
+main = rule { resp.body contains "something" }
+```
+
+### client.send(request)
+
+Make a request using the supplied `request` type. Returns a `response`.
+
+```sentinel
+import "http"
+
+req = http.request("https://example.hashicorp.com")
+resp = http.send(req)
+main = rule { resp.body contains "something" }
+```
+
+### client.accept_status_codes(list)
+
+Configures a client to not automatically cause a policy failure if
+the resulting response's status code is in `list`. Any status code received
+that is not present in this list will trigger a runtime error.
+
+By default, any request response that is not a successful "200 OK" response
+causes a policy error. This is for convenience, as the most common scenario
+by far is to receive a response and immediately signal a policy error if the
+status code is not 200. Use this scoping to specify a list of non-redirect
+HTTP codes that you wish to accept without error and evaluate the response
+yourself.
+
+Redirects are not considered final status codes in this context. That is,
+redirects are always followed up to the maximum allowed value (see [`Redirects`](#redirects))
+and the final response code in the redirect chain is validated against
+`list`.
+
+```sentinel
+resp = http.accept_status_codes([200, 404]).get(url)
+main = rule { resp.status_code is 404 }
+```
+
+### client.accepted_status_codes
+
+Returns a list of the client's accepted status codes.
+
+```sentinel
+client = http.accept_status_codes([200, 404])
+main = rule { client.accepted_status_codes contains 404 }
+```
+
+### client.with_retries(integer)
+
+Sets the maximum number of retries, specified by `integer`, that should be
+attempted on an unsuccessful request. An unsuccessful request is considered
+to be any 5xx response except `501 (Not Implemented)` or a request timeout.
+By default, one retry is attempted.
+
+The maximum number of retries is 10, for a total of 11 request attempts.
+When a request exceeds the maximum number of retries without a response, the
+result is a policy error. Specifying a number larger than this will result
+in a runtime error.
+
+Between each retry, an exponentially increasing delay is observed, allowing
+time for the server to recover. This delay starts at 1 second for the first
+retry and increases each attempt thereafter. The maximum delay for a single
+attempt is 30 seconds.
+
+Retries can be disabled by specifying 0.
+
+### client.retries
+
+Returns the client's configured number of retries as an integer.
+
+### client.with_timeout(integer)
+
+Sets the request timeout to the number of seconds indicated by `integer`.
+
+Each individual request performed by the HTTP client will be limited by the
+given request timeout. This timeout includes connection time, any redirects,
+and reading the response body.
+
+A maximum of 30 seconds is allowed. Specifying a number larger than this
+will result in a runtime error.
+
+By default, requests will time out after 10 seconds.
+
+### client.timeout
+
+Returns the client's configured timeout in whole seconds as an integer.
+
+### client.without_certificate_verification()
+
+Skips verification of TLS certificates during secure HTTP operations,
+allowing for requests to `https://` endpoints which are secured with a TLS
+certificate signed by an untrusted certificate authority. Takes no arguments.
+
+By default, the HTTP client will trigger a runtime error if a TLS certificate
+presented by a server is from an untrusted certificate authority.
+
+Do not change this setting unless you are aware of the risks involved.
+Without verification, TLS accepts any certificate presented by the server
+and any host name in that certificate. In this mode, TLS is susceptible to
+man-in-the-middle attacks. This option should only be used for testing or in
+trusted networks with self-signed certificates.
+
+```sentinel
+http.without_certificate_verification().get(url)
+```
+
+### client.certificate_verification
+
+Returns true if the client requires TLS certificates to be verifiable.
+
+## Type: request
+
+### request.url
+
+Returns the request URL as a string.
+
+### request.headers
+
+Returns the configured request headers as a string-keyed map of strings.
+
+### request.body
+
+Returns the configured body as a string.
+
+### request.with_header(key, value)
+
+Returns a `request` with the header `key` (string) set to `value` (string).
+
+Values are overridden on subsequent calls to the same `key`. To specify
+multiple values, use the existing value when setting the new one.
+
+```sentinel
+original = http.request("http://example.hashicorp.com").with_header("Foo", "bar")
+overridden = original.with_header("Foo", "baz")
+multi_value = original.with_header("Foo", original.headers["Foo"] + ",baz")
+
+main = rule {
+ original.headers["Foo"] is "bar" and
+ overridden.headers["Foo"] is "baz" and
+ multi_value.headers["Foo"] is "bar,baz"
+}
+```
+
+### request.with_basic_auth(username, password)
+
+Returns a `request` with the `Authorization` header set to use HTTP Basic
+Authentication with the provided username and password.
+
+Note that with HTTP Basic Authentication the provided username and password
+are encoded, but not encrypted.
+
+### request.with_body(body)
+
+Returns a `request` with the `body` set. This value will then be sent as a body
+as part of the request. Commonly used along with the [`post()`](#client-post-url_or_request) function.
+
+```sentinel
+req = http.request("http://example.hashicorp.com")
+ .with_header("Content-Type", "application/json")
+ .with_body(json.marshal({ "foo": "bar" }))
+
+resp = http.post(req)
+```
+
+## Type: response
+
+### response.status_code
+
+The response status code as an integer, e.g. `200`.
+
+### response.headers
+
+The response headers as a map.
+
+### response.body
+
+The response body as a string. Callers should unmarshal the content to a useful
+representation as necessary based on the content type. For example, if the
+response is in JSON:
+
+```sentinel
+import "http"
+import "json"
+
+# This example endpoint returns {"some_key": true}
+req = http.request("https://example.hashicorp.com/some-json")
+
+resp = json.unmarshal(http.get(req).body)
+main = rule { keys(resp) contains "some_key" and resp["some_key"] is true }
+```
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/imports/index.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/imports/index.mdx
new file mode 100644
index 0000000000..e45bf81b81
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/imports/index.mdx
@@ -0,0 +1,18 @@
+---
+page_title: Sentinel Language - Standard Imports
+sidebar_current: docs-imports
+description: >-
+ Standard Imports are the built-in imports that are available to all Sentinel
+ policies.
+layout: docs
+---
+
+# Standard Imports
+
+Standard Imports are the built-in [imports](/sentinel/language/imports)
+that are available to all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in import.
+
+Sentinel-embedded applications can choose to allow or deny certain
+standard imports. Please reference the documentation for the Sentinel-enabled
+application you're using to determine if all standard imports are available.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/imports/json.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/imports/json.mdx
new file mode 100644
index 0000000000..3355458f14
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/imports/json.mdx
@@ -0,0 +1,31 @@
+---
+page_title: 'Import: json'
+sidebar_current: docs-imports-json
+description: The json import enables a Sentinel policy to parse and access a JSON document.
+layout: docs
+---
+
+# Import: json
+
+The json import enables a Sentinel policy to parse and access a JSON
+document.
+
+### json.unmarshal(obj)
+
+Unmarshals the JSON object `obj` into a native Sentinel structure.
+
+All native JSON types can be represented perfectly as Sentinel native types.
+
+```sentinel
+// Typically the input for this would come from an external source.
+config = json.unmarshal("{ \"foo\": 42 }")
+config.foo // 42
+config.bar // undefined (as usual for accessing a non-existent map key)
+```
+
+### json.marshal(obj)
+
+Marshals the Sentinel object `obj` into a JSON object encoded as a string.
+
+Functions and `undefined` cannot be natively encoded as JSON. If values of
+either type are found in the structure, this will return undefined.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/imports/runtime.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/imports/runtime.mdx
new file mode 100644
index 0000000000..6493bbbb22
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/imports/runtime.mdx
@@ -0,0 +1,16 @@
+---
+page_title: 'Import: runtime'
+sidebar_current: docs-imports-runtime
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: runtime
+
+The runtime import contains various information about the Sentinel runtime. It
+can be used to get information about the runtime as it's embedded in the CLI or
+a specific integration, such as validating a specific runtime version.
+
+### runtime.version
+
+Returns the version of Sentinel within this implementation.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/imports/sockaddr.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/imports/sockaddr.mdx
new file mode 100644
index 0000000000..a6842ba5f0
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/imports/sockaddr.mdx
@@ -0,0 +1,41 @@
+---
+page_title: 'Import: sockaddr'
+sidebar_current: docs-imports-sockaddr
+description: The sockaddr import makes working with IP addresses easy.
+layout: docs
+---
+
+# Import: sockaddr
+
+The sockaddr import makes working with IP addresses easy.
+
+### sockaddr.is_contained(outer, inner)
+
+The is_contained function returns true if `inner` is an address that
+is contained within `outer`.
+
+```sentinel
+sockaddr.is_contained("192.168.0.0/24", "192.168.0.32") // true
+sockaddr.is_contained("192.168.0.0/24", "192.174.1.1") // false
+```
+
+### sockaddr.is_equal(addr1, addr2)
+
+The is_equal function returns true if addr1 is equal to addr2.
+
+```sentinel
+sockaddr.is_equal("192.168.12.24", "192.168.12.24") // true
+```
+
+### sockaddr.is_ipv4(addr)
+
+The is_ipv4 function returns true if addr is an IPv4 address.
+
+```sentinel
+sockaddr.is_ipv4("192.168.12.24") // true
+sockaddr.is_ipv4("2001:0db8:85a3:0000:0000:8a2e:0370:7334") // false
+```
+
+### sockaddr.is_ipv6(addr)
+
+The is_ipv6 function returns true if addr is an IPv6 address.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/imports/strings.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/imports/strings.mdx
new file mode 100644
index 0000000000..823c5d8f07
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/imports/strings.mdx
@@ -0,0 +1,91 @@
+---
+page_title: 'Import: strings'
+sidebar_current: docs-imports-strings
+description: The strings import exposes common string operations.
+layout: docs
+---
+
+# Import: strings
+
+The strings import exposes common string operations.
+
+### strings.has_prefix(s, prefix)
+
+Returns true if `s` starts with `prefix`.
+
+```sentinel
+strings.has_prefix("billing-id", "billing-") // true
+strings.has_prefix("bill-id", "billing-") // false
+```
+
+### strings.has_suffix(s, suffix)
+
+Returns true if `s` ends with `suffix`.
+
+```sentinel
+strings.has_suffix("billing-id", "id") // true
+strings.has_suffix("billing-name", "id") // false
+```
+
+### strings.join(a, sep)
+
+Joins a list with the string supplied by `sep`.
+
+Multi-dimensional lists are supported, and non-string primitive
+types will be converted to their string equivalents. Maps and
+other complex non-list types are not supported.
+
+```sentinel
+strings.join(["foo", "bar", "baz"], ".") // "foo.bar.baz"
+strings.join([["foo", "bar"], "baz"], ".") // "foo.bar.baz"
+strings.join(["a", 1, 1.01, true], ",") // "a,1,1.01,true"
+```
+
+### strings.trim_prefix(s, prefix)
+
+Trim the prefix from the string `s`. If the string doesn't have the
+prefix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_prefix("billing-id", "billing-") // "id"
+strings.trim_prefix("bill-id", "billing-") // "bill-id"
+```
+
+### strings.trim_suffix(s, suffix)
+
+Trim the suffix from the string `s`. If the string doesn't have the
+suffix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_suffix("billing-id", "-id") // "billing"
+strings.trim_suffix("bill-id", "-foo") // "bill-id"
+```
+
+### strings.to_lower(s)
+
+Lowercase the string s.
+
+```sentinel
+strings.to_lower("FoO") // "foo"
+```
+
+### strings.to_upper(s)
+
+Uppercase the string s.
+
+```sentinel
+strings.to_upper("FoO") // "FOO"
+```
+
+### strings.split(s, sep)
+
+Split the string 's' via a substring 'sep'. If 'sep' is not contained in
+'s', the return value will be a single-element list containing only 's'.
+As a special-case, if the input is already a list, it will be returned
+as-is. This allows calling the split function when not knowing if the input
+is already a list or not.
+
+```sentinel
+strings.split("foo,bar,baz", ",") // ["foo", "bar", "baz"]
+strings.split(["foo", "bar", "baz"], ",") // ["foo", "bar", "baz"]
+```
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/imports/time.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/imports/time.mdx
new file mode 100644
index 0000000000..3a675e2bbd
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/imports/time.mdx
@@ -0,0 +1,257 @@
+---
+page_title: 'Import: time'
+sidebar_current: docs-imports-time
+description: The time import provides access to the execution time and time functions.
+layout: docs
+---
+
+# Import: time
+
+The time import provides access to the execution time and time functions.
+
+During the execution of a policy the time is fixed to the moment of
+execution. This gives the illusion that a policy instantly executes at a
+single moment of time.
+
+The import uses Coordinated Universal Time (UTC).
+
+As an example of this, if a policy accessed `time.now.second` multiple times,
+for each access the same value would be returned even if they are several seconds apart.
+This is the execution `timespace`, which is mapped to `time.now`.
+
+It is also possible to run functions on a custom time by using the
+`time.load()` function to create a new timespace from the specified value.
+See the documentation for available actions on timespaces.
+
+Times specified as the reference time or via the `time.load()` function can
+be specified in a `timeish` fashion. This is either one of:
+
+- The number of seconds since the Unix epoch (Thursday, 1 January 1970,
+ 00:00:00 UTC),
+- A timestamp matching the format outlined in RFC3339, either in the
+ format `2006-01-02T15:04:05+07:00`, which includes a timezone offset, or
+ `2006-01-02T15:04:05Z`, which indicates UTC.
+
+### time.now
+
+Create a `timespace` locked either to execution time or, if set, the
+reference time. In a given execution, this will always produce the same
+value.
+
+```sentinel
+time.now // {"day": 17, "hour": 23, "minute": 19, "month": 11 ... }
+```
+
+### time.load(timeish)
+
+Create a timespace using the given value. Please see the import
+documentation for valid values for "timeish".
+
+```sentinel
+time.load("2019-11-16T01:20:30Z") // {"day": 16, "hour": 1, "minute": 20, "month": 11 ... }
+```
+
+### time.nanosecond
+
+A nanosecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.nanosecond * 1) // 2019-11-16T01:20:30.000000001Z
+```
+
+### time.microsecond
+
+A microsecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.microsecond * 1) // 2019-11-16T01:20:30.000001Z
+```
+
+### time.millisecond
+
+A millisecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.millisecond * 1) // 2019-11-16T01:20:30.001Z
+```
+
+### time.second
+
+A second duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.second * 1) // 2019-11-16T01:20:31Z
+```
+
+### time.minute
+
+A minute for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.minute * 1) // 2019-11-16T01:21:30Z
+```
+
+### time.hour
+
+An hour duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+## Type: timespace
+
+### timespace.hour
+
+The hour from 0 to 23.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").hour // 1
+```
+
+### timespace.minute
+
+The minute from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").minute // 20
+```
+
+### timespace.second
+
+The second from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").second // 30
+```
+
+### timespace.day
+
+The day of the month, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").day // 16
+```
+
+### timespace.month
+
+The month of the year as an integer, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month // 11
+```
+
+### timespace.month_name
+
+The name of the month of the year, in English. Example: `January`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month_name // November
+```
+
+### timespace.weekday
+
+The weekday as an integer, where 0 is Sunday and 6 is Saturday.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday // 6
+```
+
+### timespace.weekday_name
+
+The name of the day of the week, in English. Example: `Sunday`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday_name // Saturday
+```
+
+### timespace.year
+
+The year as an integer.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").year // 2019
+```
+
+### timespace.unix
+
+The number of seconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix // 1573867230
+```
+
+### timespace.unix_nano
+
+The number of nanoseconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix_nano // 1573867230000000000
+```
+
+### timespace.zone
+
+The timezone offset, in seconds. This can be a negative number to indicate
+time west of UTC.
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone // -28800
+```
+
+### timespace.zone_string
+
+The timezone offset, in the format `+HH:MM` (example: `-08:00` for PST).
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone_string // -08:00
+```
+
+### timespace.before(timeish_or_timespace)
+
+Check if the time in the timespace is before the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").before("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.after(timeish_or_timespace)
+
+Check if the time in the timespace is after the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").after("2019-11-15T01:20:30Z") // true
+```
+
+### timespace.equal(timeish_or_timespace)
+
+Check if two times are equivalent
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").equal("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.add(duration)
+
+Add a duration in nanoseconds to the time in the timespace and return a new timespace. The
+duration can be negative. The duration should be specified as an integer
+multiplied with the correct unit.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+### timespace.sub(timeish_or_timespace)
+
+Subtract a time from the time in the timespace and return a duration in nanoseconds.
+
+```sentinel
+// Gives a duration of 3 hours.
+duration = time.load(
+ "2019-11-16T01:20:30Z",
+).sub("2019-11-16T04:20:30Z")
+
+// Returns 2019-11-16T01:20:30Z
+time.load(
+ "2019-11-16T04:20:30Z",
+).add(duration)
+```
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/imports/types.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/imports/types.mdx
new file mode 100644
index 0000000000..fcfdbd2ae5
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/imports/types.mdx
@@ -0,0 +1,35 @@
+---
+page_title: 'Import: types'
+sidebar_current: docs-imports-types
+description: The types import enables a Sentinel policy to parse an object's type.
+layout: docs
+---
+
+# Import: types
+
+The types import enables a Sentinel policy to parse an object's type.
+
+### types.type_of(obj)
+
+Returns the object's type as a string
+
+```sentinel playground
+import "types"
+
+// Typically the inputs would come from an external source.
+is_boolean = rule { types.type_of(true) is "bool" }
+is_string = rule { types.type_of("Hello!") is "string" }
+is_integer = rule { types.type_of(42) is "int" }
+is_float = rule { types.type_of(42.123) is "float" }
+is_null = rule { types.type_of(null) is "null" }
+is_undefined = rule { types.type_of(undefined) is "undefined" }
+
+main = rule {
+ is_boolean and
+ is_string and
+ is_integer and
+ is_float and
+ is_null and
+ is_undefined
+}
+```
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/imports/units.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/imports/units.mdx
new file mode 100644
index 0000000000..e2ad3442f9
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/imports/units.mdx
@@ -0,0 +1,39 @@
+---
+page_title: 'Import: units'
+sidebar_current: docs-imports-units
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: units
+
+The units import provides access to quick calculations for various byte
+units.
+
+Note that this import uses base-2 terminology:
+
+- One kilobyte is 1024 bytes
+- One megabyte is 1024^2 = 1048576 bytes
+- One gigabyte is 1024^3 = 1073741824 bytes
+- One terabyte is 1024^4 = 1099511627776 bytes
+- One petabyte is 1024^5 = 1125899906842624 bytes
+
+### units.kilobyte
+
+The base-2 representation of a kilobyte (1024 bytes).
+
+### units.megabyte
+
+The base-2 representation of a megabyte (1048576 bytes).
+
+### units.gigabyte
+
+The base-2 representation of a gigabyte (1073741824 bytes).
+
+### units.terabyte
+
+The base-2 representation of a terabyte (1099511627776 bytes).
+
+### units.petabyte
+
+The base-2 representation of a petabyte (1125899906842624 bytes).
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/imports/version.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/imports/version.mdx
new file mode 100644
index 0000000000..59366c1087
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/imports/version.mdx
@@ -0,0 +1,151 @@
+---
+page_title: 'Import: version'
+sidebar_current: docs-imports-version
+description: |-
+ The version import provides functions for parsing versions and version constraints,
+ and verifying versions against a set of constraints.
+layout: docs
+---
+
+# Import: version
+
+The version import provides functions for parsing versions and version constraints,
+and verifying versions against a set of constraints.
+
+Versions are created through the `new` function, which accepts a string type in dotted-tri format (i.e. 1.0.0-alpha.1+001) that can represent a version.
+
+### version.new(v)
+
+Constructs a version from string value.
+
+```sentinel
+version.new("1.0.0-alpha.1+001")
+```
+
+### version.major
+
+An integer representation of the Major number for a given version .
+
+```sentinel
+version.new("1.0.0-alpha.1+001").major // 1
+```
+
+### version.minor
+
+An integer representation of the Minor number for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").minor // 0
+```
+
+### version.patch
+
+An integer representation of the Patch number for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").patch // 0
+```
+
+### version.prerelease
+
+A string representation of the Prerelease for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").prerelease // "alpha.1"
+```
+
+### version.metadata
+
+A string representation of the Metadata for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").metadata // "001"
+```
+
+### version.version
+
+A string representation of the version including pre-release and metadata information.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").version // "1.0.0-alpha.1+001"
+```
+
+### version.greater_than(v)
+
+Tests that the version is greater than a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").greater_than("0.12.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").gt("1.0.1-alpha.1+001") // False
+```
+
+### version.greater_than_or_equals(v)
+
+Tests that the version is greater than or equal to a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").greater_than_or_equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").gte("1.0.1-alpha.1+001") // False
+```
+
+### version.less_than(v)
+
+Tests that the version is less than a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").less_than("1.0.1-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").lt("0.12.0-alpha.1+001") // False
+```
+
+### version.less_than_or_equals(v)
+
+Tests that the version is less than or equal to a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").less_than_or_equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").lte("0.12.0-alpha.1+001") // False
+```
+
+### version.less_than_or_equals(v)
+
+Tests that the version equals a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").eq("0.12.0-alpha.1+001") // False
+```
+
+### version.compare(v)
+
+Compares one version with a given version. Compare returns -1, 0, or 1 if the version is less, equal, or greater than the other version, respectively.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").compare("0.12.0-alpha.1+001") // -1
+version.new("1.0.0-alpha.1+001").compare("1.0.0-alpha.1+001") // 0
+version.new("0.12.0-alpha.1+001").cmp("1.0.0-alpha.1+001") // 1
+```
+
+### version.satisfies(v, x)
+
+Tests that a version adheres to one or more version constraints. Satisfies supports the following restriction operators:
+
+- =
+- !=
+- \>
+- <
+- \>=
+- <=
+- ~>
+
+Satisfies supports the following usage scenarios:
+
+- A constraint with a pre-release can only match a pre-release version that contains the same major, minor and patch identifiers.
+- A constraint without a pre-release can only match a version without a pre-release.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").satisfies("> 1.0.0-alpha.1+001") // false
+version.new("1.0.0-alpha.1+001").satisfies(">= 1.0.0-alpha.1+001") // true
+version.new("1.0.0-alpha.1+001").satisfies(">= 1.0.0-alpha.1+001, < 1.0.0-beta+001") // true
+version.new("1.0.0").sat("= 1.0.0") // true
+version.new("0.12.7").sat("~> 0.12.0") // true
+```
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/index.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/index.mdx
new file mode 100644
index 0000000000..4497b40b83
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/index.mdx
@@ -0,0 +1,31 @@
+---
+layout: 'docs'
+page_title: 'Documentation'
+sidebar_current: 'docs-home'
+description: |-
+ Sentinel is a language and framework for policy built to be embedded in existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+[introduction](/sentinel/intro) to understand what Sentinel is, how it
+compares to other software, and more.
+
+
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/language/arithmetic.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/language/arithmetic.mdx
new file mode 100644
index 0000000000..c49f2fd0ce
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/language/arithmetic.mdx
@@ -0,0 +1,67 @@
+---
+page_title: Sentinel Language - Arithmetic
+sidebar_current: docs-language-arith
+description: >-
+ Sentinel supports arithmetic operators for integers and floats. Sentinel
+ supports sum, difference, product, quotient, and remainder.
+layout: docs
+---
+
+# Language: Arithmetic
+
+Sentinel supports arithmetic operators for integers and floats. Sentinel
+supports sum, difference, product, quotient, and remainder.
+
+```plaintext
++ sum
+- difference
+* product
+/ quotient
+% remainder
+```
+
+These operators work in a typical infix style:
+
+```sentinel
+4 + 8 // 12
+8 * 2 // 16
+8 / 4 // 2
+8 / 5 // 1
+8 % 5 // 3
+```
+
+## Order of Operations
+
+Arithmetic follows a standard mathematical order of operations.
+Grouping with parentheses can be used to affect ordering.
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+(4 + 5) * 2 // 18
+```
+
+A full table of operator precendence can be found on the
+[boolean expressions page](/sentinel/language/boolexpr). This
+shows how arithmetic operators relate to other operators.
+
+## Integer Division
+
+Integer division with a remainder rounds down to the nearest integer.
+Example: `8 / 3 is 2`.
+
+If the divisor is zero, an error occurs.
+
+## Mixed Numeric Operations
+
+Mixed numeric operations between integer and floating-point values are
+permitted. The result is a floating-point operation with the integer converted
+to a floating-point value for purposes of calculation.
+
+```sentinel
+import "types"
+
+a = 1.1 + 1 // 2.1
+types.type_of(a) // "float"
+```
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/language/boolexpr.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/language/boolexpr.mdx
new file mode 100644
index 0000000000..f1c518c3bf
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/language/boolexpr.mdx
@@ -0,0 +1,225 @@
+---
+page_title: Sentinel Language - Boolean Expressions
+sidebar_current: docs-language-boolexpr
+description: >-
+ Boolean expressions are expressions that evaluate to a boolean value from the
+ result of a combination of one or more comparisons and logical operators.
+layout: docs
+---
+
+# Language: Boolean Expressions
+
+Boolean expressions are expressions that evaluate to a boolean value
+from the result of a combination of one or more comparisons and logical
+operators. Boolean expressions form the basis of policy since policy can be broken
+down to a set of logical decisions that turn into true or false.
+
+A single boolean expression is the body of a [rule](/sentinel/language/rules).
+Additionally, boolean expressions are used with [conditionals](/sentinel/language/conditionals),
+and more.
+
+## Order of Operations
+
+The precedence of operators is shown below. Operators with higher
+precedence values (larger numbers) are evaluated first. Operators with
+the same precedence value are evaluated left-to-right.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Examples of this precedence are shown below:
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+```
+
+## Logical Operators
+
+Logical operators are used to change or combine logical expressions.
+There are three binary operators `and`, `or`, and `xor`. There is
+a single unary operator `not` (which can also be specified as `!`).
+
+The binary operators require two boolean operands and the unary
+operator requires a single boolean operand. In both case, the operands
+are values or expressions that are boolean types.
+
+Below is a basic explanation of the logical operators directly from
+the specification:
+
+```sentinel
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+Using parentheses to group boolean expressions, you can combine
+boolean expressions to become more complex or affect ordering:
+
+```sentinel
+(p and q) or r
+p and (q or r)
+```
+
+## Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+For any comparison, the two operands must be equivalent types. The one
+exception are integers and floats. When an integer is compared with
+a float, the integer is promoted to a floating point value.
+
+The available comparison operators are:
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+The behavior of `is` with `==` and `is not` with `!=` is identical.
+
+Example comparisons:
+
+```sentinel
+name is "Mitchell"
+idnumber > 42
+idnumber <= 12
+```
+
+Using the logical operators, these can be combined:
+
+```sentinel
+name is "Mitchell" and idnumber > 42
+```
+
+The language specification provides more detail on exactly
+[how comparison behaves](/sentinel/language/spec#comparison-operators).
+
+## Set Operators
+
+The set operators `contains` and `in` test for inclusion in a collection
+(a list or map).
+
+Set operators may 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.
+
+`contains` tests if the left-hand collection contains the right-hand value.
+`in` tests if the right-hand collection contains the left-hand value. For
+maps, "contains" looks at the keys, not the values.
+
+Examples:
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+```
+
+## Matches Operator
+
+The `matches` operator tests if a string matches a regular expression.
+The `matches` operator can be negated by prefixing the operator with
+`not`, such as `not matches`.
+
+The left-hand value is the string to test. The right-hand value is
+a string value representing a regular expression. The syntax of the regular
+expression is the same general syntax used by Python, Ruby, and other languages.
+The precise syntax accepted is the syntax accepted by RE2.
+
+Regular expressions are not anchored by default; any anchoring must be
+explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+## Any, All Expressions
+
+`any` and `all` expressions allow testing that any or all elements of a
+collection match some boolean expression. The result of an `any` and `all`
+expression is a boolean.
+
+`any` returns the boolean value `true` if any value in the collection expression
+results in the body expression evaluating to `true`. If the body expression
+evalutes to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression
+result in the body expression evaluating to `true`. If any value in the
+collection expression result in the body expression evaluating to `false`,
+the `all` expression returns `false`.
+
+For empty collections, `any` returns `false` and `all` returns `true`.
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" }
+any group.tasks as t { t.driver is "vmware" }
+```
+
+Since `any` and `all` expressions are themselves boolean expressions, they
+can be combined with other operators:
+
+```sentinel
+any ["a", "b"] as char { char is "a" } or
+other_value is "another"
+```
+
+## Emptiness Comparison
+
+The expressions `is empty` and `is not empty` provide a convenience
+method for determining the emptiness of a value. It supports the same types
+as the [built-in length](/sentinel/functions/length), collections and strings.
+
+```sentinel
+[] is empty // true
+[] is not empty // false
+["foo"] is empty // false
+["foo"] is not empty // true
+undefined is empty // undefined
+undefined is not empty // undefined
+```
+
+## Defined Comparison
+
+The expressions `is defined` and `is not defined` provide a convenience
+method for determining if a value has been defined. In other words, any value
+other than `undefined` can be considered as `defined`.
+
+```sentinel
+[] is defined // true
+4 is defined // true
+true is defined // true
+{} is defined // true
+undefined is defined // false
+[] is not defined // false
+4 is not defined // false
+true is not defined // false
+undefined is not defined // true
+```
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/language/collection-operations.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/language/collection-operations.mdx
new file mode 100644
index 0000000000..41da8f3584
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/language/collection-operations.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Sentinel Language - Collection Operations
+sidebar_current: docs-language-collection-operations
+description: |-
+ Operations for interacting with collections (list, map).
+layout: docs
+---
+
+# Language: Collection Operations
+
+Collection operations are expressions that are performed on a list or map to
+return a variation of the initial data.
+
+At the moment, `filter` is the only collection operation available.
+
+## Filter Expression
+
+`filter` is a [quantifier
+expression](/sentinel/language/spec#quantifier-expressions-any-all-filter-map) that
+returns a subset of the provided collection. Only elements whose filter body
+returns true will be returned. If any of the elements filter body returns
+undefined, the final result will be undefined.
+
+Filter uses the same syntax as the `any` and `all` [boolean
+expressions](/sentinel/language/boolexpr#any-all-expressions):
+
+```sentinel
+filter list as value { condition } // Single-iterator, list
+filter list as idx, value { condition } // Double-iterator, list
+
+filter map as key { condition } // Single-iterator, map
+filter map as key, value { condition } // Double-iterator, map
+```
+
+Examples:
+
+```sentinel
+l = [1, 1, 2, 3, 5, 8]
+evens = filter l as v { v % 2 is 0 } // [2, 8]
+
+m = { "a": "foo", "b": "bar" }
+matched_foo = filter m as _, v { v is "foo" } // { "a": "foo" }
+```
+
+## Map Expression
+
+`map` is a [quantifier
+expression](/sentinel/language/spec#quantifier-expressions-any-all-filter-map) that
+returns a list, regardless of the input collection type. Each element within the
+input collection is evaluted according to the map expression body and appended
+to the result.
+
+```sentinel
+l = [1, 2]
+r = map l as v { v % 2 } // [false, true]
+
+m = { "a": "foo", "b": "bar" }
+r = map m as k, v { v } // ["foo", "bar"]
+```
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/language/conditionals.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/language/conditionals.mdx
new file mode 100644
index 0000000000..1736795dbb
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/language/conditionals.mdx
@@ -0,0 +1,161 @@
+---
+page_title: Sentinel Language - Conditionals
+sidebar_current: docs-language-conditional
+description: |-
+ Conditional statements allow your policy to behave differently depending on a condition.
+layout: docs
+---
+
+# Language: Conditionals
+
+Conditional statements allow your policy to behave differently depending
+on a condition.
+
+Conditional statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## If Statements
+
+`if` statements only execute their bodies if a condition is met.
+The syntax of an `if` statement is:
+
+```sentinel
+if condition {
+ // ... this is executed if condition is true
+}
+```
+
+The `condition` must result in a boolean, such as by calling a function
+or evaluating a [boolean expression](/sentinel/language/boolexpr). If
+the `condition` is `true`, the body (within the `{}`) is executed. Otherwise,
+the body is skipped.
+
+Examples:
+
+```sentinel
+// This would execute the body
+value = 12
+if value is 18 {
+ print("condition met")
+}
+
+// Direct boolean values can be used
+value = true
+if value {
+ print("condition met")
+}
+
+// This would not execute the body since the boolean expression will
+// result in undefined.
+value = {}
+if value["key"] > 12 {
+ print("condition met")
+}
+```
+
+### Else, Else If
+
+An `else` clause can be given to an `if` statement to execute a body
+in the case the condition is _not met_. By putting another `if` statement
+directly after the `else`, multiple conditions can be tested for.
+The syntax is:
+
+```sentinel
+if condition {
+ // ...
+} else {
+ // ...
+}
+
+if condition {
+ // ...
+} else if other_condition {
+ // ...
+} else {
+ // ...
+}
+```
+
+### Scoping
+
+The body of an `if` statement does not create a new
+[scope](/sentinel/language/scope). Any variables assigned within the body
+of an if statement will modify the scope that the `if` statement itself
+is in.
+
+Example:
+
+```sentinel
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+```sentinel
+a = 18
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+## Case Statements
+
+`case` statements are a selection control mechanism that execute a clause based
+on matching expressions. It is worth noting that the expression for `case` is
+optional. When no expression is provided, it defaults the expression to `true`.
+Additionally, the order of clauses is important, as they are evaluated from top
+to bottom, executing the first match.
+The syntax of a case statement is:
+
+```sentinel
+case expression {
+ when clause_expression:
+ // executed when clause_expression and expression are equal
+ else:
+ // executed if no clause matches expression
+}
+```
+
+### When Clause
+
+Any clause that has an expression for comparison must use the `when` keyword.
+It accepts a list of expressions, seperated by a `,`.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+}
+```
+
+```sentinel
+case {
+ when x > 40:
+ return true
+}
+```
+
+### Else Clause
+
+The `else` keyword allows for capturing any expressions that have no matching
+`when` clause.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+ else:
+ return false
+}
+```
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/language/functions.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/language/functions.mdx
new file mode 100644
index 0000000000..8214078b6b
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/language/functions.mdx
@@ -0,0 +1,229 @@
+---
+page_title: Sentinel Language - Functions
+sidebar_current: docs-language-functions
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Functions
+
+Functions allow you to create reusable code to perform computations.
+
+Below is a trivial example function that doubles a number and shows
+the main rule using the function:
+
+```sentinel playground
+double = func(x) {
+ return x * 2
+}
+
+main = rule { double(12) is 24 }
+```
+
+Functions may contain any expressions,
+[conditionals](/sentinel/language/conditionals),
+and [loops](/sentinel/language/loops). They must return a value
+at the end of their execution. If no reasonable return value exists,
+the function should return [undefined](/sentinel/language/undefined).
+
+## Function Types
+
+### Named Functions
+
+-> **NOTE:** Named functions must be created within the [package scope](/sentinel/language/scope#package-scope).
+
+Named functions are declared using the `func` keyword as its own statement.
+They provide a safe method of creating functions and have additional
+restrictions that do not apply to anonymous functions.
+
+Firstly, named functions cannot be re-assigned, and also cannot use a name
+that is already used elsewhere. For instance, the below example will error due
+to the attempt to reassign the named function identifier to a new value:
+
+```sentinel
+func sum(a, b) {
+ return a + b
+}
+
+sum = 4
+```
+
+Additionally, the following will error due to the named function attempting
+to make use of an already assigned identifier:
+
+```sentinel
+sum = 4
+
+func sum(a, b) {
+ return a + b
+}
+```
+
+Named functions are helpful for policy authors to declare critical functions
+whose value or implementation should not be changed.
+
+### Anonymous Functions
+
+An anonymous function is created by assigning a variable to a `func`. The
+variable can be reassigned at any time including to different value types.
+Anonymous functions are helpful for use cases like closures, where a function
+can return another function.
+
+```sentinel
+func makeAdder(a) {
+ return func(b) {
+ return a + b
+ }
+}
+```
+
+## Creating a Function
+
+A function can have zero or more parameters. These parameters are specified
+within parentheses `()` separated by commas. If a function has no parameters,
+the parentheses must still be present.
+
+A function must terminate with a `return` statement to return a value back to
+the caller. Only a single value can be returned. The type of a return can
+vary between return statements.
+
+Anonymous function example:
+
+```sentinel
+add1 = func(x) { return x + 1 }
+```
+
+Named function example:
+
+```sentinel
+func add1(x) {
+ return x + 1
+}
+```
+
+Both examples create a function that adds 1 to the parameter `x`.
+
+## Calling a Function
+
+Functions are called by accessing their name followed by parentheses with
+a list of comma-separated values for the parameters. If the function takes no
+parameters, an empty set of parentheses must still be used to denote a
+function call.
+
+To call the function in the example above:
+
+```sentinel
+x = 1
+y = add1(x)
+```
+
+## Scoping
+
+The body of a `func` creates a new [scope](/sentinel/language/scope).
+
+The parent scope is the scope in which the function is created. This allows
+for the creation of functions within functions that can access their outer
+scopes.
+
+Example:
+
+```sentinel
+f = func() {
+ a = 42
+ print(a) // 42
+ return undefined
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+f = func() {
+ a = 42
+ return undefined
+}
+
+print(a) // 18
+```
+
+And below is an example of creating a function in a function which uses
+outer values:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double()
+}
+
+print(f()) // 84
+```
+
+A more complex example below shows how scoping works when passing
+functions around as arguments or results:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double
+}
+
+double = f()
+print(double()) // 84
+```
+
+## Pass By Value
+
+The parameters to a function are passed by value, but not deep copied.
+This means that elements of collections can still be modified but the
+root value cannot be changed.
+
+Example:
+
+```sentinel
+f = func(x) {
+ x = "value"
+ return x
+}
+
+x = "outside"
+f(x)
+print(x) // "outside"
+```
+
+```sentinel
+f = func(x) {
+ append(x, "value")
+ return x
+}
+
+x = []
+f(x)
+print(x) // ["value"]
+```
+
+## Recursion
+
+A function is allowed to call other functions, including itself.
+This can be used to implement recursion. For example, the fibonacci sequence
+is implemented below:
+
+```sentinel
+fib = func(x) {
+ if x <= 0 {
+ return undefined
+ }
+
+ if x == 1 {
+ return 1
+ } else {
+ return x + fib(x - 1)
+ }
+}
+```
+
+Note that this example also shows using
+[undefined](/sentinel/language/undefined)
+as a return value in cases where a function has undefined behavior.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/language/imports.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/language/imports.mdx
new file mode 100644
index 0000000000..0ed1541b85
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/language/imports.mdx
@@ -0,0 +1,116 @@
+---
+page_title: Sentinel Language - Imports
+sidebar_current: docs-language-imports
+description: >-
+ Imports enable a Sentinel policy to access reusable libraries and external
+ data and functions.
+layout: docs
+---
+
+# Language: Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external
+data and functions. The exact list of available imports is dependent on the
+host system. Host systems may also make the available imports configurable
+via plugins.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that
+the import `calendar` allows access to engineer calendars. This import
+only exists for the purpose of this example and at the time of writing is
+not a real available import.
+
+```sentinel
+import "calendar"
+
+// Get the calendar for Bob for today
+bob_calendar = calendar.for("bob").today
+
+// Allow this policy to pass if Bob is not on vacation.
+main = rule { not bob_calendar.has_event("vacation") }
+```
+
+This example shows an import being used to deny a behavior if Bob is on
+vacation. Hopefully real policies do not depend on a single person being
+in the office, but the example shows the power of imports.
+
+In addition to consuming imports, you can also
+[extend Sentinel by writing custom imports](/sentinel/extending)
+This allows you to add any arbitrary behavior to your Sentinel-protected
+systems.
+
+## Importing
+
+Whether accessing a built-in library or an external plugin, the syntax
+for an import is the same:
+
+```sentinel
+import "name"
+```
+
+This adds the import with the name `name`. It can be referenced using
+the identifier `name`. For example, if the import above exposed first
+and last name data, you could access it via `name.first` or `name.last`.
+
+You can shadow imports by assigning a variable. In the example below,
+the import `name` becomes unavailable within the function because it is
+shadowed by a variable of the same name.
+
+Shadowing an import is not the same as overwriting a variable. Imports
+are not part of the [scope](/sentinel/language/scope), meaning that
+the shadow only exists for the scope it is created within. For everything
+else, the import works even after it is shadowed. The example below shows that
+as well.
+
+```sentinel
+import "name"
+
+f = func() {
+ name = "jane"
+ return name
+}
+
+print(f()) // "jane"
+print(name.first) // "bob"
+```
+
+## Aliases
+
+An import can be aliased to another name using the syntax below:
+
+```sentinel
+import "name" as other
+```
+
+This would make the import "name" available as `other`.
+
+An import may only be imported once, regardless of aliases. The following
+would be **invalid** and would result in an error:
+
+```sentinel
+import "name" as one
+import "name" as two
+```
+
+## Accessing Import Data
+
+Imports are accessed with dot-separated identifiers, such as `name.first` or
+`name.stage.first`. These are called _selector expressions_. An import may
+return nested data that further can be accessed via selector expressions.
+
+In the first example on this page with the "calendar" import, we see this:
+
+```sentinel
+import "calendar"
+
+a = calendar.for("bob") // selector expression calendar.for
+b = a.today // selector expression on the result
+c = b.vacation // accessing further nested data
+```
+
+The exact structure of an import is dependent on the import. Please reference
+the documentation for an import to learn more.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/language/index.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/language/index.mdx
new file mode 100644
index 0000000000..7f588c94cd
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/language/index.mdx
@@ -0,0 +1,114 @@
+---
+page_title: Sentinel Language
+sidebar_current: docs-language-basics
+description: |-
+ Sentinel policies are written using the Sentinel language. This language
+ is easy to learn and easy to write. You can learn the Sentinel language
+ and be productive within an hour. Learning Sentinel doesn't require any
+ formal programming experience.
+layout: docs
+---
+
+# Sentinel Language
+
+Sentinel policies are written using the Sentinel language. This language
+is easy to learn and easy to write. You can learn the Sentinel language
+and be productive within an hour. Learning Sentinel doesn't require any
+formal programming experience.
+
+This language guide will contain many details that are not necessary to
+be productive with Sentinel or are targeted to those who are looking for
+exact information about the language (such as what types of line endings are
+allowed). You can safely ignore these sentences.
+
+You may also view the
+[official language specification](/sentinel/language/spec)
+This is a specific and detailed document on the syntax and behavior of
+the language primarily intended for implementation creators and to disambiguate
+the language.
+
+## Simplest Example
+
+The example below is about the simplest practical example of Sentinel.
+It is reasonable to imagine this as a realistic policy. This shows that
+in most cases, Sentinel will be extremely simple:
+
+```sentinel
+main = rule { request.method is "GET" and request.headers contains "X-Key" }
+```
+
+## Files
+
+Sentinel policies are single files that end in the `.sentinel` file extension.
+There is currently no built-in mechanism to Sentinel for merging multiple
+files. This is purposefully done to make Sentinel policies easy to submit
+to systems that support Sentinel policies.
+
+Sentinel policy files must be UTF-8 encoded and can end in both
+Unix (LF) or Windows (CRLF) line breaks. When running the
+[auto-formatter](#),
+line endings will always use Unix line breaks.
+
+## Ordering
+
+Sentinel policies are executed top-down. For example:
+
+```sentinel
+a = 1 // a = 1 here
+b = a + 1 // b = 2 here
+a = 3 // a = 3, b = 2
+```
+
+In this example, the value of `a` and `b` is shown at each line. Since Sentinel
+executes values top-down, the final value of `a` is 3 and `b` is 2. `b` _does
+not_ become 4.
+
+## Main
+
+Sentinel expects there to be a `main` [rule](/sentinel/language/rules).
+The value of this rule is the result of the entire policy.
+
+The result of the policy depends on the evaluated contents of the `main` rule.
+For booleans, a policy passes on a `true` value, and fails on a `false` value.
+Other types generally follow a zero or zero-length pattern for determining
+success.
+
+| Type | Passing Condition | Failing Condition |
+| ------- | ----------------- | -------------------------- |
+| Boolean | `true` | `false` |
+| String | `""` | Any non-zero length string |
+| Integer | `0` | Any non-zero value |
+| Float | `0.0` | Any non-zero value |
+| List | `[]` | Any non-zero length list |
+| Map | `{}` | Any non-zero length map |
+
+A value of main that falls outside of the above types will result in a policy
+error. As a special case, if `main` evaluates to an undefined value, the error
+message will indicate as such, with a reference to where the undefined value was
+encountered.
+
+## More Complex Example
+
+The simple example above is a full working example. In our experience with
+Sentinel, many policies can be representing using this simple form. However,
+to show more features of the language, a more complex example is shown below.
+This example is also a realistic example of what Sentinel may be used for.
+
+```sentinel
+import "units"
+
+memory = func(job) {
+ result = 0
+ for job.groups as g {
+ for g.tasks as t {
+ result += t.resources.memory else 0
+ }
+ }
+
+ return result
+}
+
+main = rule {
+ memory(job) < 1 * units.gigabyte
+}
+```
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/language/lists.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/language/lists.mdx
new file mode 100644
index 0000000000..bc5269d8eb
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/language/lists.mdx
@@ -0,0 +1,135 @@
+---
+page_title: Sentinel Language - Lists
+sidebar_current: docs-language-lists
+description: Lists are a collection of zero or more values.
+layout: docs
+---
+
+# Language: Lists
+
+Lists are a collection of zero or more values.
+
+Lists can be created using by wrapping values in `[]` and separating them
+by commas. An optional trailing comma is allowed. List elements can be
+differing types. Examples:
+
+```sentinel
+[] // An empty list
+["foo"] // Single element list
+["foo", 1, 2, true] // Multi element list with different types
+["foo", [1, 2]] // List containing another list
+```
+
+A list can be [sliced](/sentinel/language/slices) to create sublists.
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for value inclusion in a list.
+
+## Accessing Elements
+
+List elements can be accessed with the syntax `name[index]` where
+`index` is zero-indexed.
+
+A negative index accesses the list in reverse. It is the same as
+reversing a list and then using a positive index. Similar to a positive
+index, it is bounded by the length of the list as a negative value.
+
+Accessing beyond the length of the list results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+a = ["foo", 1, true, [1, 2]]
+
+a[0] // "foo"
+a[2] // true
+a[4] // undefined
+a[-2] // true
+a[-4] // "foo"
+a[-5] // undefined
+a[3][1] // 2
+```
+
+## List Append
+
+Values can be appended to a list using the built-in
+[append](/sentinel/functions/append) function.
+
+This modifies the list in-place and returns
+[undefined](/sentinel/language/undefined). For more information on
+why `append` behaves this way, please read the full documentation for the
+[append](/sentinel/functions/append) function.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append([1,2], "foo") // [1, 2, "foo"]
+append([1,2], [3]) // [1, 2, [3]]
+append(1, 3) // error()
+```
+
+## List Concatenation
+
+Two lists can be concatenated using the `+` operator or the shorthand
+`+=` assignment operator. For the `+` operator, a new list is returned.
+For `+=`, the left-hand list is modified in place.
+
+Examples:
+
+```sentinel
+[1] + [2] // [1, 2]
+[1] + [[1]] // [1, [1]]
+[1] + 1 // error
+
+a = [1]
+a += [2] // a = [1, 2]
+a += 3 // error
+```
+
+## List Length
+
+The length of a list can be retrieved using the
+[length](/sentinel/functions/length) function.
+
+Examples:
+
+```sentinel
+length([]) // 0
+length(["foo"]) // 1
+```
+
+## Removing Items From a List
+
+You can use a combination of [list concatenation](#list-concatenation) and
+[slicing](/sentinel/language/slices) to remove elements from a list.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+a = a[:2] + a[3:] // [1, 2, 4, 5]
+```
+
+The shorthand shown here is effectively the same as `a[0:2] + a[3:length(a)]`,
+which creates a new list out of the concatenation two sub-lists composed of the
+first two elements, and the rest of the list starting at index 3. This
+effectively removes the 3rd element from the list (index 2).
+
+## List Comparison
+
+Lists may be [compared](/sentinel/language/boolexpr#comparison-operators)
+for equality. Lists are equal if they are of equal length and their
+corresponding elements are comparable and equal. Lists with the same elements
+but in a different order are not equal.
+
+```sentinel
+[1, 2] is [1, 2] // true
+[1, 2] is [2, 1] // false
+["a"] is ["a", "b"] // false
+["a", ["b", "c"]] is ["a", ["b", "c"]] // true
+```
+
+List comparison speed is O(N), meaning that the speed of the comparison is
+_linearly_ proportional to the number of elements in the list. The more
+elements, the more iterations that are necessary to verify equality.
+
+-> The N-value quoted above should account for the sum of the elements of _all_
+lists in the subjects of comparison, as list comparison will recurse into these
+lists to check for equality.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/language/logging-errors.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/language/logging-errors.mdx
new file mode 100644
index 0000000000..705a72cbd4
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/language/logging-errors.mdx
@@ -0,0 +1,52 @@
+---
+page_title: Sentinel Language - Logging and Errors
+sidebar_current: docs-language-log
+description: The built-in functions `print` and `error` can be used for logging and errors.
+layout: docs
+---
+
+# Language: Logging and Errors
+
+The built-in functions `print` and `error` can be used for logging
+and errors. Logging is helpful to understand how a policy is executing
+in development. And errors are useful to halting execution immediately in
+certain scenarios.
+
+## Print
+
+The `print` function is used to output debug information. The exact location
+where this print statements go is dependent on the host application and
+the Sentinel runtime itself.
+
+The `print` function takes zero or more Sentinel values as arguments.
+Each value is formatted for human-friendly output and space-separated.
+Example:
+
+```sentinel
+value = 42
+print("the value is", value) // the value is 42
+
+map = { "foo": false }
+print(map) // { "foo": false }
+
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // false
+```
+
+## Errors
+
+Certain actions in Sentinel, such as accessing an undefined variable,
+attempting to add two incompatible types, etc. result in _runtime errors_.
+These errors halt the execution of a policy immediately. The pass/fail result
+of a policy is considered fail.
+
+Runtime errors can be manually triggered using the `error` function.
+The `error` function behaves exactly like the `print` function except that
+execution is halted immediately.
+
+This should only be used in scenarios where the policy _must fail_ due to
+some unexpected or invalid condition. The line between `error` and
+[undefined](/sentinel/language/undefined) can sometimes be unclear. Undefined
+is recommended when a policy can conceivably recover or safely ignore the
+undefined behavior. An error should be used when the policy should fail and
+notify someone regardless.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/language/loops.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/language/loops.mdx
new file mode 100644
index 0000000000..622732bd35
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/language/loops.mdx
@@ -0,0 +1,92 @@
+---
+page_title: Sentinel Language - Loops
+sidebar_current: docs-language-loops
+description: >-
+ Loop statements allow you to execute a body of code for each element in a
+ collection or for some fixed number of times.
+layout: docs
+---
+
+# Language: Loops
+
+Loop statements allow you to execute a body of code for each element in
+a collection or for some fixed number of times.
+
+Loop statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## For Statements
+
+`for` statements allow repeated execution of a block for each
+element in a collection.
+
+Example:
+
+```sentinel
+// A basic sum
+count = 0
+for [1, 2, 3] as num {
+ count += num
+}
+```
+
+The syntax is `for COLLECTION as value`. This will iterate over the
+collection, assigning each element to `value`. In the example above,
+each element is assigned to `num`. The body is executed for each element.
+In the example above, the body adds `num` to the `count` variable. This
+creates a basic sum of all values in the collection.
+
+For a map, the assigned element is the key in the map. In the example
+below, `name` would be assigned map keys.
+
+```sentinel
+list = []
+for { "a": 1, "b": 2 } as name {
+ append(list, name)
+}
+
+print(list) // ["a" "b"]
+```
+
+An alternate syntax is `for COLLECTION as key, value`. This will assign
+both the key and value to a variable. For a list, the key is the element
+index. For a map, it is the key and value is assigned the element value.
+Example:
+
+```sentinel
+count = 0
+for { "a": 1, "b": 2 } as name, num {
+ count += num
+}
+
+print(count) // 3
+```
+
+## Scoping
+
+The body of a `for` statement creates a new
+[scope](/sentinel/language/scope). If a variable is assigned within
+the body of a for statement that isn't assigned in a parent scope,
+that variable will only exist for the duration of the body execution.
+
+Example:
+
+```sentinel
+for list as value {
+ a = 42
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+for list as value {
+ a = 42
+}
+
+print(a) // 18
+```
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/language/maps.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/language/maps.mdx
new file mode 100644
index 0000000000..298bf9a567
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/language/maps.mdx
@@ -0,0 +1,121 @@
+---
+page_title: Sentinel Language - Maps
+sidebar_current: docs-language-maps
+description: >-
+ Maps are a collection of zero or more key/value pairs. These are useful for
+ storing values that are looked up by a unique key.
+layout: docs
+---
+
+# Language: Maps
+
+Maps are a collection of zero or more key/value pairs. These are useful
+for storing values that are looked up by a unique key.
+
+Maps can be created using `{}` and specifying key/value pairs. Keys and
+values can be differing types. An optional trailing comma is allowed.
+Examples:
+
+```sentinel
+// Empty map
+{}
+
+// Map with a single value on one line
+{ "key": "value" }
+
+// Map with multiple values with differing types on multiple lines
+{
+ "key": "value",
+ 42: true,
+}
+```
+
+Maps are **unordered**. When
+[looping](/sentinel/language/loops)
+over a map, the key/value pairs can be returned in any order.
+
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for key inclusion in a map.
+
+## Accessing Elements
+
+Map elements can be accessed with the syntax `name[key]`. This looks up
+a value by a key.
+
+Accessing a key that doesn't exist results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+map = { "key": "value", 42: true, }
+
+map["key"] // "value"
+map[42] // true
+map[0] // undefined
+```
+
+## Modifying or Adding Elements
+
+Elements can be added or modified in a map by assigning to `name[key]`.
+If the key doesn't exist, the value is added. If the key already exists,
+the value is overridden.
+
+```sentinel
+map = { "key": "value" }
+
+map[42] = true // Add a new key/value
+map["key"] = 12 // Modify the value of "key"
+```
+
+## Deleting Elements
+
+An element can be deleted from a map using the
+[delete](/sentinel/functions/delete) function.
+
+Examples:
+
+```sentinel
+map = { "key": "value" }
+delete(map, "key") // map is now empty
+delete(map, "other") // no effect for non-existent key
+```
+
+## Keys and Values
+
+The keys and values of a map can be retrieved as
+[lists](/sentinel/language/lists) using the
+[keys](/sentinel/functions/keys) and
+[values](/sentinel/functions/values) functions.
+
+Because maps are unordered, the keys and values are returned in an
+unspecified order. It should not be assumed that keys and values will be
+returned in a consistent order.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+## Map Comparison
+
+Maps may be [compared](/sentinel/language/boolexpr#comparison-operators) for
+equality. Maps are equal if they are of equal length and both their
+corresponding keys and values are comparable and equal.
+
+```sentinel
+{"foo": "bar"} is {"foo": "bar"} // true
+{"foo": "bar"} is {"baz": "bar"} // false
+{"foo": "bar"} is {"foo": "baz"} // false
+{"foo": "bar"} is {"foo": "bar", "baz": "qux"} // false
+{1: "a"} is {1.0: "a"} // true (int/float comparable)
+
+// also true (maps are not ordered):
+{"m": {"a": "b"}, "l": ["a"]} is {"l": ["a"], "m": {"a": " b"}}
+```
+
+-> The current worst-case comparison speed for maps is O(N²), or _quadratic
+time_. This is the square of the cumulative elements or the parent and all child
+maps contained within the map. Consider this when making comparisons on larger,
+more complex maps. This may be optimized in the future.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/language/parameters.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/language/parameters.mdx
new file mode 100644
index 0000000000..348acf2593
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/language/parameters.mdx
@@ -0,0 +1,133 @@
+---
+page_title: Sentinel Language - Parameters
+sidebar_current: docs-language-parameters
+description: >-
+ Parameteres allow a Sentinel policy to describe variables that are expected
+ to be supplied by the calling application, in addition to any default value.
+layout: docs
+---
+
+# Language: Parameters
+
+Sentinel allows a policy author to supply parameters to help facilitate policy
+reuse and ensure sensitive values do not need to be hard-coded in a policy.
+
+Parameters are supplied by using the `param` keyword, followed by an identifier.
+A default value can also be supplied by using the `default` keyword.
+
+```sentinel
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+```
+
+Once declared, parameters can be used like any other variable, including being
+re-assigned.
+
+```sentinel
+param foo default 1 // 1 (default)
+
+foo = foo + 1 // 2
+```
+
+## Variable Descriptions
+
+You can supply a description to a parameter by adding a comment at the top of
+it. This value can be communicated to a specific implementation of Sentinel to
+provide information about what the parameter is for during configuration.
+
+```sentinel
+// An example parameter. Must be supplied or the policy will fail.
+param foo
+```
+
+## Supplying Parameter Values Using the Sentinel CLI
+
+In a production implementation, supplying parameters to a policy is an
+implementation-specific detail - see the documentation for your particular
+implementation for details.
+
+Using the [Sentinel CLI](/sentinel/commands), you can supply parameters one of
+four ways.
+
+### Supplying Parameter Values Using the Configuration File
+
+You can supply parameters using the
+[`param`](/sentinel/configuration#parameters) section of the
+[configuration file](/sentinel/configuration).
+
+```hcl
+param "foo" {
+ value = "bar"
+}
+```
+
+This method works for both `sentinel apply` and `sentinel test`.
+
+### Supplying Parameter Values Using the Environment
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can supply a value using environment variables - prefix the parameter with
+`SENTINEL_PARAM_`, using the name of the parameter to supply.
+
+```plaintext
+SENTINEL_PARAM_foo=bar sentinel apply policy.sentinel
+```
+
+### Supplying Parameter Values Using the CLI
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can also use the `-param` CLI argument to supply parameter in a `key=value`
+pair.
+
+```plaintext
+sentinel apply -param foo=bar policy.sentinel
+```
+
+### Interactive CLI Prompting
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+If a required value has not been supplied when a policy is run with `sentinel apply`, it will be prompted for, along with its description:
+
+
+
+ $ sentinel apply policy.sentinel
+
+ policy.sentinel:2:7: requires value for parameter foo
+
+ An example parameter. Must be supplied or the policy will fail.
+
+
+ Values can be strings, floats, or JSON array or object values. To
+ force
+
+ strings, use quotes.
+
+
+ Enter a value: bar
+
+
+
+ Pass
+
+
+
+
+### CLI Value Format
+
+-> **NOTE:** This section contains details for the parameter features supported by `sentinel apply`.
+
+The CLI takes either strings, or JSON numbers, arrays, or maps. If you need a
+literal string value, quote the value.
+
+```sentinel
+foo // string
+42 // number (float)
+"42" // string ("42", without quotes)
+[1, 2] // array (list)
+{"a": "b"} // object (map)
+```
+
+-> **NOTE:** Boolean values are not supported by this method.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/language/rules.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/language/rules.mdx
new file mode 100644
index 0000000000..e31924d414
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/language/rules.mdx
@@ -0,0 +1,204 @@
+---
+page_title: Sentinel Language - Rules
+sidebar_current: docs-language-rules
+description: >-
+ Rules form the basis of a policy by representing behavior that is either
+ passing or failing (true or false).
+layout: docs
+---
+
+# Language: Rules
+
+Rules form the basis of a policy by representing behavior that is either passing
+or failing. A policy can be broken down into a set of rules. Breaking down a
+policy into a set of rules can make it more understandable and aids with
+testing.
+
+An example is shown below:
+
+```sentinel playground
+weather = "sunny"
+day = "wednesday"
+is_sunny = rule { weather is "sunny" }
+is_wednesday = rule { day is "wednesday" }
+
+main = rule {
+ is_sunny and
+ is_wednesday
+}
+```
+
+A rule contains a single expression. This can be a simple [boolean
+expression](/sentinel/language/boolexpr), or an expression representing the
+discovery of a set of violations using more in-depth expressions like [`filter`
+and `map`](/sentinel/language/collection-operations). You can split expressions
+into multiple lines for readability, as you would with any other expression
+within Sentinel.
+
+A rule is also _lazy_ and _memoized_. _Lazy_ means that the rule is only
+evaluated when it is actually required, not at the point that it is
+created. This is covered in more detail in the [lazy](#lazy-and-memoized) section.
+_Memoized_ means that the value is only computed once and then saved. Once
+a rule is evaluated, the result of it is reused anytime the rule is referenced
+again.
+
+## Making rules easier to understand
+
+Rules are an abstraction that make policies much more understandable
+by making it easier for humans to see what the policy is trying to do.
+
+For example, consider the policy before which doesn't abstract into rules:
+
+```sentinel
+main = rule {
+ ((day is "saturday" or day is "sunday") and homework is "") or
+ (day in ["monday", "tuesday", "wednesday", "thursday", "friday"] and
+ not school_today and homework is "")
+}
+```
+
+Assume that `day`, `homework`, and `school_day` are available.
+
+In plain English, this policy is trying to say: you can play with your friends
+only on the weekend as long as there is no homework, or during the week if
+there is no school and no homework.
+
+Despite the relatively simple nature of this policy (a few lines, about 5
+logical expressions), it is difficult to read and understand, especially if
+you've not seen it before.
+
+The same policy using rules as an abstraction:
+
+```sentinel
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+is_valid_weekend = rule { is_weekend and homework is "" }
+is_valid_weekday = rule { not is_weekend and not school_today and homework is "" }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+By reading the names of the rules, its much clearer to see what each individual
+part of the policy is trying to achieve. The readability is improved even
+further when adding comments to the rules to explain them further, which is
+a recommended practice:
+
+```sentinel
+// A weekend is Sat or Sun
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+// A valid weekend is a weekend without homework
+is_valid_weekend = rule { is_weekend and homework is "" }
+
+// A valid weekday is a weekday without school
+is_valid_weekday = rule { not is_weekend and not school_today and homework is "" }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+## "When" Predicates
+
+A rule may have an optional `when` predicate attached to it. When this is
+present, the rule is only evaluated when the predicate results in `true`.
+Otherwise, the rule is not evaluated and the result of the rule is always
+`true`.
+
+"When" predicates should be used to define the context in which the rule
+is semantically meaningful. It is common when translating human language
+policy into Sentinel to have scenarios such as "when the key has a prefix
+of `/account/`, the remainder must be numeric." In that example, when the
+key _does not_ have the specified prefix, the policy doesn't apply.
+
+Assume you have rules `is_prefix` and `is_numeric` to check both the
+conditions in the example above. An example is shown with and without
+the "when" predicate:
+
+```sentinel
+example_no_when = rule { (is_prefix and is_numeric) or not is_prefix }
+
+example_when = rule when is_prefix { is_numeric }
+```
+
+The rules are equivalent in behavior for all values of `is_prefix` and
+`is_numeric`, but the second rule is more succint and easier to understand.
+
+## Testing
+
+To verify a policy works correct, the
+[built-in Sentinel test framework](/sentinel/writing/testing)
+uses rules as the point where you can assert behavior. You say that
+you expect certain rules to be true or false. By doing so, you ensure that
+the policy results in the value you expect using the rule flow that you
+expect.
+
+Therefore, in addition to readability, we recommend splitting policies into
+rules to aid testability.
+
+## Non-Boolean Values
+
+Sentinel supports non-boolean values in rules. This can be useful for passing
+more detail along to a calling integration when more detail is required than a
+boolean value would be able to communicate. This data shows up in the [policy
+trace](/sentinel/writing/tracing) and can be utilized in different ways
+depending on the integration you are using Sentinel with.
+
+Consider a different take on our policy above:
+
+```sentinel
+// A policy to check a set of scheduled days to determine what days you can't
+// come out and play, based on the day not being a weekend day and there being
+// homework to do.
+
+param days
+
+main = rule {
+ filter days as d {
+ d.day not in ["saturday", "sunday"] and
+ d.homework is not ""
+ }
+}
+```
+
+When given a value in the set of `days` that has a `day` that is a weekday, and
+`homework` present, the policy will fail. Additional, when tracing was enabled,
+the days that were detected as violating the policy would be given in the trace
+for the `main` rule.
+
+Generally, for non-boolean values, a policy fails on non-zero data. See the
+details on [the `main` rule](/sentinel/language#main) for more details.
+
+The result of a rule must be either a boolean, string, integer, float, list, or
+map. All other types will result in a runtime error.
+
+## Lazy and Memoized
+
+A rule is _lazy_ and _memoized_.
+
+_Lazy_ means that the rule is only evaluated when it is actually required,
+not at the point that it is created. And _memoized_ means that the value is
+only computed once and then saved. Once a rule is evaluated, the result of it
+is reused anytime the rule is referenced again.
+
+Both of these properties have important implications for Sentinel
+policies:
+
+**Performance:** Rules are a way to improve the performance of a Sentinel
+policy. If you have a boolean expression that is reused a lot, a rule will
+only be evaluated once. In the example shown above, `is_weekend` is used
+multiple times, but will only have to be evaluated once.
+
+**Behavior:** Rules accessing variables see the value of those variables
+at _the time they are evaluated_. This can lead to surprising behavior
+in some cases. For example:
+
+```sentinel playground
+a = 1
+b = rule { a == 1 }
+a = 2
+main = b
+```
+
+In this example, `main` will actually result to `false`. It is evaluated
+when it is needed, which is when the policy executes `main`. At this point,
+`a` is now 2 and `b` has not been evaluated yet. Therefore, `b` becomes `false`.
+All future references to `b` will return `false` since a rule is memoized.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/language/scope.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/language/scope.mdx
new file mode 100644
index 0000000000..f498e26dc2
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/language/scope.mdx
@@ -0,0 +1,47 @@
+---
+page_title: Sentinel Language - Scope
+sidebar_current: docs-language-scope
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Scope
+
+Scope is the context in which variables are created and accessed. If a
+variable exists in a parent scope, it is accessed and can be modified.
+Otherwise, the variable is created in your current scope.
+
+Scopes are created with _blocks_. A block is a possibly empty sequence
+of statements within matching brace brackets `{}`. You may nest blocks
+to create nested scopes.
+
+## Package Scope
+
+The package scope is the top level scope within a policy file and encapsulates
+the entire file contents. Imports, parameters and named functions must be
+declared within the package scope.
+
+## Implicit Scopes
+
+Each `any`, `all`, and `for` statement is considered to be in its own block.
+Note that `if` statements _do not_ create their own block.
+
+## Examples
+
+The example policy below shows various effects of scopes:
+
+```sentinel
+a = 1
+print(a) // 1
+
+f = func() {
+ print(a) // 1
+ a = 12
+ b = 1
+ return undefined
+}
+f()
+
+print(a) // 12
+print(b) // undefined
+```
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/language/slices.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/language/slices.mdx
new file mode 100644
index 0000000000..82dc7f4c18
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/language/slices.mdx
@@ -0,0 +1,45 @@
+---
+page_title: Sentinel Language - Slices
+sidebar_current: docs-language-slices
+description: >-
+ Slices are an efficient way to create a substring or sublist from an existing
+ string or list, respectively.
+layout: docs
+---
+
+# Language: Slices
+
+Slices are an efficient way to create a substring or sublist from an
+existing string or list, respectively.
+
+The syntax for creating a slice is:
+
+```sentinel
+a[low : high]
+```
+
+`low` is the lowest index to slice from. The resulting slice includes
+the value at `low`. `high` is the index to slice to. The resulting slice
+will not include the value at `high`. The length of the resulting list
+or string is always `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+
+a = "hello"
+b = a[1:4] // "ell"
+```
+
+## Convenience Shorthands
+
+Some convenience shorthands are available by omitting the value for either the
+low or high index in the slice expression: omitting `low` implies a low index of
+0, and omitting `high` implies a high index of the length of the list.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+
+b = a[:2] // [1, 2] (same as a[0:2])
+b = a[2:] // [3, 4, 5] (same as a[2:length(a)])
+```
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/language/spec.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/language/spec.mdx
new file mode 100644
index 0000000000..d8efb378cd
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/language/spec.mdx
@@ -0,0 +1,1333 @@
+---
+page_title: Sentinel Language Specification
+sidebar_current: docs-language-spec
+description: This is the specification for the Sentinel policy language.
+layout: docs
+---
+
+# Sentinel Language Specification
+
+This is the specification for the Sentinel policy language.
+
+The Sentinel language is designed with policy enforcement in mind. It is dynamically typed and garbage collected and has explicit support for _rule_ construction representing boolean logic.
+
+The language is designed to be easy to learn and use by non-programmers. It is expected to be embedded within applications.
+
+## Table of Contents
+
+
+
+
+- [Source code representation](#source-code-representation)
+- [Declarations and Scope](#declarations-and-scope)
+- [Blocks](#blocks)
+- [Lexical Elements](#lexical-elements)
+ - [Comments](#comments)
+ - [Identifiers](#identifiers)
+ - [Keywords](#keywords)
+ - [Operators and Delimiters](#operators-and-delimiters)
+ - [Integer Literals](#integer-literals)
+ - [Floating-point Literals](#floating-point-literals)
+ - [String Literals](#string-literals)
+ - [Implicit Line Joining](#implicit-line-joining)
+ - [Whitespace](#whitespace)
+ - [Semicolons](#semicolons)
+- [Variables](#variables)
+- [Undefined](#undefined)
+- [Expressions](#expressions)
+ - [Operand](#operand)
+ - [Primary Expressions](#primary-expressions)
+ - [Null](#null)
+ - [Booleans](#booleans)
+ - [Boolean Literals](#boolean-literals)
+ - [Boolean Expressions](#boolean-expressions)
+ - [List Literals](#list-literals)
+ - [Map Literals](#map-literals)
+ - [Function Literals](#function-literals)
+ - [Rule Expressions](#rule-expressions)
+ - [Index Expressions](#index-expressions)
+ - [Selectors](#selectors)
+ - [Slice Expressions](#slice-expressions)
+ - [Calls](#calls)
+ - [Operators](#operators)
+ - [Operator Precedence](#operator-precedence)
+ - [Arithmetic Operators](#arithmetic-operators)
+ - [Integer operators](#integer-operators)
+ - [Integer overflow](#integer-overflow)
+ - [Floating-point operators](#floating-point-operators)
+ - [String Concatenation](#string-concatenation)
+ - [List Concatenation](#list-concatenation)
+ - [Comparison Operators](#comparison-operators)
+ - [Logical Operators](#logical-operators)
+ - [Set Operators](#set-operators)
+ - [Matches Operator](#matches-operator)
+ - [Else Operator](#else-operator)
+ - [Quantifier Expressions (any, all, filter, map)](#quantifier-expressions-any-all-filter-map)
+- [Statements](#statements)
+ - [Expression Statements](#expression-statements)
+ - [Assignments](#assignments)
+ - [If Statements](#if-statements)
+ - [Case Statements](#case-statements)
+ - [For Statements](#for-statements)
+ - [Break Statements](#break-statements)
+ - [Continue Statements](#continue-statements)
+ - [Return Statements](#return-statements)
+- [Imports](#imports)
+- [Parameters](#parameters)
+- [Built-in Functions](#built-in-functions)
+ - [Length](#length)
+ - [Collections](#collections)
+ - [List Append](#list-append)
+ - [Map Delete](#map-delete)
+ - [Keys and Values](#keys-and-values)
+ - [Range](#range)
+ - [Type Conversion](#type-conversion)
+ - [Printing](#printing)
+ - [Errors](#errors)
+- [Program Execution](#program-execution)
+
+
+
+## Source code representation
+
+Source code is Unicode text encoded in UTF-8. A "character" referenced in this document refers to a Unicode code point. Each code point is distinct: upper and lower case letters are different characters.
+
+The underscore character \_ (U+005F) is considered a "letter".
+
+```ebnf
+letter = unicode_letter | "_" .
+decimal_digit = "0" … "9" .
+octal_digit = "0" … "7" .
+hex_digit = "0" … "9" | "A" … "F" | "a" … "f" .
+```
+
+## Declarations and Scope
+
+A declaration binds an identifier to a value. An identifier is declared at the point it is first assigned. An assignment is considered a first assignment if the identifier hasn't already been previously declared in the current scope or any parent scopes.
+
+The scope of an identifier is the extent of source text in which the identifier denotes the specified value. Sentinel is lexically scoped using blocks.
+
+An identifier declared in a block may be redeclared in an inner block. While the identifier of the inner declaration is in scope, it denotes the value assigned in the inner declaration.
+
+## Blocks
+
+A block is a possibly empty sequence of statements within matching brace brackets.
+
+```ebnf
+Block = "{" StatementList "}" .
+StatementList = { Statement ";" } .
+```
+
+Blocks nest and affect scoping.
+
+In addition to explicit blocks in the source code, there are implicit blocks:
+
+1. The universe block encompasses all source text.
+2. Each program has a program block containing all source text for that program.
+3. Each "any", "all", and "for" statements is considered to be in its own implicit block. "if" does not create an implicit block.
+
+## Lexical Elements
+
+### Comments
+
+Comments are sections of source text used for documentation.
+
+```plaintext
+# Single line comment
+// Single line comment
+/* multi
+line
+ comment */
+```
+
+Three forms of comments are supported: two single-line forms and one multi-line form. A single-line comment begins with the token `//` or `#`. Everything between the starting token and the end of the line is ignored.
+
+A multi-line comment begins with the token `/*` and ends with the token `*/`. Everything between `/*` and `*/` is ignored.
+
+A comment may not start inside a string literal or inside another comment.
+
+A multi-line comment containing no newlines acts like a space.
+
+### Identifiers
+
+Identifiers name program entities such as rules, variables, and functions. An identifier is a sequence of one or more letters and digits. The first character in an identifier must be a letter.
+
+```ebnf
+identifier = letter { letter | unicode_digit } .
+```
+
+```sentinel
+a
+_a
+_A
+αβ
+```
+
+#### Pre-Declared Identifiers
+
+The following identifiers are implicitly declared in the [universe block](#blocks):
+
+```plaintext
+Constants:
+ false null true undefined
+
+Functions:
+ append bool delete error float int keys length print range string values
+```
+
+### Keywords
+
+The following keywords are reserved and may not be used as identifiers:
+
+```plaintext
+all
+any
+as
+break
+case
+continue
+default
+else
+empty
+filter
+for
+func
+if
+import
+map
+param
+return
+rule
+when
+```
+
+### Operators and Delimiters
+
+The following character sequences represent operators, delimiters, and other special tokens:
+
+```plaintext
++
+-
+*
+/
+%
++=
+-=
+*=
+/=
+%=
+==
+<
+>
+=
+!
+!=
+<=
+>=
+( )
+[ ]
+{ }
+,
+.
+:
+;
+and
+contains
+else
+in
+is
+matches
+not
+or
+xor
+```
+
+As a special case, `else` is both a keyword and operator, depending on the context. See [Else Operator](#else-operator) for more details.
+
+### Integer Literals
+
+An integer literal is a sequence of digits representing an integer constant. An optional prefix sets a non-decimal base: `0` for octal, `0x` or `0X` for hexadecimal. In hexadecimal literals, letters `a-f` and `A-F` represents values 10 through 15.
+
+Integers are signed 64-bit values (-9223372036854775808 to 9223372036854775807).
+
+```ebnf
+int_lit = decimal_lit | octal_lit | hex_lit .
+decimal_lit = ( "1" … "9" ) { decimal_digit } .
+octal_lit = "0" { octal_digit } .
+hex_lit = "0" ( "x" | "X" ) hex_digit { hex_digit } .
+```
+
+```
+42
+0600
+0xBadFace
+170141183460469231731687303715884105727
+```
+
+### Floating-point Literals
+
+A floating-point literal is a decimal representation of a floating-point constant. It has an integer part, a decimal point, a fractional part, and an exponent part. The integer and fractional part comprise decimal digits; the exponent part is an e or E followed by an optionally signed decimal exponent. One of the integer part or the fractional part may be elided; one of the decimal point or the exponent may be elided.
+
+Floating-point numbers are IEEE-754 64-bit floating numbers.
+
+```ebnf
+float_lit = decimals "." [ decimals ] [ exponent ] |
+ decimals exponent |
+ "." decimals [ exponent ] .
+decimals = decimal_digit { decimal_digit } .
+exponent = ( "e" | "E" ) [ "+" | "-" ] decimals .
+```
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+### String Literals
+
+String literals are character sequences between double quotes, as in "bar". Within the quotes, any character may appear except newline and unescaped double quote. The text between the quotes forms the value of the literal.
+
+A multi-character sequence beginning with a backslash encode values in various formats.
+
+Backslash escapes allow arbitrary values to be encoded as ASCII text. There are four ways to represent the integer value as a numeric constant: `\x` followed by exactly two hexadecimal digits; `\u` followed by exactly four hexadecimal digits; `\U` followed by exactly eight hexadecimal digits, and a plain backslash `\` followed by exactly three octal digits. In each case the value of the literal is the value represented by the digits in the corresponding base.
+
+After a backslash, certain single-character escapes represent special values:
+
+```plaintext
+\a U+0007 alert or bell
+\b U+0008 backspace
+\f U+000C form feed
+\n U+000A line feed or newline
+\r U+000D carriage return
+\t U+0009 horizontal tab
+\v U+000b vertical tab
+\\ U+005c backslash
+\" U+0022 double quote
+```
+
+The three-digit octal (\nnn) and two-digit hexadecimal (\xnn) escapes represent individual bytes of the resulting string; all other escapes represent the (possibly multi-byte) UTF-8 encoding of individual characters. Thus inside a string literal \377 and \xFF represent a single byte of value 0xFF=255, while ÿ, \u00FF, \U000000FF and \xc3\xbf represent the two bytes 0xc3 0xbf of the UTF-8 encoding of character U+00FF.
+
+A string value is a (possibly empty) sequence of bytes. Strings are immutable: once created, it is impossible to change the contents of a string.
+
+The length of a string s (its size in bytes) can be discovered using the built-in function `length`. An individual character (of type string) can be accessed by integer indices 0 through `length(s)-1`.
+
+```ebnf
+string_lit = `"` { unicode_value | byte_value } `"` .
+unicode_value = unicode_char | little_u_value | big_u_value | escaped_char .
+byte_value = octal_byte_value | hex_byte_value .
+octal_byte_value = `\` octal_digit octal_digit octal_digit .
+hex_byte_value = `\` "x" hex_digit hex_digit .
+little_u_value = `\` "u" hex_digit hex_digit hex_digit hex_digit .
+big_u_value = `\` "U" hex_digit hex_digit hex_digit hex_digit
+ hex_digit hex_digit hex_digit hex_digit .
+escaped_char = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | `"` ) .
+```
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+### Implicit Line Joining
+
+Expressions can be split over more than one line. For example:
+
+```sentinel
+a or
+b or # This is a comment
+c
+```
+
+Implicitly continued lines can have trailing comments. Blank continued lines are allowed.
+
+### Whitespace
+
+Whitespace is needed to separate tokens, but no distinction is made between the number and combination of whitespace characters. Whitespace characters can be space (U+0020), horizontal tabs (U+0009), carriage returns (U+000D), and newlines (U+000A).
+
+```sentinel
+a or b
+
+a or b
+
+a or
+ b
+
+rule { a or b }
+
+rule {
+ a or b }
+
+rule {
+ a or
+ b
+}
+```
+
+### Semicolons
+
+The formal grammar uses semicolons ";" as terminators in a number of productions.
+It is idiomatic Sentinel source to omit semicolons in most cases.
+
+A semicolon is automatically inserted into the token stream immediately after
+a line's final token if that token is:
+
+- An identifier
+- An integer, float, or string literal
+- The keyword `break`, `continue`, or `return`
+- The delimiter `)`, `]`, or `}`
+
+## Variables
+
+A variable is a storage location for holding a value.
+
+A variable has a dynamic type, which is the concrete type of the value assigned to the variable at run time. The dynamic type may vary during execution and change as a result of further assignments.
+
+A variable's value is retrieved by referring to the variable in an expression; it is the most recent value assigned to the variable. If a variable has not yet been declared (initially assigned), it is an error.
+
+```sentinel
+x = 7 // x is type int
+x = "foo" // x is now type string
+
+x = y // error if y is not previously assigned
+```
+
+## Undefined
+
+The value denoted by the keyword `undefined` represents undefined behavior or values. It can be created directly using the keyword `undefined`. It is also returned as a result of expressions in specified cases.
+
+`undefined` is a valid operand for any operations. Only `undefined or true` will result in true. All other operations result in `undefined`. An exception is if `undefined` is not reached as a result of short-circuit operations.
+
+```sentinel
+undefined OR true = true
+undefined OR false = undefined
+undefined OR undefined = undefined
+undefined AND true = undefined
+undefined AND false = undefined
+undefined AND undefined = undefined
+undefined XOR true = undefined
+undefined XOR false = undefined
+undefined XOR undefined = undefined
+
+// Short-circuit examples
+false OR true OR undefined = true
+false OR undefined OR true = true
+true AND false AND undefined = false
+true AND undefined AND false = undefined
+
+// Non-logical operators
+undefined + 5 = undefined
+-undefined = undefined
+!undefined = undefined
+```
+
+If the result of the main rule is `undefined`, it is treated as `false` but is indicative of erroneous logic.
+
+## Expressions
+
+### Operand
+
+Operands denote the elementary values in an expression. An operand may be a literal, an identifier denoting a variable, rule, or function, or a parenthesized expression.
+
+```ebnf
+Operand = Literal | OperandName | MethodExpr | "(" Expression ")" .
+Literal = BasicLit | MapLit | ListLit | FunctionLit | RuleLit .
+BasicLit = int_lit | float_lit | string_lit .
+OperandName = identifier .
+```
+
+### Primary Expressions
+
+Primary expressions are the operands for unary and binary expressions.
+
+```ebnf
+PrimaryExpr =
+ Operand |
+ PrimaryExpr Selector |
+ PrimaryExpr Index |
+ PrimaryExpr Slice |
+ PrimaryExpr Arguments .
+
+Selector = "." identifier .
+Index = "[" Expression "]" .
+Slice = "[" [ Expression ] ":" [ Expression ] "]" .
+Arguments = "(" [ Expression { "," Expression } ] ")" .
+```
+
+```sentinel
+x
+2
+(s + ".txt")
+f(3.1415, true)
+m["foo"]
+s[i : j + 1]
+obj.color
+f.p[i].x()
+x is empty
+x is not empty
+```
+
+### Null
+
+The reserved word `null` denote the singleton value `null`. Null represents the explicit absence of a value. Behavior of `null` within expressions is specified explicitly for each expression.
+
+### Booleans
+
+### Boolean Literals
+
+The reserved words `true` and `false` denote objects that represent the boolean values `true` and `false`, respectively. These are boolean literals.
+
+```ebnf
+BoolLit = "true" | "false" .
+```
+
+#### Boolean Expressions
+
+Boolean expressions are expressions that must result in a boolean value. Any other value type becomes the `undefined` value.
+
+```ebnf
+BoolExpr = Expression .
+```
+
+### List Literals
+
+A list literal denotes a list, which is an integer indexed collection of values.
+
+```ebnf
+ListLit = "[" [ ElementList [ "," ] ] "]" .
+ElementList = Element { "," Element } .
+Element = Expression | LiteralValue .
+```
+
+A list may contain zero or more values. The number of values in a list is its length.
+
+A list has a set of indices. An empty list has an empty set of indices. A non-empty list has the index set `{0...n - 1}` where `n` is the length of the list. Attempting to access a list using an index that is not a member of its set of indices results in the `undefined` value.
+
+### Map Literals
+
+A map literal denotes a map, which is an unordered group of elements indexed by a set of unique keys.
+
+```ebnf
+MapLit = "{" [ KeyedElementList [ "," ] ] "}" .
+KeyedElementList = KeyedElement { "," KeyedElement } .
+KeyedElement = Element ":" Element .
+```
+
+Keys can only be a boolean, numeric, or string type.
+
+The value of a non-existent key is the `undefined` value.
+
+### Function Literals
+
+A function literal represents a function.
+
+```ebnf
+FunctionLit = "func" Function .
+Function = Parameters FunctionBody .
+FunctionBody = Block .
+Parameters = "(" [ IdentifierList [ "," ] ] ")" .
+IdentifierList = identifier { "," identifier } .
+```
+
+```sentinel
+func(a, b) { return b }
+```
+
+Function literals are only allowed in the file scope. They may refer to variables defined in surrounding blocks.
+
+A function must terminate with a return statement. If a function has no meaningful return value, it should return `undefined`.
+
+### Rule Expressions
+
+A rule is an expression that is evaluated lazily and the result is memoized.
+
+If the optional "when" predicate is present, the rule is evaluated only when
+the "when" boolean expression results in true. If the predicate is false,
+the rule is not evaluated and returns true. The predicate is evaluated when
+the rule would be evaluated; it is also lazy and memoized in the same way.
+
+```ebnf
+RuleExpr = "rule" [ "when" BoolExpr ] "{" Expr "}" .
+```
+
+```sentinel
+rule { x is y }
+
+rule {
+ x == "value" or
+ y == "other"
+}
+
+rule when x is y { y > 42 }
+
+rule { map ["a", "b", "c"] as _, id {
+ { "id": id }
+}}
+```
+
+### Index Expressions
+
+A primary expression of the form `a[x]` denotes the element of a list or map indexed by x. The value x is called the index or key.
+
+For `a` of type map:
+
+- If `a` contains a key `x`, `a[x]` is the map value with key `x`.
+- If `a` does not contain key `x`, `a[x]` is the `undefined` value.
+
+For `a` of type list:
+
+- `x` must be an integer
+- `x` must be in the range `[-1 * length(a), length(a)-1]`
+- If `x` is contained in the set of indices of `a`, `a[x]` is the list element at index `x`. If `x` is negative, `a[x]` is equivalent to `a[length(a)+x]`.
+- If `x` is not contained in the set of indices of `a`, `a[x]` is the `undefined` value.
+
+For `a` of value `null`:
+
+- `a[x]` is the `undefined` value.
+
+Otherwise `a[x]` is an error.
+
+### Selectors
+
+For a primary expression x, the selector expression `x.f` denotes the field `f` of the value `x`. The identifier `f` is called the selector. The type of the selector expression is the type of the selector.
+
+As a special case, selectors can be [reserved words](#keywords) and keyword [operators](#operators-and-delimiters), but cannot be any other non-identifier element.
+
+Selectors are used to access data from [imports](#imports). The first primary expression `x` in `x.f` denotes the import name. The field `f` is the selector to access data from the import.
+
+Selectors may also be used to access map data with static keys. They are syntactic sugar over index expressions. `math.pi` is equivalent to `math["pi"]` and exhibit the same limitations and behavior as an index expression. Selectors cannot, however, be used to assign values to map data.
+
+Selectors on undefined result in undefined.
+
+```sentinel
+math.pi
+time.pst.hour
+```
+
+### Slice Expressions
+
+Slice expressions construct a substring or list from a string or list.
+
+The primary expression
+
+```sentinel
+a[low : high]
+```
+
+constructs a substring or list. The indices `low` and `high` select which elements of operand `a` appear in the result. The result has indices starting at 0 and length equal to `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+```
+
+For convenience, any of the indices may be omitted. A missing `low` index defaults to zero; a missing `high` index defaults to the length of the sliced operand:
+
+```sentinel
+a[2:] // same as a[2 : length(a)]
+a[:3] // same as a[0 : 3]
+a[:] // same as a[0 : length(a)]
+```
+
+The indices are in range if `0 <= low <= high <= length(a)`, otherwise they are out of range. If the indices are out of range at run time, the result is the `undefined` value.
+
+If `a` is the value `null`, the result is the `undefined` value.
+
+If `a` is any other value type, it is an error.
+
+### Calls
+
+```ebnf
+CallExpr = identifier Arguments .
+```
+
+Given an expression `f` where `f` is a function value:
+
+```sentinel
+f(a1, a2, … an)
+```
+
+calls `f` with arguments `a1, a2, ... an`. The type of the expression is the result of `f`. The arguments are evaluated left to right. Arguments are passed by value.
+
+### Operators
+
+```ebnf
+Expression = UnaryExpr | Expression binary_op Expression .
+UnaryExpr = PrimaryExpr | unary_op UnaryExpr .
+
+binary_op = logical_op | set_op | rel_op | add_op | mul_op | else_op .
+logical_op = "and" | "or" | "xor" .
+set_op = ["not"] ( "contains" | "in" ).
+rel_op = "==" | "!=" | "<" | "<=" | ">" | ">=" |
+ "is" | "is not" | "matches" | "not matches" .
+add_op = "+" | "-" .
+mul_op = "*" | "/" | "%" .
+else_op = "else" .
+unary_op = "+" | "-" | "!" | "not" | empty_op | defined_op .
+empty_op = "is empty" | "is not empty" .
+defined_op = "is defined" | "is not defined" .
+```
+
+#### Operator Precedence
+
+Unary operators have the highest precedence.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Binary operators of the same precedence associate from left to right. For instance, x / y _ z is the same as (x / y) _ z.
+
+### Arithmetic Operators
+
+Arithmetic operators apply to numeric values and yield a result of the same type when both operands are the same.
+
+Arithmetic operations between integer and floating-point types result in a floating-point value with the integer operand treated as a floating-point value for purposes of calculation.
+
+Arithmetic operations between numeric values (integer, floating-point) and non-numeric values (strings, lists) are not permitted.
+
+All five supported arithmetic operators (+, -, \*, /, %) apply to both integer and floating-point types; + also applies to strings.
+
+```plaintext
++ sum integers, floats, strings, lists
+- difference integers, floats
+* product integers, floats
+/ quotient integers, floats
+% remainder integers, floats
+```
+
+#### Integer operators
+
+For two integer values x and y, the integer quotient `q = x / y` and remainder `r = x % y` satisfy the following relationships:
+
+```sentinel
+x = q*y + r and |r| < |y|
+```
+
+with x / y truncated towards zero.
+
+```plaintext
+ x y x / y x % y
+ 5 3 1 2
+-5 3 -1 -2
+ 5 -3 -1 2
+-5 -3 1 -2
+```
+
+As an exception to this rule, if the dividend x is the most negative value for the int type of x (-9223372036854775808), the quotient `q = x / -1` is equal to x (and r = 0).
+
+If the divisor is a constant, it must not be zero. If the divisor is zero at run time, an error occurs.
+
+#### Integer overflow
+
+For signed integers, the operations `+`, `-`, and `*` may legally overflow and the resulting value exists and is deterministically defined by the signed integer representation, the operation, and its operands. No exception is raised as a result of overflow.
+
+#### Floating-point operators
+
+For floating-point numbers, +x is the same as x, while -x is the negation of x. The result of a floating-point division by zero is not specified beyond the IEEE-754 standard.
+
+#### String Concatenation
+
+Strings can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = "hi"
+y = "hello"
+x = x + ", " + y // "hi, hello"
+x += " and good bye" // "hi, hello and good bye"
+```
+
+String addition creates a new string by concatenating the operands.
+
+#### List Concatenation
+
+Lists can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = [1, 2]
+x = x + [2, 3] // [1, 2, 2, 3]
+x += [4] // [1, 2, 2, 3, 4]
+```
+
+List addition creates a new list by concatenating the operands.
+
+### Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+In any comparison, the two operands must be equivalent types. The only exception are integers and floats, which are considered numeric and may be compared. Any comparison between other non-matching types results in the `undefined` value.
+
+The equality operators `==`, `!=`, `is`, and `is not` apply to operands that are comparable. The ordering operators `<`, `<=`, `>`, and `>=` apply to operands that are ordered. The behavior of `is` with `==` and `is not` with `!=` is identical. These terms and the result of the comparisons are defined as follows:
+
+- Boolean values are comparable. Two boolean values are equal if they are either both true or both false.
+- Integer values are comparable and ordered, in the usual way.
+- Floating point values are comparable and ordered, as defined by the IEEE-754 standard.
+- An integer compared with floating point value treats the integer as the converted floating point value.
+- String values are comparable and ordered, lexically byte-wise.
+- Lists are comparable. Lists are equal if they are of equal length and their
+ corresponding elements are comparable and equal.
+- Maps are comparable. Maps are equal if they are of equal length and both their
+ corresponding keys and values are comparable and equal.
+
+If either operand is the `undefined` value, the result of the expression is the `undefined` value.
+
+### Emptiness Comparisons
+
+Checking the emptiness of an object can be achieved by using one of the emptiness expressions, `is empty` or `is not empty`. Although similar to [Comparison Operators](#comparison-operators) in that they yield a boolean value and read as though a comparison is taking place, both `is empty` and `is not empty` are evaluated as a multi-word expression.
+
+An emptiness comparison can only be performed against collections, strings or `undefined`, with the same rules as the [built-in length](/sentinel/functions/length) function.
+
+```sentinel
+"" is empty // true
+"foo" is empty // false
+[] is empty // true
+[1] is empty // false
+{} is empty // true
+{"a": "b"} is empty // false
+
+"" is not empty // false
+"foo" is not empty // true
+[] is not empty // false
+[1] is not empty // true
+{} is not empty // false
+{"a": "b"} is not empty // true
+
+undefined is empty // undefined
+undefined is not empty // undefined
+```
+
+### Logical Operators
+
+Logical operators apply to boolean values and yield a boolean result.
+
+Logical operators are evaluated left-to-right and perform short-circuit logic. The right-hand side is not guaranteed to be evaluated if a short-circuit can be performed.
+
+```plaintext
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+### Set Operators
+
+The set operators `contains` and `in` test for set inclusion for lists
+and maps, and substring inclusion for strings.
+
+Set operators may be negated by prefixing the operator with `not`: `not contains` and `not in`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+`contains` tests if the left-hand collection contains the right-hand value. For lists, this tests equality of any value. For maps, this tests equality of any key. For strings, this tests for substring existence.
+
+`in` tests if the right-hand collection contains the left-hand value. The behavior is equivalent to `contains`.
+
+The collection must be a list, map, or string. If it is the `undefined` value, the result is the `undefined` value. For any other value, the result is an error.
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+
+"test" contains "est" // true
+"test" contains "best" // false
+"test" in "testing" // true
+"best" in "testing" // false
+```
+
+### Matches Operator
+
+The matches operator tests if a string matches a regular expression.
+
+The matches operators may be negated by prefixing the operator with `not`: `not matches`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+The left-hand value is the string to test. The right-hand value is a string value representing a regular expression.
+
+The syntax of the regular expression is the same general syntax used by Python, Ruby, and other languages. More precisely, it is the syntax accepted by RE2. The regular expression is not anchored by default; any anchoring must be explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+If either operand is `undefined`, the result is the `undefined` value. For any other non-string value, the result is an error.
+
+### Else Operator
+
+Else operators can be used to provide a default value for an expression that may evaluate to the `undefined` value. Else operators return their left-hand value unless it is `undefined`, otherwise they return their right-hand value.
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else null
+```
+
+### Quantifier Expressions (any, all, filter, map)
+
+Quantifiers are expressions that apply a predicate to a collection (list or map). The purpose of the quantifier is to produce a result based on its particular type.
+
+`any` and `all` expressions are existential and universal quantifiers, respectively. `any` expressions are equivalent to a chain of `or` and `all` expressions are equivalent to a chain of `and`. Both expressions implement short-circuiting equivalent to logical operators.
+
+The `map` expression is an apply-to-all quantifier and returns a new list for all values in the input collection. The output list will be the same length as the input collection.
+
+The `filter` expression is a subset quantifier and returns the subset of all values in the input collection for which the supplied predicate applies. This will be zero or more elements, up to the length of the input.
+
+The body of a quantifier expression is a boolean expression.
+
+`any` returns the boolean value `true` if any value in the collection expression results in the body expression evaluating to `true`. If the body expression evaluates to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression result in the body expression evaluating to `true`. If any value in the collection expression result in the body expression evaluating to `false`, the all expression returns `false`.
+
+For empty collections, `any` returns false and `all` returns `true`.
+
+`map` always returns a list, regardless of if the input collection is a list itself; maps (as in the data type) also return lists when processed through `map`. Each element is the result of the supplied expression body.
+
+`filter` returns a list or map, the same type as its input collection. Only the elements for which the expression body evaluated to `true` will be returned. If an iteration of the expression body results in `undefined`, the entire result set is `undefined`.
+
+When the collection is a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When the collection is a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+QuantExpr = QuantOp Expression "as" [ identifier "," ] identifier "{" BoolExpr "}" .
+QuantOp = "all" | "any" | "filter" | "map" .
+```
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" } // true if every iterations is true
+any group.tasks as t { t.driver is "vmware" } // true if any iteration is true
+map group.tasks as t { { "driver": t.driver } } // [{"driver": "vmware"}, ...]
+filter group.tasks as t { t.driver is "vmware" } // subset of tasks that is true
+```
+
+## Statements
+
+### Expression Statements
+
+Function call expressions may exist in the statement context.
+
+```ebnf
+ExpressionStmt = Expression .
+```
+
+### Assignments
+
+Assignments set the value of an expression to the value of another expression.
+
+```ebnf
+Assignment = AssignExpr assign_op Expression .
+AssignExpr = identifier | IndexExpr .
+assign_op = [ add_op | mul_op ] "=" .
+```
+
+The assignment `x op= y` is equivalent to `x = x op (y)` for supported values of `op`.
+
+```sentinel
+a = 1
+b[12] = 42
+c["key"] = "value"
+
+a *= 12
+b[12] += 8
+```
+
+Assignments to lists and maps can be carried out using [index expressions](#index-expressions), with the operands of the index expression being evaluated alongside the right hand side expression in a right-to-left (RHS, LHS) order.
+
+In addition to the rules detailed in the section describing their use, the following rules apply when assigning to maps or lists using index expressions:
+
+- The [variable](#variables) must exist and be a list or map. Attempts to use an index assignment on an unknown variable, or a variable that not a list or map, results in a runtime error.
+- Assigning to a list or map index that already exists overwrites that index's data with evaluated right hand side's value.
+- Assigning to an unknown key in a map creates a key for that map with the evaluated right hand side's value assigned to that key.
+- Attempting to assign a value to a list index that is out of range results in a runtime error.
+
+### If Statements
+
+If statements specify the conditional execution of two branches according to the value of a boolean expression. If the expression evaluates to true, the "if" branch is executed, otherwise, if present, the "else" branch is executed.
+
+```ebnf
+IfStmt = "if" BoolExpr Block [ "else" ( IfStmt | Block ) ] .
+```
+
+```sentinel
+if x < y {
+ return x
+} else if x > z {
+ return z
+} else {
+ return y
+}
+```
+
+### Case Statements
+
+Case statements specify a selection control mechanism to conditionally execute a branch based on expression equality.
+
+Each clause that wishes to provide an expression must use the "when" keyword. Multiple expressions can be provided, separated with a comma (",").
+
+There can be one, optional, "else" clause within a `case` statement. The "else" clause is executed if all other clauses failed to run.
+
+The clauses are evaluated in the order they are listed, with the first successful evaluation being executed.
+
+A `case` statement can optionally provide an expression. If no expression is provided, it is the equivalent of writing `case true {`.
+
+```ebnf
+CaseStmt = "case" [ Expression ] "{" { CaseWhenClause } "}" .
+CaseWhenClause = CaseWhenCase ":" StatementList .
+CaseWhenCase = "when" Expression { "," Expression } | "else" .
+```
+
+```sentinel
+case x {
+when y, z:
+ return true
+else:
+ return false
+}
+
+case {
+when x > 42:
+ return true
+else:
+ return false
+}
+```
+
+### For Statements
+
+A `for` statement specifies repeated execution of a block.
+
+The expression must evaluate to a list or a map.
+
+When iterating over a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When iterating over a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+ForStmt = "for" Expressions "as" [ identifier "," ] identifier Block .
+```
+
+```sentinel
+for [1, 2, 3] as v { count += v } // basic sum
+
+for [1, 2, 3] as idx, v {
+ if idx > 1 { count += v }
+}
+
+data = { "a": 12, "b": 32 }
+for data as k { count += data[k] } // sum map values
+for data as k, v { count += v }
+```
+
+### Break Statements
+
+A `break` statement terminates execution of the innermost `for` statement
+within the same function.
+
+```ebnf
+BreakStmt = "break" .
+```
+
+```sentinel
+// Will only print "1"
+for [1, 2, 3] as v {
+ print(v)
+ break
+}
+```
+
+### Continue Statements
+
+A `continue` statement begins the next iteration of the innermost `for` loop.
+The `for` loop must be within the same function.
+
+```ebnf
+ContinueStmt = "continue" .
+```
+
+```sentinel
+// Will print 1, 3
+for [1, 2, 3] as v {
+ if v == 2 {
+ continue
+ }
+
+ print(v)
+ break
+}
+```
+
+### Return Statements
+
+A return statement in a function F terminates the execution of F and provides the result value.
+
+```ebnf
+ReturnStmt = "return" Expression .
+```
+
+A function must terminate with a return statement.
+
+The return statement can be invoked at any time during a function to terminate execution.
+
+## Imports
+
+An import adds an assignment to the global scope to external definitions.
+
+The import declarations must only appear at the top of the source file, before any other statements. Comments may appear before imports.
+
+```ebnf
+ImportDecl = "import" Name ["as" identifier] .
+Name = string_lit .
+```
+
+An import name and identifier must be distinct. Two names may not repeated even if they're declared with different identifiers.
+
+If an identifier is not specified, the identifier is the name of the import itself.
+
+An import is not a valid value. The identifier representing the import may not be used as an expression, such as in assignment statements or call expressions.
+
+Values in imports are not assignable directly via their selectors. Function calls may be used to alter the state of the external data represented by the import. Whether or not this is supported is specific to the import implementation. The exception to this assignment restriction is the memoized data returned by a call to an import (see below).
+
+Data returned by imports can be memoized, meaning that data is returned by the import in the form of a map which is then used as much as possible without having to return to the import for more data. For example, `t = time.now` returns a map so that `t.hour` will be able to be looked up without having to go back to the import for that data.
+
+Data returned by imports may be callable, meaning that methods may be called on the memoized data returned by an import. For example, given `t = time.now`, `t.after(some_previous_time)` is valid, with the receiver data being set to the local memoized data stored in `t`. It is a valid case to accept modifications to the receiver data (example: assignment to the memoized data via index expressions), as long as all data in the receiver continues to be string-keyed. It is an invalid case to accept receiver data with non-string-typed keys. Methods may also alter the contents of receiver data so that it is different after the call is finished.
+
+As with methods, imports may also have callable keys where the data may not be memoized. Example: in the event of `v = foo.bar`, if `v.baz` is not included in the memoized data, a call will be made to the import to fetch it. The same rules and restrictions to receiver data apply to callable keys as do to method calls.
+
+The support for callable methods and non-memoized keys on data returned by imports is specific to the import implementation.
+
+The handling of import loading is specific to the runtime implementation.
+
+Implicit imports may be added by the runtime, for example for standard library definitions. An explicit import of the same name should override a matching implicit import. For example, if "time" is an implicit import and the program specifies `import "time" as cal`, then only `cal` is defined.
+
+```sentinel
+import "time"
+import "math" as m
+```
+
+## Parameters
+
+```ebnf
+ParamDecl = "param" identifier [ "default" Expression ]
+```
+
+A parameter is a way for a policy to describe variables that are expected to be supplied by the calling application, in addition to any default value.
+
+Parameter declarations must appear at the top of the source file, following imports.
+
+Like imports, comments may appear before parameters; this is mainly pertinent when the policy is not importing anything, which would make parameters the first declarations that appear in a policy.
+
+A parameter declaration describes a valid variable that is added to the scope of a policy at runtime. This variable has the same properties and rules as other variables assigned during the course of policy evaluation as defined by the specification. This includes having a dynamic type and being able to be re-assigned during execution.
+
+Parameters may only be strings, integers, floating point numbers, booleans, lists, or maps. More complex types such as rules or functions are not allowed.
+
+A parameter can specify a default value by adding one in the declaration via use of the "default" keyword, and supplying a literal for the default value. The literal for a default is a very limited expression, comprising of:
+
+- For strings, a simple string literal;
+- For integers and floating-point numbers, either a literal denoting the value, or a unary expression with the literal, and the operators - or +;
+- For booleans, the pre-declared identifiers true or false;
+- For lists and maps, their respective literals composed of values and keys (for maps) of the above values only.
+
+Any other type of expression or identifier is not allowed.
+
+A parameter that does not have a default value defined is required by the policy. Evaluating a policy with a required parameter that has not been supplied at execution results in a runtime error.
+
+Parameters must not conflict with imports or any other identifiers currently defined in the global scope, including any reserved or pre-declared identifiers. Attempting to assign a parameter to an existing value results in a runtime error.
+
+```sentinel
+import "time"
+
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+param time default "11:00" // error, conflicts with "time" import
+param undefined // error, reserved identifier
+```
+
+## Built-in Functions
+
+Built-in functions are predeclared. They are called like any other function.
+
+### Length
+
+The built-in function `length` returns the length of a collection of string.
+
+The length of a string is the number of bytes in the string. It is not necessarilly the number of characters.
+
+The length of a collection is the number of elements in that collection.
+
+The length of `undefined` is `undefined`.
+
+### Collections
+
+#### List Append
+
+The built-in function `append` appends a value to the end of a list in-place.
+
+Appending to a non-list value results in an immediate `fail`.
+
+The return value of `append` is always the `undefined` value.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+#### Map Delete
+
+The built-in function `delete` deletes elements from a map by key. The map is modified in-place.
+
+Deleting a key that does not exist does nothing.
+
+Calling delete for a non-map value results in an immediate `fail`.
+
+The return value of `delete` is always the `undefined` value.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
+
+#### Keys and Values
+
+The built-in function `keys` and `values` return the keys and values of a map, respectively. The return value is a list. Both functions return values in an unspecified order.
+
+The `keys` or `values` of `undefined` is `undefined`.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+### Range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it defaults to `1`.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4]
+range(1, 5, 2) // [1,3]
+range(0, -3, -1) // [0,-1,-2]
+```
+
+### Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a value of that type according to the rules below.
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
+
+### Printing
+
+The built-in function `print` can be used to output formatted debug information. The runtime may omit print function calls depending on its execution mode. The runtime may choose where the output of a print function goes.
+
+`print` is a variadic function, accepting one or more values to be printed; values are separated by a single whitespace character (`" "`).
+
+The return value of `print` is always `true` to allow it to be used in boolean expressions.
+
+```sentinel
+print("hello") // "hello"
+print("hello", "world") // "hello world"
+print("The", "number", "is", 42) // "The number is 42"
+print([1, 2, 3]) // "[1, 2, 3]"
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // "false"
+```
+
+### Errors
+
+The built-in function `error` is equivalent to `print` but immediately causes execution to halt and the main rule to evaluate to `false`. The program execution is considered to have failed.
+
+## Program Execution
+
+Program execution begins by evaluating the source top to bottom. If evaluation is successful, the `main` rule is evaluated and its result is returned. Execution can be interrupted at any time by an error, which can be called explicitly or implicitly through illegal or undefined behavior.
+
+---
+
+> The content of this page is licensed under the [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
+>
+> Based on [The Go Programming Language Specification](https://golang.org/ref/spec) licensed under [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/language/undefined.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/language/undefined.mdx
new file mode 100644
index 0000000000..dc51531783
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/language/undefined.mdx
@@ -0,0 +1,97 @@
+---
+page_title: Sentinel Language - Undefined
+sidebar_current: docs-language-undefined
+description: >-
+ The value `undefined` is a special value representing undefined behavior or an
+ unknown value. Any operation on an `undefined` value results in `undefined`.
+layout: docs
+---
+
+# Language: Undefined
+
+The value `undefined` is a special value representing undefined behavior
+or an unknown value.
+
+Undefined is not an error because there are situations where the undefined
+value can be safely ignored and the language also provides construts for
+recovering from an undefined value.
+
+The undefined value can be created manually with `undefined`. In most cases,
+undefined is created by accessing a non-existent list element or value.
+The undefined value is created in a number of other circumstances. The documentation
+will note when an undefined value may be created.
+
+Almost any operation with `undefined` results in `undefined`. For example,
+performing math on undefined, attempting to call a function that is undefined,
+etc.
+
+## Main and Undefined
+
+If the value `undefined` is returned from the top-level `main` rule, then
+the policy is considered `false`. However, the runtime provides mechanisms
+for the host application to determine the policy failure was caused by
+an undefined value and report that to the user.
+
+The `main` rule returning the undefined value should be considered a logical
+error in most cases and should probably be corrected by the policy writer.
+
+## Debugging Undefined
+
+When an `undefined` value is first created (either explicitly or implicitly),
+the runtime will record the position where the undefined was created. This
+location can be used to find logic that could be erroneous.
+
+## Boolean Logic and Undefined
+
+Boolean logic is one way to safely recover from `undefined`. If boolean
+logic can safely determine a result despite an undefined value, that result
+will be returned.
+
+For example: `undefined or true` will result in `true`, because no matter
+what actual value `undefined` could've been if the policy behaved properly,
+the boolean expression would still be true.
+
+Another scenario where `undefined` can be safely ignored is short-circuit
+logic with `and`. `false and undefined` will result in `false`.
+
+The full table of logical operations on `undefined` is below:
+
+```sentinel
+undefined or true = true
+undefined or false = undefined
+undefined or undefined = undefined
+undefined and true = undefined
+undefined and false = undefined
+undefined and undefined = undefined
+undefined xor true = undefined
+undefined xor false = undefined
+undefined xor undefined = undefined
+
+// Short-circuit examples
+false or true or undefined = true
+false or undefined or true = true
+true and false and undefined = false
+true and undefined and false = undefined
+```
+
+## Else Expressions
+
+The `else` expression allows explicit recovery from undefined and is useful
+for setting default values.
+
+`else` is an operator where the left and right hand side are values. If the
+left-hand value is `undefined`, the right-hand value is returned. Otherwise,
+the left-hand value is returned.
+
+Examples:
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else "default"
+
+// In more complex scenarios
+
+foo() else 42 == 12
+a = config.value else "default"
+```
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/language/values.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/language/values.mdx
new file mode 100644
index 0000000000..f932480340
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/language/values.mdx
@@ -0,0 +1,210 @@
+---
+page_title: Sentinel Language - Values
+sidebar_current: docs-language-values
+description: Values are the data that Sentinel policies operate on.
+layout: docs
+---
+
+# Language: Values
+
+Values are the _data_ that Sentinel policies operate on. You can create this
+data yourself, for example by just typing the number `42`, or you may access
+this data from an external source to make policy decisions.
+
+Values have a _type_, which determines what kind of operations can be performed
+on the data. Example types are booleans (true and false), numbers,
+and strings (text).
+
+This page documents all the available value types. You can also
+[convert between types](#type-conversion).
+
+## Boolean
+
+A boolean is a value that is either true or false.
+
+A boolean value is created literally with `true` or `false`.
+
+As a policy language, booleans are central to the behavior of Sentinel.
+Booleans are used as conditions in [if statements](/sentinel/language/conditionals),
+are the result of [rules](/sentinel/language/rules), and more. The ultimate
+result of a Sentinel policy is true or false.
+
+## Integer
+
+An integer is a whole number.
+
+An integer is a 64-bit value. This means it can represent numbers from
+-9,223,372,036,854,775,808 to 9,223,372,036,854,775,808.
+
+Integers are created by typing them out literally with no
+separators (such as a comma). An optional prefix can set a non-decimal base:
+`0` for octal, and `0x` for hexadecimal. In hexadecimal literals, letters
+`a-f` and `A-F` represent values 10 to 15.
+
+Example integers are shown below:
+
+```sentinel
+42
+0600
+0xBadFace
+170141183460469
+```
+
+Integers are used for math and numerical comparison.
+
+## Float
+
+A float is a number with a decimal point. It has an integer part, a decimal
+point, a fractional part, and optionally an exponent part. Example
+floats are shown below:
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+Floats are IEEE-754 64-bit floating point numbers.
+
+## String
+
+Strings are text values.
+
+Strings are created by wrapping text in double quotes, such as `"hello"`.
+Within the quotes, any character may appear except newline and an unescaped
+double quote. The text between the quotes is the value.
+
+Because Sentinel policies must be UTF-8 encoded text, strings themselves are
+UTF-8 encoded text. This means you can put any value UTF-8 character into
+a string, such as `"日本語"`.
+
+You can have a string with a literal double-quote by _escaping_ it with
+a backslash. For example: `"they said \"hello\""` turns into the value
+`they said "hello"`.
+
+Backslash escapes can be used for much more than only escaping a double quote.
+Newlines are `\n`, a literal backslash is `\\`, and many more. The full list
+of available backslash escapes can be found
+[in the language specification](/sentinel/language/spec#string-literals).
+
+Example strings:
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+Strings do not support indexing, however it is possible to achieve a similar
+result through the combination of the [strings](/sentinel/imports/strings)
+standard import and list indexing.
+
+```sentinel playground
+import "strings"
+
+asciistr = "Hello, world!"
+utf8str = "日本語"
+
+utf8split = rule {
+ strings.split(utf8str, "")[2] == "語"
+}
+
+asciisplit = rule {
+ strings.split(asciistr, "")[5] == ","
+}
+
+main = rule { utf8split and asciisplit }
+```
+
+Strings support [slicing](/sentinel/language/slices) to efficiently create
+substrings.
+
+## List
+
+See [Lists](/sentinel/language/lists).
+
+## Map
+
+See [Maps](/sentinel/language/maps).
+
+## Rule
+
+See [Rules](/sentinel/language/rules).
+
+## Function
+
+See [Functions](/sentinel/language/functions).
+
+## Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a
+value of that type. Some examples are shown below followed by a list of exact
+rules for type conversion.
+
+```sentinel
+int(42) // 42
+int("42") // 42
+int(42.8) // 42
+int(true) // 1
+
+float(1.2) // 1.2
+float(1) // 1.0
+float("4.2") // 4.2
+float(true) // 1.0
+
+string("foo") // "foo"
+string(88) // "88"
+string(0xF) // "15"
+string(true) // "true"
+
+bool("true") // true
+bool(1) // true
+bool(-1) // true
+bool(0.1) // true
+bool("false") // false
+bool(0) // false
+```
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/language/variables.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/language/variables.mdx
new file mode 100644
index 0000000000..7516890212
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/language/variables.mdx
@@ -0,0 +1,99 @@
+---
+page_title: Sentinel Language - Variables
+sidebar_current: docs-language-vars
+description: Variables store values that can be used later.
+layout: docs
+---
+
+# Language: Variables
+
+Variables store values that can be used later.
+
+Most notably, a variable assignment of `main` is required for all
+Sentinel policies. This is the main [rule](/sentinel/language/rules) that
+is executed to determine the result of a policy. The `main` rule may
+use other variables that are assigned in the policy.
+
+Example:
+
+```sentinel
+a = 1
+b = a + 1
+```
+
+In this example, two variables are assigned: `a` and `b`. `a` is given
+the value of "1" and `b` uses the `a` to calculate its own value.
+
+## Syntax
+
+The syntax for assigning a variable is:
+
+```sentinel
+IDENTIFIER = VALUE
+```
+
+On the left of the equal sign is an _identifier_. This is a name for your
+variable and will be how you reference it later. An identifier is any
+combination of letters and digits, but must start with a letter. An
+underscore `_` is also a valid letter.
+
+On the right is any valid Sentinel value or expression. A value is a
+literal value such as a number or string. An expression is some computed
+value such as doing math, calling a function, etc.
+
+## Assignment and Reassignment
+
+A variable is _assigned_ when it is given a value. You can also _reassign_
+variables at any time by setting it to a new value. The new value takes
+effect for any subsequent use of that variable. A variable can be reassigned
+to a different type.
+
+For example:
+
+```sentinel
+a = 1 // a = 1
+b = a // b = 1
+a = "value" // a = "value", b = 1
+c = a // c = "value", b = 1
+```
+
+In the above example you can see that the variables are set and reassigned.
+Notice that the value of a variable is the _current_ value, and that reassigning
+a variable only affects _future_ uses of that variable. You can see this with
+`c` and `b` being different values.
+
+## Unassigned Variables
+
+Using a variable that is _unassigned_ is an error.
+
+In the example below, the first line would result in the policy erroring.
+Sentinel is executed top-down and the value of `c` is not available yet
+on the first line.
+
+```sentinel
+a = c // Error!
+c = 1
+```
+
+## Type Conversion
+
+While a topic more related to [values][lang-values], variables can be assigned
+(or-reassigned) a different value type via type conversion.
+
+[lang-values]: /sentinel/language/values
+
+```sentinel
+s = "1.1"
+a = int(s) // a = 1
+a = float(s) // a = 1.1
+
+a = 1
+s = string(a) // s = "1"
+```
+
+For more details, see the type conversion sections in the
+[values][lang-values-type-conversion] page, or the [Sentinel Language
+Specification][lang-spec-type-conversion].
+
+[lang-values-type-conversion]: /sentinel/language/values#type-conversion
+[lang-spec-type-conversion]: /sentinel/language/spec#type-conversion
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/nomad.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/nomad.mdx
new file mode 100644
index 0000000000..a105b0337d
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/nomad.mdx
@@ -0,0 +1,51 @@
+---
+page_title: Nomad and Sentinel
+sidebar_current: docs-app-nomad
+description: >-
+ Nomad Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies can currently execute on job
+ submission (creation, update).
+layout: docs
+---
+
+# Nomad
+
+Nomad is a simple, flexible scheduler and workload orchestrator.
+[Nomad Enterprise](https://www.hashicorp.com/products/nomad/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies can currently execute on job submission (creation, update).
+
+Sentinel policies have full access to the job structure. This allows the
+Sentinel policy to control behavior based on any attribute within a job,
+such as the driver, resource requests, network configuration, volume
+configuration, and more. The information that Sentinel policies have access
+to will expand over time.
+
+Nomad fully supports all [enforcement levels](/sentinel/concepts/enforcement-levels).
+For soft mandatory policies, the `sentinel-override` capability must be
+available on the user's ACL policy to allow override. Overrides are always
+logged.
+
+The Nomad integration with Sentinel is documented in depth in the
+[Nomad Enterprise documentation](https://www.nomadproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Only allow Docker-based jobs.**
+
+```sentinel
+# Test policy only allows Docker based tasks
+main = rule { all_drivers_docker }
+
+# all_drivers_docker checks that all the drivers in use are Docker
+
+all_drivers_docker = rule {
+ all job.task_groups as tg {
+ all tg.tasks as task {
+ task.driver is "docker"
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/terraform.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/terraform.mdx
new file mode 100644
index 0000000000..6300ab7f1c
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/terraform.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Terraform and Sentinel
+sidebar_current: docs-app-terraform
+description: >-
+ Use Sentinel with Terraform Cloud and Enterprise to enforce policy
+ on Terraform configurations, states, and plans.
+layout: docs
+---
+
+# Terraform
+
+[Terraform Cloud and Enterprise](https://www.hashicorp.com/products/terraform/) use Sentinel to enforce
+policy on [Terraform](https://www.terraform.io) configurations, states, and plans.
+
+The Sentinel integration with Terraform runs within
+Terraform Cloud and Enterprise
+after a `terraform plan` and before a `terraform apply`. The policies
+have access to the created plan, the state at the time of the plan,
+and the configuration at the time of the plan.
+
+The Terraform integration with Sentinel is documented in depth in the [Terraform Cloud and Enterprise documentation](/terraform/cloud-docs/policy-enforcement/sentinel).
+Please read that page for full documentation. This page will only show basic examples.
+
+### Examples
+
+**Example: All AWS instances must have a tag**
+
+```sentinel
+import "tfplan"
+
+main = rule {
+ all tfplan.resources.aws*instance as *, instances {
+ all instances as \_, r {
+ (length(r.applied.tags) else 0) > 0
+ }
+ }
+}
+```
+
+**Example: Only allow GCP instance sizes smaller than `n1-standard-16`**
+
+```sentinel
+import "tfplan"
+
+allowed_machine_types = [
+ "n1-standard-1",
+ "n1-standard-2",
+ "n1-standard-4",
+ "n1-standard-8",
+]
+
+main = rule {
+ all tfplan.resources.google_compute_instance as _, instances {
+ all instances as _, r {
+ r.applied.machine_type in allowed_machine_types
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/vault.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/vault.mdx
new file mode 100644
index 0000000000..6db2f8af22
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/vault.mdx
@@ -0,0 +1,64 @@
+---
+page_title: Vault and Sentinel
+sidebar_current: docs-app-vault
+description: >-
+ Vault Enterprise uses Sentinel to augment the built-in policy system to
+ provide Role Governing Policies (RGPs) and Endpoint Governing Policies (EGPs)
+ to enable complex, flexible policies across identities and endpoints.
+layout: docs
+---
+
+# Vault
+
+[Vault Enterprise](https://www.hashicorp.com/products/vault/) uses Sentinel
+to augment the built-in policy system to provide Role Governing Policies (RGPs)
+and Endpoint Governing Policies (EGPs) to enable complex, flexible policies
+across identities and endpoints.
+
+**Role Governing Policies (RGPs)** are Sentinel policies that are tied to
+particular tokens, Identity entities, or Identity groups. They have access to
+a rich set of controls across various aspects of Vault. These are evaluated
+whenever a token they're attached to is used.
+
+**Endpoint Governing Policies (EGPs)** are Sentinel policies that are tied to
+particular paths instead of tokens. They have access to as much request
+information as possible, but they can take effect even on unauthenticated
+paths, such as login paths.
+
+The Vault integration with Sentinel is documented in depth in the
+[Vault Enterprise documentation](https://www.vaultproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Endpoint policy that requires MFA authentication from a corporate network.**
+
+```sentinel
+import "sockaddr"
+
+# We expect logins to come only from our private IP range
+cidrcheck = rule {
+ sockaddr.is_contained(request.connection.remote_addr, "10.20.0.0/16")
+}
+
+# Require Ping MFA validation to succeed
+ping_valid = rule {
+ mfa.methods.ping.valid
+}
+
+main = rule when request.path is "auth/ldap/login" {
+ ping_valid and cidrcheck
+}
+```
+
+**Example: Endpoint policy that disallows tokens created before a certain time.**
+
+```sentinel
+import "time"
+import "strings"
+
+main = rule when not strings.has_prefix(request.path, "auth/ldap/login") {
+ time.load(token.creation_time).unix > time.load("2017-09-17T13:25:29Z").unix
+}
+```
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/writing/basic.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/writing/basic.mdx
new file mode 100644
index 0000000000..b62533466e
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/writing/basic.mdx
@@ -0,0 +1,113 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-basic
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Policy Basics
+
+Sentinel policies are easy to write while still supporting advanced constructs
+for creating complex policies. This page will explain the basics of writing
+Sentinel policies to get started. You don't need any prior Sentinel knowledge,
+but we do recommend reading the
+[getting started guide](/sentinel/intro/getting-started/first-policy) and
+[language guide](/sentinel/language) after this.
+
+Sentinel policies are text files written using the [Sentinel language](/sentinel/language).
+The policies are evaluated top-to-bottom. The value of `main` after execution
+determines whether a policy passes or fails.
+
+## The Simplest Policy
+
+Sentinel only requires that a policy have a `main` variable that evaluates to
+a boolean value.
+
+A valid example is shown below:
+
+```sentinel playground
+main = 10 > 5
+```
+
+This type of minimal policy is not purely academic. In practice, simple
+policies can often be reduced to a single line logical statement resulting
+in `true` or `false`. However, the expression is usually wrapped in a
+[rule](/sentinel/language/rules) for testing reasons.
+
+You can verify Sentinel will execute this minimal policy using the CLI:
+
+```
+$ sentinel apply minimal.sentinel
+Pass
+```
+
+## Logical Expressions
+
+Policy is at its core a set of logic: you can or can not perform some action
+under a certain set of circumstances. Those circumstances are logical
+expressions. Therefore, Sentinel policies primarily translate into logical
+expressions.
+
+Detailed documentation on boolean expressions is
+[available in the language guide](/sentinel/language/boolexpr).
+
+A simple numerical comparison was seen in the first example on this page.
+Sentinel also provides inclusion operators such as `contains`, `any`, `all`, and
+more. Sentinel allows some operators to have aliases to promote readability
+while remaining programmer-familiar, such as `==` which can equivalently be `is`.
+
+The example below verifies that all numbers in a list are even:
+
+```sentinel playground
+numbers = [6, 22, 8, 4, 12]
+main = all numbers as n { n % 2 is 0 }
+```
+
+## Variables
+
+A policy will very often use variables. Applications such as
+[Nomad](https://www.nomadproject.io) inject variables into the global
+scope of a policy for making policy decisions. For example, Nomad injects
+the job that is being run into the policy scope. Knowing how to use
+variables is critical to effectively using Sentinel.
+
+Detailed documentation on how to define and access variables is
+[available in the language guide](/sentinel/language/variables).
+
+Variables can be defined and used explicitly. For example:
+
+```sentinel playground
+value = 10
+main = value > 5
+```
+
+But they may also be introduced implictly by the host system. Nomad
+injects `job` into policies to describe the job that is being run. The
+policy below is a valid policy that requires a job have two task groups.
+Notice that `job` is not defined anywhere. It is implicitly inserted by
+the host application (in this case Nomad). Refer to the application you're
+writing policy for to determine if it implicitly inserts values.
+
+```json sentinel
+{
+ "policy": "main = length(job.task_groups) is 2",
+ "globals": {
+ "job": {
+ "task_groups": ["foo", "bar"]
+ }
+ }
+}
+```
+
+## And more!
+
+The Sentinel language supports many more features such as functions,
+loops, and more. You can learn about all of this in the
+[complete language guide](/sentinel/language).
+
+The other pages in the [writing policy](/sentinel/writing)
+will cover other information you need to know about writing Sentinel
+policies that isn't simply a language reference.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/writing/debugging.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/writing/debugging.mdx
new file mode 100644
index 0000000000..7dc284d222
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/writing/debugging.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-debugging
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Debugging
+
+As policies become more complex, it becomes harder to understand exactly why
+a policy may be behaving differently than you expect.
+
+Prior to actively debugging, there are multiple best practices we recommend
+following. We recommend breaking your policy down into a set of
+[rules](/sentinel/writing/rules). This will make it easier to understand
+[traces](/sentinel/writing/tracing) and make it easier to
+[test](/sentinel/writing/testing) your policies.
+This should help to debug policies up to a significant complexity.
+
+## Print Logging
+
+The built-in [print function](/sentinel/language/logging-errors#print)
+can be used to log data. The print output is only shown during failures
+or when [tracing is explicitly enabled](/sentinel/writing/tracing).
+
+The `print` function outputs each argument, which can be of any type, converted
+to a human-friendly string format. Arguments are separated with a single space.
+
+Example:
+
+```sentinel playground
+value = 42
+print("the number is", value) // the number is 42
+
+object = { "foo": false }
+print(object) // { "foo": false }
+
+main = rule { true }
+```
+
+The `print` function returns the value `true` so that it can also be
+used within rule expressions. Note that the `print` function should be
+used at the beginning of statements otherwise they may never be
+reached. This is due to internal performance techniques within Sentinel
+like [short-circuiting](https://en.wikipedia.org/wiki/Short-circuit_evaluation).
+
+Example:
+
+```sentinel playground
+// rule_a and rule_b will evaluate to false
+rule_a = rule { print("rule_a is printed") and false } // "rule_a is printed"
+rule_b = rule { false and print("rule_b is printed") } // Nothing is printed
+
+// rule_c and rule_d will evaluate to true
+rule_c = rule { print("rule_c is printed") or true } // "rule_c is printed"
+rule_d = rule { true or print("rule_d is printed") } // Nothing is printed
+
+main = rule { rule_a or rule_b or rule_c and rule_d }
+```
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/writing/imports.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/writing/imports.mdx
new file mode 100644
index 0000000000..74ac751ee4
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/writing/imports.mdx
@@ -0,0 +1,117 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom import](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only local
+context for making policy decisions.
+
+Sentinel also comes with a set of [standard imports](/sentinel/imports).
+Standard imports are available to every Sentinel policy to help policy writers
+with common tasks such as working with the time, network addresses, and more.
+
+-> **This page is about writing policies that _use_ imports.** If you're
+interested in _creating_ a new import, please see the section on [extending
+Sentinel](/sentinel/extending) for information on how to write modules and
+import plugins.
+
+## Using Imports
+
+To use an import, you use the `import` keyword at the top of your policy. This
+specifies the name of the import you want to use. The application you're writing
+the policy for must already be
+[configured](/sentinel/configuration#imports) to provide that
+import.
+
+Details on imports can be found in the
+[import section in the language reference](/sentinel/language/imports).
+
+In the example below, we use the [time](/sentinel/imports/time) import:
+
+```sentinel playground
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+There are two options to develop a policy locally when using imports:
+configure Sentinel to launch the import on `apply`, or mock the import
+using `mock`. The former requires access to the import while the
+latter is faster (doesn't have to launch a process for plugins) and
+doesn't require the import.
+
+## Mocking Imports
+
+The first option to developing policies locally is to mock the import values.
+When mocking an import, you don't need the import to be available. This can be
+useful since some imports may not be available as a plugin and may only be
+available to the application the policy runs in.
+
+Mocks are specified via the [configuration
+file](/sentinel/configuration). Mocks can also be used for
+[testing](/sentinel/writing/testing).
+
+You can supply mock configuration one of two ways, depending on your use case:
+
+- **[Using static data](/sentinel/configuration#mocking-static-data):**
+ Use this method when you can accurately represent your mock data in JSON and
+ do not need to mock complex Sentinel features such as functions.
+- **[Using Sentinel code](/sentinel/configuration#mocking-with-sentinel-code):**
+ Use this method when using a static JSON object is insufficient, such as when
+ you need to mock functions or other complex Sentinel features.
+
+Our example does not require complex data to be mocked, so a static object
+is sufficient:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ hour = 12
+ weekday_name = "Tuesday"
+ }
+ }
+}
+```
+
+This can be used via the CLI:
+
+```
+$ sentinel apply -config=config.hcl policy.sentinel
+Pass
+```
+
+## Launching Import Plugins
+
+If you have access to the plugin binary, you can launch the import. The
+benefit of this is that it is really using the import to test your
+policy. If the import changes, your policies may start failing. If you
+only use mock data and the import changes, your policies will still
+appear to work.
+
+Imports are configured in the configuration file:
+
+```hcl
+import "plugin" "custom_time" {
+ source = "/path/to/sentinel-time-import"
+}
+```
+
+This would require the `sentinel-time-import` binary. For this example
+this doesn't currently exist. We plan on writing one to provide for this
+section of the documentation.
+
+## Custom Imports
+
+You can also [create your own imports](/sentinel/extending).
+
+If your policy decisions could benefit from accessing external information,
+then you can use custom imports as a way to do this.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/writing/index.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/writing/index.mdx
new file mode 100644
index 0000000000..064069d389
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/writing/index.mdx
@@ -0,0 +1,35 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing
+description: >-
+ Sentinel provides a language and workflow for building policy across any
+ system that embeds Sentinel. By learning Sentinel once, you are able to
+ effectively control access to many systems using Sentinel's powerful features.
+ Basic concepts that are important to understand for Vault usage.
+layout: docs
+---
+
+# Writing Sentinel Policy
+
+This section covers how to write Sentinel policies.
+
+It is recommended that you complete the
+[getting started guide](/sentinel/intro/getting-started/first-policy) prior to
+reading this section of the documentation. The getting started guide will
+explain all the basics of writing and testing policies. This section of the
+documentation will serve as more of a reference guide to all available
+features of Sentinel.
+
+Sentinel provides a language and workflow for building policy across any system
+that embeds Sentinel. By learning Sentinel once, you are able to effectively
+control access to many systems using Sentinel's powerful features.
+
+Sentinel also provides a [local CLI](/sentinel/commands) for developing and testing
+Sentinel policies. This CLI can be integrated into a daily developer's workflow
+along with CI to treat [policy as code](/sentinel/concepts/policy-as-code).
+
+Sentinel uses its own [language](/sentinel/language) for writing
+policies. You can view a [language reference](/sentinel/language)
+as well as the [specification](/sentinel/language/spec) for details. You
+don't have to read those documents immediately, since the language should
+be easy enough to pick up throughout this section.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/writing/rules.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/writing/rules.mdx
new file mode 100644
index 0000000000..ab4fc69d08
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/writing/rules.mdx
@@ -0,0 +1,65 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-rules
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Rules
+
+Rules form the basis of a policy by representing behavior that is either passing
+or failing. Rules are a first class language construct in Sentinel. A policy can
+and should be broken down into rules to aid with readability, testability, and
+performance.
+
+Rules provide:
+
+- **Readability:** A policy that is broken down into rules can be read
+ more easily. The logic becomes clearer to see for policy writers when
+ they come back to it.
+
+- **Reportability:** When [tracing](/sentinel/writing/tracing) is enabled,
+ rules form the foundation of the data returned. All evaluated rules are added
+ to the trace with the data the rule evaluated to, and their position within
+ policy or module source files.
+
+- **Testability:** [Policy testing](/sentinel/writing/testing) is
+ built on asserting the values of rules. By breaking logic down into
+ rules, you're able to more effectively test your policies.
+
+- **Performance:** Rules are only evaluated on demand, and are also only
+ evaluated once. This means that a rule referenced multiple times only has a
+ one-time performance cost. For complex logic, this could result in improved
+ performance.
+
+An example usage of rules is shown below:
+
+```json sentinel
+{
+ "policy": "is_sunny = rule { weather is \"sunny\" }\nis_wednesday = rule { day is \"wednesday\" }\nmain = rule { is_sunny and is_wednesday }",
+ "globals": {
+ "weather": "sunny",
+ "day": "wednesday"
+ }
+}
+```
+
+Rules can also contain non-boolean data. This can be useful for passing more
+detail to the caller than an opaque boolean value would be able to communicate.
+
+```sentinel playground
+days = [{"weather": "sunny"}]
+main = rule {
+ filter days as d {
+ d.weather is not "sunny"
+ }
+}
+```
+
+Details about rules and their behavior can be found in
+[the language reference](/sentinel/language/rules). Rules have important
+behavior so we recommend reading the language reference on rules.
+Rules will be used throughout the remainder of the documentation.
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/writing/testing.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/writing/testing.mdx
new file mode 100644
index 0000000000..61ab0ea46c
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/writing/testing.mdx
@@ -0,0 +1,491 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-testing
+description: >-
+ Sentinel has a built-in test framework to validate a policy behaves as
+ expected.
+layout: docs
+---
+
+# Testing
+
+Sentinel has a built-in test framework to validate a policy behaves as expected.
+
+With an ever-increasing amount of automation surrounding technology,
+the guardrails provided by policies are a critical piece towards ensuring
+expected behavior. As a reliance on correct policy increases, it is important
+to test and verify policies.
+
+Testing is a necessary step to fully realize
+[policy as code](/sentinel/concepts/policy-as-code). Just as good software
+is well tested, a good set of policies should be equally well tested.
+
+## Test Folder Structure
+
+Policies are tested by asserting that [rules](/sentinel/writing/rules)
+are the expected values given a pre-configured input. Tests are run
+by executing the [test command](/sentinel/commands/test).
+
+Sentinel is opinionated about the folder structure required for tests.
+This opinionated structure allows testing to be as simple as running
+`sentinel test` with no arguments. Additionally, it becomes simple to test
+in a CI or add new policies.
+
+The structure Sentinel expects is `test//*.[hcl|json]` where ``
+is the name of your policy file without the file extension. Within that folder
+is a list of HCL or JSON files. Each file represents a single test case.
+Therefore, each policy can have multiple tests associated with it.
+
+-> Note that the primary configuration file format for Sentinel is HCL. While
+you can write configuration files in a HCL-equivalent JSON, we only discuss the
+use of HCL on this page.
+
+## Test Case Format
+
+Each HCL file within the test folder for a policy is a single test case.
+
+The file is the same configuration format as the [CLI configuration
+file](/sentinel/configuration). The format lets you define mock data, imports to
+use, and more. This mock data is the key piece in being able to test policies:
+you craft a specific scenario and assert your policy behaves as you expect.
+
+Test cases also use the `test` block within the configuration file to assert the
+value of [rules](/sentinel/writing/rules). If the `test` key is omitted, the
+policy is expected to pass. If the test key is specified, only the rules
+specified in the map will be asserted. This means if you omit `main`, then the
+final policy result is not asserted.
+
+Example with assertions:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+
+test {
+ rules = {
+ main = false
+ is_open_hours = false
+ is_weekday = true
+ }
+}
+```
+
+The configuration above specifies some parameter data, and asserts the result of
+some rules. This is the same configuration used in the example section below.
+
+## Example
+
+Lets use the following file as an example. Save this file to a directory
+and name it `policy.sentinel`. It can be named anything with the `sentinel`
+extension, but by naming it `policy.sentinel` your output should match
+the example output on this page.
+
+```sentinel
+// The day of the week.
+param day
+
+// The hour of the day.
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+### A Passing Test
+
+Next, let's define a single test case. Relative to where you saved
+the policy, create a file at the path `test/policy/good.hcl`.
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 14
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/good.hcl
+```
+
+This passed because the policy passed. We didn't assert any specific
+rules. By not specifying any assertions, `test` expects the policy itself
+to fully pass.
+
+### A Failing Test
+
+Define another test case to fail. We want to verify our policy fails when expected, too.
+
+Save the following as `test/policy/7-am.hcl`:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+FAIL - policy.sentinel
+ FAIL - test/policy/7-am.hcl
+ expected "main" to be true, got: false
+
+ trace:
+ policy.sentinel:9:1 - Rule "main"
+ bool: false
+
+ policy.sentinel:8:1 - Rule "is_open_hours"
+ bool: false
+
+ PASS - test/policy/good.hcl
+```
+
+As you can see, the test fails because "main" is false. This is good
+because the policy _should_ have failed since we specified an invalid
+hour. But, we expect main to be false and don't want our test to fail!
+Update `7-am.hcl` to add test assertions:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+
+test {
+ rules = {
+ main = false
+ is_open_hours = false
+ }
+}
+```
+
+And when we run the tests:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/7-am.hcl
+ PASS - test/policy/good.hcl
+```
+
+The test passes. We asserted that we expect the `main` rule to be false,
+the `is_open_hours` rule to be false, and the `is_weekday` rule to be
+true. By asserting some rules are true, we can verify that our policy
+is failing for reasons we expect.
+
+## Mocking
+
+The above example demonstrates how to test by supplying different
+[parameters](/sentinel/language/parameters). Parameters in a policy can be
+specifically useful when you want to control user-defined input values to a
+policy.
+
+However, generally, when testing, you will need mimic the conditions you will
+see in production. Production implementations of Sentinel will supply data using
+one of two methods:
+
+- **Global data:** Data is injected directly into the policy's scope and is
+ accessible using normal identifiers, similar to variables.
+- **Imports:** Data is stored behind an [import](/sentinel/language/imports)
+ and loaded on demand as needed by the policy author.
+
+Proper testing of a policy requires that these values be able to be _mocked_ -
+or, in other words, simulated in a way that allows the accurate testing of the
+scenarios that a policy could reasonably pass or fail under.
+
+Mocking both globals and imports can be done by setting various parts of the
+configuration file.
+
+### Mocking Globals
+
+Demonstrating the mocking of globals can be seen by making a few modifications to
+our example policy, removing the `param` declarations:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Then, change the `param` section in the configuration file to
+[`global`](/sentinel/configuration#global).
+
+```json
+global "day" {
+ value = "monday"
+}
+
+global "hour" {
+ value = 14
+}
+```
+
+This test should still pass, as if nothing had happened, although what we've
+done is shifted our parameters to globals, simulating an environment where `day`
+and `hour` are already defined for us.
+
+### Mocking Imports
+
+To mock imports, we need to use the
+[`mock`](/sentinel/configuration#mock-imports) section of the
+configuration file.
+
+Let's say the above example is behind an import named `time`.
+
+-> **NOTE:** [`time`](/sentinel/imports/time) is a valid standard import.
+This example may not be accurate to the
+import's syntax.
+
+The code now looks like this:
+
+```sentinel
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To mock this import, we can [mock it as static
+data](/sentinel/configuration#mocking-static-data). The configuration
+file now looks like, without assertions:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ weekday_name = "Monday"
+ hour = 14
+ }
+ }
+}
+```
+
+The policy will now pass, with the `time` import mocked.
+
+Data can also be [mocked as Sentinel
+code](/sentinel/configuration#mocking-with-sentinel-code). In this case,
+the above configuration file would look like:
+
+```hcl
+mock "time" {
+ module {
+ source = "mock-time.sentinel"
+ }
+}
+```
+
+And a file named `mock-time.sentinel` would now hold your mock values:
+
+```sentinel
+day = "monday"
+hour = 14
+```
+
+Mocking as Sentinel code allows more complex details to be mocked as well, such
+as functions. Say we wanted to mock the `time.load()` function. To mock this,
+just add it to the `mock-time.sentinel` file:
+
+```sentinel
+load = func(_) {
+ return {
+ "weekday_name": "Monday",
+ "hour": 14,
+ }
+}
+```
+
+Your code can now be written as:
+
+```sentinel
+import "time"
+
+t = time.load("a_mock_timestamp")
+
+is_weekday = rule { t.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { t.hour > 8 and t.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To see more details, see the [Mock
+Imports](/sentinel/configuration#mock-imports) section in the
+configuration file.
+
+## Asserting Non-Boolean Rules
+
+Non-boolean rules can also be asserted by `sentinel test`.
+
+To assert non-boolean values, simply enter the expected value into the rule
+contents:
+
+```hcl
+param "maintenance_days" {
+ value = [
+ {
+ day = "wednesday"
+ hour = 9
+ },
+ {
+ day = "friday"
+ hour = 1
+ },
+ {
+ day = "sunday"
+ hour = 1
+ },
+ ]
+}
+
+test {
+ rules = {
+ main = [
+ {
+ day = "wednesday"
+ hour = 9
+ },
+ ]
+ }
+}
+```
+
+This would assert a policy checking for violations of a maintenance policy,
+saying that maintenance hours should happen before 6AM on any given day:
+
+```sentinel
+param maintenance_days
+
+main = rule {
+ filter maintenance_days as d {
+ d.hour >= 6
+ }
+}
+```
+
+## Test JSON Output
+
+Running `sentinel test` with the `-json` flag will give you the test results in
+a JSON output format, suitable for parsing by reporting software.
+
+-> **Note:** The JSON output format is currently under development and the
+format may change at a later time. Additionally, support for other well-known
+formats, such as JUnit, may become available in the future.
+
+The current format is an object with the only top-level key being `policies`,
+with each test grouped up by policy being run.
+
+The policy result fields are:
+
+- `path`: The path of the policy and the index of the test result in the
+ `policies` field in the root object.
+- `status`: A string representation of the policy's test status as a whole. Can
+ be one of `PASS`, `FAIL`, `ERROR`, or `?`. The final status, `?`, represents a
+ policy that has no tests to process, and acts like a passing test.
+- `errors`: An array of any error messages encountered during processing the
+ policy for testing. Usually reserved for policy file or parser-related errors.
+ For case-specific errors, see the error field for the particular case.
+- `cases`: A map of case results, indexed by case path.
+
+The case result fields are:
+
+- `path`: The path of the test case and the result's index in the `cases` field
+ in the policy object.
+- `status`: A string representation of the case's test status. Can be one of
+ `PASS`, `FAIL` or `ERROR`.
+- `errors`: An array of any error messages encountered during running this test
+ case.
+- `trace`: The trace for this policy in JSON format. See the
+ [tracing](/sentinel/writing/tracing) page for more details.
+- `rule_detail`: When the `status` of this test case is `FAIL`, contains an
+ object of assertion failure detail, indexed by rule. Only failures are counted
+ here; any rules not found here can be assumed to have passed or not asserted.
+- `config_warnings`: An array of strings denoting any configuration warnings
+ found while processing the configuration for this test case.
+- `config_legacy`: Denotes whether or not the configuration is a legacy JSON
+ configuration and needs to be modernized. This field may be removed in future
+ releases.
+
+A passing example is shown below:
+
+```json
+{
+ "policies": {
+ "policy.sentinel": {
+ "path": "policy.sentinel",
+ "status": "PASS",
+ "errors": null,
+ "cases": {
+ "test/policy/pass.hcl": {
+ "path": "test/policy/pass.hcl",
+ "status": "PASS",
+ "errors": null,
+ "trace": {
+ "description": "A very basic policy to determine if a run is within working hours.",
+ "error": null,
+ "print": "",
+ "result": true,
+ "rules": {
+ "is_open_hours": {
+ "desc": "Passes if run during business hours.",
+ "ident": "is_open_hours",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 290,
+ "line": 13,
+ "column": 1
+ },
+ "value": true
+ },
+ "is_weekday": {
+ "desc": "Passes if the day does not fall on the weekend.",
+ "ident": "is_weekday",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 193,
+ "line": 10,
+ "column": 1
+ },
+ "value": true
+ },
+ "main": {
+ "desc": "",
+ "ident": "main",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 338,
+ "line": 14,
+ "column": 1
+ },
+ "value": true
+ }
+ }
+ },
+ "rule_detail": {},
+ "config_warnings": null,
+ "config_legacy": false
+ }
+ }
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.25.x/content/sentinel/docs/writing/tracing.mdx b/content/sentinel/v0.25.x/content/sentinel/docs/writing/tracing.mdx
new file mode 100644
index 0000000000..f2069bd682
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/docs/writing/tracing.mdx
@@ -0,0 +1,401 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-tracing
+description: >-
+ Sentinel provides execution traces to better understand the execution of a
+ policy.
+layout: docs
+---
+
+# Traces
+
+Sentinel provides execution traces to better understand the execution of a policy.
+
+When using the Sentinel [CLI](/sentinel/commands), traces are shown
+on policy failure during `apply` or `test`, or when the `-trace` flag is
+explicitly specified. For Sentinel-enabled applications, traces are optional
+and may require special configuration to enable.
+
+The availability of the trace may vary from application to application. While
+some applications may only log traces on failure, others, such as [Terraform
+Cloud](https://www.terraform.io/cloud), log the trace on every execution. For
+more details, consult your implementation's documentation.
+
+-> **Note:** We're actively improving Sentinel tracing in each release
+to provide better data and improve readability. The exact format of a
+trace may not match the Sentinel version embedded in the application you're
+using, but the data should be similar. The canonical format for a trace should
+get more stable as we approach a 1.0 release.
+
+## Analyzing a Trace
+
+To show traces, let's use the example policy below with the CLI:
+
+```sentinel
+param day
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+
+main = rule { is_open_hours and is_weekday }
+```
+
+Let's cause the policy to fail so the trace is more interesting.
+If you're on a terminal that supports it, the trace output will be colored
+so you can more clearly see passes, failures, and rules.
+
+
+
+ $ sentinel apply main.sentinel{'\n'}
+
+ main.sentinel:1:7: requires value for parameter day
+
+ {'\n'}
+ {'\n'}
+ {'\n'}
+ {' '}Values can be strings, floats, or JSON array or object values. To force
+ {'\n'}
+ {' '}strings, use quotes.{'\n'}
+ {'\n'}
+ {' '}Enter a value: sunday{'\n'}
+ {'\n'}
+
+ main.sentinel:2:7: requires value for parameter hour
+
+ {'\n'}
+ {'\n'}
+ {'\n'}
+ {' '}Values can be strings, floats, or JSON array or object values. To force
+ {'\n'}
+ {' '}strings, use quotes.{'\n'}
+ {'\n'}
+ {' '}Enter a value: 14{'\n'}
+ {'\n'}
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+
+ Fail - main.sentinel
+
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:7:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}
+ false
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:5:1 - Rule "is_open_hours"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}true{'\n'}
+ {'\n'}
+
+ main.sentinel:4:1 - Rule "is_weekday"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}false{'\n'}
+
+
+
+We can see all of our rules neatly and clearly displayed along with the result
+of the policy. If you are getting the trace due to a failed policy and not
+because you explicitly used `-trace`, an informational message is displayed.
+
+As we can see from our basic example, the `main` rule has failed and its result
+is highlighted in red. All peripheral rules that were also evaluated as part of
+the policy are also displayed below the main rule. Source positions are also
+displayed so that you can find the references to the rules in your code.
+
+Via the trace, we can clearly see that while `is_open_hours` returned a true
+result, `is_weekday` did not, and per the logic in our code, the policy is
+rejected.
+
+## Non-Boolean Rules and the Trace
+
+The trace is particularly important when working with rules that return
+non-boolean data, such as rules where you want to see violations instead of a
+simple opaque boolean value. Let's take our example from the [rules](./rules)
+section, and write a small static policy for it:
+
+```sentinel playground
+days = [
+ {"weekday": "Sunday", "weather": "clouds"},
+ {"weekday": "Monday", "weather": "rain"},
+ {"weekday": "Tuesday", "weather": "sunny"},
+ {"weekday": "Wednesday", "weather": "sunny"},
+ {"weekday": "Thursday", "weather": "clouds"},
+ {"weekday": "Friday", "weather": "rain"},
+ {"weekday": "Saturday", "weather": "sunny"},
+]
+
+main = rule {
+ filter days as d {
+ d.weather is not "sunny"
+ }
+}
+```
+
+Here is the output of our policy:
+
+
+
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+ {'\n'}
+
+ Fail - weather.sentinel
+
+ {'\n'}
+ {'\n'}
+
+ weather.sentinel:11:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+
+ {' [\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Sunday"'}
+ {'\n'}
+ {' "weather": "clouds"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Monday"'}
+ {'\n'}
+ {' "weather": "rain"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Thursday"'}
+ {'\n'}
+ {' "weather": "clouds"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Friday"'}
+ {'\n'}
+ {' "weather": "rain"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' ]'}
+
+
+
+
+We can now see all of the weekdays with non-sunny weather.
+
+Note that to prevent formatting issues and excessive output, the human-readable
+trace is limited in the volume of output it will display for collections.
+
+## JSON Output
+
+The trace can also be displayed in JSON by adding `-json` to `sentinel apply`
+and `sentinel test`, and will display the trace in its entirety, unlike the
+standard CLI output.
+
+Here is the result of running `sentinel apply -json` against our weather policy:
+
+```json
+{
+ "can_override": false,
+ "error": null,
+ "policies": [
+ {
+ "allowed_failure": false,
+ "error": null,
+ "policy": "weather.sentinel",
+ "result": false,
+ "trace": {
+ "description": "",
+ "error": null,
+ "print": "",
+ "result": false,
+ "rules": {
+ "main": {
+ "desc": "",
+ "ident": "main",
+ "position": {
+ "filename": "weather.sentinel",
+ "offset": 328,
+ "line": 11,
+ "column": 1
+ },
+ "value": [
+ {
+ "weather": "clouds",
+ "weekday": "Sunday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Monday"
+ },
+ {
+ "weather": "clouds",
+ "weekday": "Thursday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Friday"
+ }
+ ]
+ }
+ }
+ }
+ }
+ ],
+ "result": false
+}
+```
+
+Note that the trace is always included with this output, regardless of whether
+or not the policy passes or fails; using `-trace` is unnecessary.
+
+Additionally, with `sentinel apply`, you can display the value of a single rule
+in JSON, instead of the entire trace. This is done with the `-json-rule` flag.
+
+-> **Note:** `sentinel apply -json-rule` needs to be run either against a single
+on-disk policy, or against a single policy within a policy set. It is ignored
+in full policy set executions and functions exactly like `-json` in these
+scenarios.
+
+Here is the result of executing `sentinel apply -json-rule main weather.sentinel`:
+
+```json
+[
+ {
+ "weather": "clouds",
+ "weekday": "Sunday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Monday"
+ },
+ {
+ "weather": "clouds",
+ "weekday": "Thursday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Friday"
+ }
+]
+```
+
+## Other Trace Details
+
+There are some other details that are good to know when working with the trace:
+
+- Only rules that get executed are added to the trace. Considering our first
+ example where the `main` rule is the expression `is_open_hours and is_weekday`,
+ if our expression was using an `or` operator instead of `and`, in addition to
+ the policy passing, `is_open_hours` would be present in the trace, but
+ `is_weekday` would not be, as the policy would have never evaluated that rule.
+- Descriptions for both rules and policies will show up in the trace as well if
+ present. Here is the output to our first policy with the appropriate
+ annotations:
+
+
+
+ ...{'\n\n'}
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+
+ Fail - main.sentinel
+
+ {'\n'}
+ {'\n'}
+ Description:{'\n'}
+ {' A very basic policy to determine if a run is within working\n'}
+ {' hours.\n'}
+ {'\n'}
+
+ main.sentinel:7:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}
+ false
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:5:1 - Rule "is_open_hours"
+
+ {'\n'}
+ {' '}
+ Description:
+ {'\n'}
+ {' Passes if run during business hours.\n'}
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}true{'\n'}
+ {'\n'}
+
+ main.sentinel:4:1 - Rule "is_weekday"
+
+ {'\n'}
+ {' '}
+ Description:
+ {'\n'}
+ {' Passes if the day does not fall on the weekend.\n'}
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}false{'\n'}
+
+
diff --git a/content/sentinel/v0.25.x/content/sentinel/intro/getting-started/first-policy.mdx b/content/sentinel/v0.25.x/content/sentinel/intro/getting-started/first-policy.mdx
new file mode 100644
index 0000000000..38d7b22c9a
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/intro/getting-started/first-policy.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Your First Sentinel Policy
+sidebar_current: intro-getting-started-first
+description: Let's begin by writing a working Sentinel policy.
+layout: intro
+---
+
+# Your First Sentinel Policy
+
+Sentinel is a system to enforce complex policies on an integrated application.
+
+Writing Sentinel policy requires minimal programming experience. The Sentinel
+language is designed to be approachable and learned quickly and easily. Whether
+you're a professional programmer or someone who uses SQL and Excel, you can
+learn to write Sentinel policies.
+
+Let's begin by writing a simple, working Sentinel policy:
+
+```sentinel playground
+hour = 4
+main = rule { hour >= 0 and hour < 12 }
+```
+
+This is a valid Sentinel policy. It will pass since we hardcoded the
+`hour` to be 4. In a real system, `hour` may be something that is provided
+to us and actually set to the current hour. We'll learn more about that later.
+
+For now, try running this policy locally. Save the above example to a file
+named `policy.sentinel` and execute it. Then, modify the policy to make it
+fail. Play around more if you'd like.
+
+```
+$ sentinel apply policy.sentinel
+Pass
+```
+
+## Main
+
+Every Sentinel policy must have a `main` rule. This is the rule that
+is evaluated to determine the result of a policy.
+
+A rule describes an expression that generally means one of two things:
+
+- Does a policy _pass a condition_ that would authorize an operation? In our
+ above example, describe a policy that checks the supplied hour (4) is within an
+ authorized time window (between 0 - midnight, and 12 noon).
+- Conversely, can a policy find any _violations_ that would block authorization
+ of the operation? Building on the above, consider a policy that takes a
+ schedule, and finds all time blocks that fall outside of the example time window
+ supplied in the above policy.
+
+It is easy to imagine that such a rule might be used in a system such
+as [Nomad](https://www.nomadproject.io) to restrict the times when a
+deploy can occur. The power of arbitrary logical statements within Sentinel
+allows Sentinel policies to restrict almost any behavior.
+
+Next, we'll
+[introduce and explain rules](/sentinel/intro/getting-started/rules)
+so we can use them in our policies.
diff --git a/content/sentinel/v0.25.x/content/sentinel/intro/getting-started/imports.mdx b/content/sentinel/v0.25.x/content/sentinel/intro/getting-started/imports.mdx
new file mode 100644
index 0000000000..dbbb146ecf
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/intro/getting-started/imports.mdx
@@ -0,0 +1,58 @@
+---
+page_title: Imports
+sidebar_current: intro-getting-started-imports
+description: Imports enable Sentinel to access external data and functions.
+layout: intro
+---
+
+# Imports
+
+Imports enable Sentinel to access external data and functions.
+
+Sentinel ships with a set of [standard imports](/sentinel/imports).
+Standard imports are available by default to every Sentinel policy. Note that
+the application embedding Sentinel may allow or deny the available
+set of imports.
+
+To use an import, use the `import` statement. Then, access data and
+functions on the import using the import name followed by a period and
+the field you want to access. The example below checks whether the request
+path starts with a prefix. Assume that `request_path` is available.
+
+```sentinel
+import "strings"
+
+main = rule { strings.has_prefix(request_path, "/admin") }
+```
+
+## External Data
+
+The true power in imports is their ability to reference external data.
+Imports can be added to a Sentinel-enabled application through
+[plugins](/sentinel/extending). This enables any Sentinel-enabled
+application to make policy decision based on any source of data.
+
+This ability allows the policy system to enforce almost any necessary
+organizational policy, since the ability of a policy isn't restricted
+purely to the embedding application's data model.
+
+For example, policies in [Nomad](https://www.nomadproject.io) can
+access data in [Consul](https://www.consul.io) to determine attributes
+of a policy. In the example below, we use a hypothetical Consul import:
+
+```sentinel
+import "consul"
+
+main = rule {
+ job.tasks[0].resources.memory <= int(consul.get("policy/nomad/max-memory"))
+}
+```
+
+In this example, a Nomad job's memory usage is limited to the value of
+a Consul key/value item. By simply changing a Consul KV entry, policy
+can be changed. Imports can do anything, you can [write your own import plugins](/sentinel/extending)
+to extend Sentinel.
+
+Next, we'll learn how to
+[test policies](/sentinel/intro/getting-started/testing)
+to ensure our policy logic is correct.
diff --git a/content/sentinel/v0.25.x/content/sentinel/intro/getting-started/index.mdx b/content/sentinel/v0.25.x/content/sentinel/intro/getting-started/index.mdx
new file mode 100644
index 0000000000..5c9b6b43c5
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/intro/getting-started/index.mdx
@@ -0,0 +1,16 @@
+---
+page_title: Install Sentinel CLI - Getting Started
+sidebar_current: intro-getting-started-overview
+description: The first step to using Sentinel is to get the CLI installed.
+layout: intro
+---
+
+# Getting Started With Sentinel
+
+This section covers getting started with Sentinel. Here, we will take you
+through the first steps that you will need to do to get started with installing
+the Sentinel CLI, writing your first policy, and getting familiar with rules,
+boolean logic, imports, and testing.
+
+You can select a topic on the menu to get started. The first step is to [install
+the Sentinel CLI](/sentinel/intro/getting-started/install).
diff --git a/content/sentinel/v0.25.x/content/sentinel/intro/getting-started/install.mdx b/content/sentinel/v0.25.x/content/sentinel/intro/getting-started/install.mdx
new file mode 100644
index 0000000000..a23a5b7239
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/intro/getting-started/install.mdx
@@ -0,0 +1,56 @@
+---
+page_title: Install Sentinel CLI - Getting Started
+sidebar_current: intro-getting-started-install
+description: The first step to using Sentinel is to get the CLI installed.
+layout: intro
+---
+
+# Install Sentinel CLI
+
+Sentinel is a policy framework that is embedded in the enterprise versions of
+HashiCorp tools. The policies you write are deployed to these applications and
+enforced there.
+
+The Sentinel CLI is a command-line interface for local development and testing.
+For the getting started guide, we'll use the CLI to learn how to write policies
+for Sentinel-enabled applications. The Sentinel CLI is distributed as a [binary
+package](/sentinel/install) for all supported platforms and architectures.
+
+## Installation Instructions
+
+To install the Sentinel CLI, find the [appropriate package](/sentinel/install) for your
+system and download it. The CLI is packaged as a zip archive.
+
+After downloading Sentinel, unzip the package. The CLI runs as a single binary
+named `sentinel`. Any other files in the package can be safely removed and
+Sentinel will still function.
+
+The final step is to make sure that the `sentinel` binary is available on the `PATH`.
+See [this page](https://stackoverflow.com/questions/14637979/how-to-permanently-set-path-on-linux)
+for instructions on setting the PATH on Linux and Mac.
+[This page](https://stackoverflow.com/questions/1618280/where-can-i-set-path-to-make-exe-on-windows)
+contains instructions for setting the PATH on Windows.
+
+## Verifying the Installation
+
+After installing the Sentinel CLI, verify the installation worked by opening a
+new terminal session and checking that the `sentinel` binary is available. By
+executing `sentinel`, you should see help output similar to the following:
+
+```
+$ sentinel
+Usage: sentinel [--version] [--help] []
+
+Available commands are:
+ apply Execute a policy and output the result
+ fmt Format Sentinel policy to a canonical format
+ test Test policies
+ version Prints the Sentinel version
+```
+
+If you get an error that the binary could not be found, then your `PATH`
+environment variable was not setup properly. Please go back and ensure that your
+`PATH` variable contains the directory where Sentinel was installed.
+
+Otherwise, the CLI is installed and ready to go. Let's celebrate by [writing our
+first policy!](/sentinel/intro/getting-started/first-policy)
diff --git a/content/sentinel/v0.25.x/content/sentinel/intro/getting-started/logic.mdx b/content/sentinel/v0.25.x/content/sentinel/intro/getting-started/logic.mdx
new file mode 100644
index 0000000000..d3716a64ea
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/intro/getting-started/logic.mdx
@@ -0,0 +1,54 @@
+---
+page_title: Logic
+sidebar_current: intro-getting-started-logic
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Logic
+
+The core of a policy is a set of logical expressions to determine whether
+a behavior is allowed or not.
+Sentinel makes it easy to write readable logical expressions to express
+the intended policy.
+
+The logical expression syntax will be familiar to anyone who has programmed
+before. Sentinel also supports a couple more unique constructs to assist
+with common policy requirements. Detailed documentation on how to write
+logical expressions and the supported operators is available in
+[the boolean expression reference](/sentinel/language/boolexpr).
+
+Example logic:
+
+```sentinel
+a is b // Equality, you can also use ==
+a is not b // Inequality, you can also use !=
+
+a > b // Relational operators, comparison
+a < b
+a >= b
+a <= b
+
+a contains b // Inclusion check, substrings, etc.
+a not contains b // Negated version of above
+```
+
+The full list of available operators can be found in
+[the boolean expression reference](/sentinel/language/boolexpr).
+
+Logic can be combined with `and` or `or`. When combined with `and`, both
+sides must result in `true`. When combined with `or`, only one side needs
+to be true to result in `true`.
+
+With these building blocks, basic rules can be built up:
+
+```sentinel
+main = rule {
+ hour >= 8 and
+ hour < 17
+}
+```
+
+Next, we'll introduce
+[imports](/sentinel/intro/getting-started/imports)
+so that we can write rules that interact with external data.
diff --git a/content/sentinel/v0.25.x/content/sentinel/intro/getting-started/next-steps.mdx b/content/sentinel/v0.25.x/content/sentinel/intro/getting-started/next-steps.mdx
new file mode 100644
index 0000000000..23b470be59
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/intro/getting-started/next-steps.mdx
@@ -0,0 +1,21 @@
+---
+page_title: Next Steps
+sidebar_current: intro-getting-started-nextsteps
+description: That concludes the getting started guide for Sentinel.
+layout: intro
+---
+
+# Next Steps
+
+That concludes the getting started guide for Sentinel. Hopefully you're excited
+about the possibilities of Sentinel and ready to put this knowledge to use to
+improve the safety of your environments.
+
+We've covered the basics of the core features of Sentinel in this guide.
+We recommend the following as next steps:
+
+- [Documentation](/sentinel) - The documentation is an in-depth
+ reference guide of all the features of Sentinel.
+
+- [Language Reference](/sentinel/language) - The language reference
+ is a complete reference to the features of the Sentine policy language.
diff --git a/content/sentinel/v0.25.x/content/sentinel/intro/getting-started/rules.mdx b/content/sentinel/v0.25.x/content/sentinel/intro/getting-started/rules.mdx
new file mode 100644
index 0000000000..f12a61fe75
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/intro/getting-started/rules.mdx
@@ -0,0 +1,86 @@
+---
+page_title: Rules
+sidebar_current: intro-getting-started-rules
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Rules
+
+Rules in Sentinel are a first-class concept. Within a policy, rules serve a few
+purposes:
+
+- They make complex logic more understandable by allowing said logic to be
+ broken down.
+- They allow assertion of this logic through
+ [testing](/sentinel/intro/getting-started/testing) of the rule's contents.
+- They facilitate reporting of data as rules get published as part of the [policy
+ trace](/sentinel/writing/tracing).
+
+A rule functions in ways similar to both a variable and a function: they hold a
+value, but are lazily evaluated; a rule's value is not assigned until the first
+time it's referenced in a policy. Additionally, while the value of evaluated
+rules will be available within a policy's trace after it's evaluated, values of
+variables - and the return value of functions - are not. Finally, a rule value
+is memoized - further references to the rule will not change its result.
+
+Rules can hold more than just boolean data. For more advanced rule patterns, see
+[the language reference](/sentinel/language/rules).
+
+## Writing Rules
+
+Let's look at the Sentinel policy we wrote in the previous section:
+
+```sentinel playground
+hour = 4
+main = rule { hour >= 0 and hour < 12 }
+```
+
+The logic in this policy is straightforward and easy to understand. However,
+it is common for policy logic to become much more complicated. Rules are the
+key abstraction for making complex logic understandable. We can turn the
+policy above into a set of rules:
+
+```sentinel playground
+hour = 4
+
+past_midnight = rule { hour >= 0 }
+before_noon = rule { hour < 12 }
+
+main = rule {
+ past_midnight and
+ before_noon
+}
+```
+
+This policy is easier to read. Our original policy was already quite
+simple, but it is easy to imagine a more complex policy becoming much
+more readable by extracting logical expressions into a set of rules.
+
+Rules don't just exist to make a policy more readable. They're also a
+testing and debugging point. For testing, you can assert that certain rules
+are certain values given a specific input to verify that your policy works
+as you expect. Testing and debugging are covered in later sections.
+
+## Conditional Rules
+
+In many cases, a rule is only valid when something else is also true.
+
+Consider the human language statement "before you eat, you must wash your hands."
+The rule "you must wash your hands" is only valid "before you eat". In any
+other scenario, the rule is meaningless.
+This is also true in a technical context. Imagine the rule "if the driver
+is Docker, you must not expose any ports." For this example, assume
+rules `is_docker` and `has_no_exposed_ports` exist. You can write this rule
+like this:
+
+```sentinel
+main = rule when is_docker { has_no_exposed_ports }
+```
+
+When `is_docker` is true, the rule is evaluated as normal. However,
+when `is_docker` is false, the rule always returns `true`, because the
+logic of the rule is meaningless.
+
+Let's learn how to create more complex rules with
+[logical expressions](/sentinel/intro/getting-started/logic).
diff --git a/content/sentinel/v0.25.x/content/sentinel/intro/getting-started/testing.mdx b/content/sentinel/v0.25.x/content/sentinel/intro/getting-started/testing.mdx
new file mode 100644
index 0000000000..7149a7966f
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/intro/getting-started/testing.mdx
@@ -0,0 +1,72 @@
+---
+page_title: Testing
+sidebar_current: intro-getting-started-testing
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Testing
+
+It is important to ensure the policies you write are correct. Sentinel
+includes a built-in test framework that can be run locally and in CI
+environments to test that policies behave as expected.
+
+A common pitfall with many simple ACL systems is that they provide no easy
+way to verify their correctness. You basically have to set the ACL and try
+the behaviors against a real system to verify it is working as expected.
+This requires a lot of setup and is unique to each system.
+
+[Sentinel's built-in test framework](/sentinel/writing/testing) has zero
+dependencies. Contained within the [Sentinel CLI](/sentinel/commands), it can
+mock the data that real systems are exposing to the policy. It is designed to be
+CI-friendly and enables continuous testing of your policies. This is necessary
+for [policy as code](/sentinel/concepts/policy-as-code).
+
+Detailed documentation on testing policies is available in [the Sentinel Testing
+reference](/sentinel/writing/testing).
+
+## Writing Tests
+
+For this example, save the following policy as `policy.sentinel`:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Next, let's write a passing test case. This will test that the policy tests
+when we expect it to pass. Save the following in `test/policy/good.hcl`:
+
+```hcl
+global "day" {
+ value = "monday"
+}
+
+global "hour" {
+ value = 14
+}
+```
+
+And run `sentinel test`:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/good.json
+```
+
+The `sentinel test` command will automatically find all Sentinel policies
+and their associated test cases and run them all. The test framework will
+run all HCL files as individual test cases, allowing you to test a variety
+of scenarios for your policies.
+
+Try adding another test case to force the policy to fail. This test case can
+be saved at `test/policy/fail.hcl`. For test cases with intentional
+failures, you'll need to use the [test assertions described in the testing
+reference](/sentinel/writing/testing#a-failing-test).
+
+Now that you have a good grasp of what Sentinel is and how to use it, please feel
+free to look through the
+[next steps](/sentinel/intro/getting-started/next-steps)
+you can take to further your Sentinel knowledge.
diff --git a/content/sentinel/v0.25.x/content/sentinel/intro/index.mdx b/content/sentinel/v0.25.x/content/sentinel/intro/index.mdx
new file mode 100644
index 0000000000..785a442fb8
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/intro/index.mdx
@@ -0,0 +1,32 @@
+---
+layout: intro
+page_title: Documentation
+sidebar_current: docs-home
+description: >-
+ Sentinel is a language and framework for policy built to be embedded in
+ existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+[introduction](/sentinel/intro) to understand what Sentinel is, how it
+compares to other software, and more.
+
+
diff --git a/content/sentinel/v0.25.x/content/sentinel/intro/what.mdx b/content/sentinel/v0.25.x/content/sentinel/intro/what.mdx
new file mode 100644
index 0000000000..36853d366c
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/intro/what.mdx
@@ -0,0 +1,64 @@
+---
+page_title: Introduction
+sidebar_current: intro-what
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: intro
+---
+
+# Introduction to Sentinel
+
+Welcome to the intro guide to Sentinel! This guide is the best
+place to start with Sentinel.
+
+If you are already familiar with the basics of Sentinel, the
+[documentation](/sentinel) provides a better reference
+guide for all available features as well as internals.
+
+## What is Sentinel?
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+Sentinel provides a language for writing policy and a workflow for developing
+and testing policies independent of the software they'll be written to. We
+also provide a framework for developers to build plugins that allow a
+Sentinel-enabled system to access external information to make policy
+decisions.
+
+Most systems today have some degree of access control. You are able to define
+identities and what they have access to. These ACL systems solve an immediate
+and necessary problem of locking down a system in very broad strokes. Sentinel
+is a reusable system for more advanced software policy decisions. Sentinel
+enables:
+
+- **Fine-Grained Policy**: Most ACL systems only enable coarse-grained
+ behaviors: "read", "write", etc. Sentinel enables fine-grained behavior such
+ as disallowing a certain API call when specific parameters are present.
+
+- **Logic-Based Policy**: You can write policy using full
+ conditional logic. For example, you may only allow a certain application behavior
+ on Monday to Thursday unless there is a manager override.
+
+- **Accessing External Information**: Sentinel can source external information
+ to be used in policy decisions. For example, a policy that restricts the size
+ of a payload may read data from [Consul](https://www.consul.io) to determine
+ the payload size limit.
+
+- **Enforcement levels**: Sentinel allows policies to be defined along
+ with an "enforcement level" that dictates the pass/fail behavior of a policy.
+ Advisory policies warn if they fail, soft mandatory policies can have their
+ failures overridden, and hard mandatory policies must pass under all
+ circumstances. Having this as a built-in concept enables you to model
+ policy more accurately and completely for your organization.
+
+## Next Steps
+
+See the page on [Why Sentinel?](/sentinel/intro/why) to learn more
+about the origins of the product. Then, continue onwards with
+the [getting started guide](/sentinel/intro/getting-started/install) to write
+your first policies and see how Sentinel works in practice.
diff --git a/content/sentinel/v0.25.x/content/sentinel/intro/why.mdx b/content/sentinel/v0.25.x/content/sentinel/intro/why.mdx
new file mode 100644
index 0000000000..98b58aab92
--- /dev/null
+++ b/content/sentinel/v0.25.x/content/sentinel/intro/why.mdx
@@ -0,0 +1,49 @@
+---
+page_title: Why Sentinel?
+sidebar_current: intro-why
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: intro
+---
+
+# Why Sentinel?
+
+The growth of infrastructure and applications has been enabled in part
+by an increasing trend towards automation everywhere.
+Configuration as code (such as Chef or Puppet with [Packer](https://www.packer.io))
+has enabled automated machine configuration. Infrastructure
+as code (such as [Terraform](https://www.terraform.io)) has enabled
+automatic infrastructure creation. And schedulers (such as
+[Nomad](https://www.nomadproject.io) and Kubernetes) have enabled
+automatic application deployment.
+Sentinel enables guardrails to be put in place
+on automation while allowing the codification and automatic enforcement
+of business requirements in critical areas of your infrastructure.
+
+Meanwhile, businesses have business requirements and sometimes legal
+requirements which must be expressed in policies. Traditionally, these
+policies are enforced by humans. But in a highly automated world, the
+automation is only as fast as its slowest component. In many cases, this
+is the human verification step.
+
+As an example: before infrastructure as code and autoscaling, if an order
+came through for 5,000 new machines, a human would likely respond to the
+ticket verifying that the user really intended to order 5,000 new machines.
+Today, automation can almost always freely order 5,000 new compute instances
+without any hesitation, which can result in unintended expense or system
+instability.
+
+Sentinel introduces [policy as code](/sentinel/concepts/policy-as-code)
+and a powerful framework built-in to HashiCorp tooling to allow automation
+guardrails, business requirements, legal compliance, and more to be
+actively enforced by the running systems in realtime.
+
+With Sentinel, you can require an override to create certain numbers of
+infrastructure resources. You can disallow unsafe deployment configurations
+with Nomad. You can enforce certain key/value formats in Consul. You
+can restrict secret access by time in Vault. And more.
+
+Sentinel is available today in HashiCorp enterprise products. You can
+try Sentinel immediately with the [getting started guide](/sentinel/intro/getting-started/install) or learn more about the integrations in the
+[documentation](/sentinel).
diff --git a/content/sentinel/v0.25.x/data/docs-nav-data.json b/content/sentinel/v0.25.x/data/docs-nav-data.json
new file mode 100644
index 0000000000..0bfc027b10
--- /dev/null
+++ b/content/sentinel/v0.25.x/data/docs-nav-data.json
@@ -0,0 +1,366 @@
+[
+ {
+ "title": "Release Notes",
+ "path": "changelog"
+ },
+ {
+ "title": "Basic Concepts",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "concepts"
+ },
+ {
+ "title": "Policy as Code",
+ "path": "concepts/policy-as-code"
+ },
+ {
+ "title": "Policy Language",
+ "path": "concepts/language"
+ },
+ {
+ "title": "Imports",
+ "path": "concepts/imports"
+ },
+ {
+ "title": "Enforcement Levels",
+ "path": "concepts/enforcement-levels"
+ }
+ ]
+ },
+ {
+ "title": "Configuration File Syntax",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "configuration"
+ },
+ {
+ "title": "Override Files",
+ "path": "configuration/overrides"
+ },
+ {
+ "title": "Remote Sources",
+ "path": "configuration/remote-sources"
+ }
+ ]
+ },
+ {
+ "title": "Commands (CLI)",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "commands"
+ },
+ {
+ "title": "apply",
+ "path": "commands/apply"
+ },
+ {
+ "title": "fmt",
+ "path": "commands/fmt"
+ },
+ {
+ "title": "test",
+ "path": "commands/test"
+ }
+ ]
+ },
+ {
+ "title": "Writing Policy",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "writing"
+ },
+ {
+ "title": "Basics",
+ "path": "writing/basic"
+ },
+ {
+ "title": "Rules",
+ "path": "writing/rules"
+ },
+ {
+ "title": "Traces",
+ "path": "writing/tracing"
+ },
+ {
+ "title": "Testing",
+ "path": "writing/testing"
+ },
+ {
+ "title": "Imports",
+ "path": "writing/imports"
+ },
+ {
+ "title": "Debugging",
+ "path": "writing/debugging"
+ }
+ ]
+ },
+ {
+ "title": "Extending Sentinel",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "extending"
+ },
+ {
+ "title": "Modules",
+ "path": "extending/modules"
+ },
+ {
+ "title": "Plugins",
+ "path": "extending/plugins"
+ },
+ {
+ "title": "Static Imports",
+ "path": "extending/static-imports"
+ },
+ {
+ "title": "Internals",
+ "path": "extending/internals"
+ }
+ ]
+ },
+ {
+ "title": "Features",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "features"
+ },
+ {
+ "title": "terraform",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "features/terraform"
+ },
+ {
+ "title": "tfplan/v1",
+ "path": "features/terraform/tfplan-v1"
+ },
+ {
+ "title": "tfplan/v2",
+ "path": "features/terraform/tfplan-v2"
+ },
+ {
+ "title": "tfconfig/v1",
+ "path": "features/terraform/tfconfig-v1"
+ },
+ {
+ "title": "tfconfig/v2",
+ "path": "features/terraform/tfconfig-v2"
+ },
+ {
+ "title": "tfstate/v1",
+ "path": "features/terraform/tfstate-v1"
+ },
+ {
+ "title": "tfstate/v2",
+ "path": "features/terraform/tfstate-v2"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Language",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "language"
+ },
+ {
+ "title": "Variables",
+ "path": "language/variables"
+ },
+ {
+ "title": "Values",
+ "path": "language/values"
+ },
+ {
+ "title": "Lists",
+ "path": "language/lists"
+ },
+ {
+ "title": "Maps",
+ "path": "language/maps"
+ },
+ {
+ "title": "Rules",
+ "path": "language/rules"
+ },
+ {
+ "title": "Imports",
+ "path": "language/imports"
+ },
+ {
+ "title": "Parameters",
+ "path": "language/parameters"
+ },
+ {
+ "title": "Boolean Expressions",
+ "path": "language/boolexpr"
+ },
+ {
+ "title": "Arithmetic",
+ "path": "language/arithmetic"
+ },
+ {
+ "title": "Slices",
+ "path": "language/slices"
+ },
+ {
+ "title": "Conditionals",
+ "path": "language/conditionals"
+ },
+ {
+ "title": "Loops",
+ "path": "language/loops"
+ },
+ {
+ "title": "Collection Operations",
+ "path": "language/collection-operations"
+ },
+ {
+ "title": "Functions",
+ "path": "language/functions"
+ },
+ {
+ "title": "Scope",
+ "path": "language/scope"
+ },
+ {
+ "title": "Undefined",
+ "path": "language/undefined"
+ },
+ {
+ "title": "Logging and Errors",
+ "path": "language/logging-errors"
+ },
+ {
+ "title": "Specification",
+ "path": "language/spec"
+ }
+ ]
+ },
+ {
+ "title": "Builtin Functions",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "functions"
+ },
+ {
+ "title": "append",
+ "path": "functions/append"
+ },
+ {
+ "title": "delete",
+ "path": "functions/delete"
+ },
+ {
+ "title": "error",
+ "path": "functions/error"
+ },
+ {
+ "title": "keys",
+ "path": "functions/keys"
+ },
+ {
+ "title": "length",
+ "path": "functions/length"
+ },
+ {
+ "title": "print",
+ "path": "functions/print"
+ },
+ {
+ "title": "range",
+ "path": "functions/range"
+ },
+ {
+ "title": "values",
+ "path": "functions/values"
+ }
+ ]
+ },
+ {
+ "title": "Standard Imports",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "imports"
+ },
+ {
+ "title": "base64",
+ "path": "imports/base64"
+ },
+ {
+ "title": "decimal",
+ "path": "imports/decimal"
+ },
+ {
+ "title": "http",
+ "path": "imports/http"
+ },
+ {
+ "title": "json",
+ "path": "imports/json"
+ },
+ {
+ "title": "runtime",
+ "path": "imports/runtime"
+ },
+ {
+ "title": "sockaddr",
+ "path": "imports/sockaddr"
+ },
+ {
+ "title": "strings",
+ "path": "imports/strings"
+ },
+ {
+ "title": "time",
+ "path": "imports/time"
+ },
+ {
+ "title": "types",
+ "path": "imports/types"
+ },
+ {
+ "title": "units",
+ "path": "imports/units"
+ },
+ {
+ "title": "version",
+ "path": "imports/version"
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Consul",
+ "path": "consul"
+ },
+ {
+ "title": "Nomad",
+ "path": "nomad"
+ },
+ {
+ "title": "Terraform",
+ "path": "terraform"
+ },
+ {
+ "title": "Vault",
+ "path": "vault"
+ }
+]
diff --git a/content/sentinel/v0.25.x/data/intro-nav-data.json b/content/sentinel/v0.25.x/data/intro-nav-data.json
new file mode 100644
index 0000000000..c95181af70
--- /dev/null
+++ b/content/sentinel/v0.25.x/data/intro-nav-data.json
@@ -0,0 +1,47 @@
+[
+ {
+ "title": "What is Sentinel?",
+ "path": "what"
+ },
+ {
+ "title": "Why Sentinel?",
+ "path": "why"
+ },
+ {
+ "title": "Getting Started",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "getting-started"
+ },
+ {
+ "title": "Installing the CLI",
+ "path": "getting-started/install"
+ },
+ {
+ "title": "Your First Policy",
+ "path": "getting-started/first-policy"
+ },
+ {
+ "title": "Rules",
+ "path": "getting-started/rules"
+ },
+ {
+ "title": "Logic",
+ "path": "getting-started/logic"
+ },
+ {
+ "title": "Imports",
+ "path": "getting-started/imports"
+ },
+ {
+ "title": "Testing",
+ "path": "getting-started/testing"
+ },
+ {
+ "title": "Next Steps",
+ "path": "getting-started/next-steps"
+ }
+ ]
+ }
+]
diff --git a/content/sentinel/v0.25.x/img/sentinel-import-topology.svg b/content/sentinel/v0.25.x/img/sentinel-import-topology.svg
new file mode 100644
index 0000000000..36f7c79bc9
--- /dev/null
+++ b/content/sentinel/v0.25.x/img/sentinel-import-topology.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/content/sentinel/v0.25.x/redirects.jsonc b/content/sentinel/v0.25.x/redirects.jsonc
new file mode 100644
index 0000000000..1c7c77f138
--- /dev/null
+++ b/content/sentinel/v0.25.x/redirects.jsonc
@@ -0,0 +1,7 @@
+[
+ {
+ "source": "/sentinel/commands/config",
+ "destination": "/sentinel/configuration",
+ "permanent": true,
+ },
+]
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/changelog.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/changelog.mdx
new file mode 100644
index 0000000000..190b9b2a59
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/changelog.mdx
@@ -0,0 +1,893 @@
+---
+page_title: Sentinel Runtime Release Notes
+sidebar_current: docs-changelog
+description: >-
+ These are the public release notes for the Sentinel runtime. See this document
+ for the latest updates.
+layout: docs
+---
+
+# Sentinel Runtime Release Notes
+
+These are the release notes for the Sentinel runtime.
+
+Each version of the runtime is released with a corresponding version of
+[Sentinel CLI](/sentinel/commands). To download the CLI, see the [install
+page](/sentinel/install).
+
+Sentinel integrations and embedded runtimes may not always have the latest
+version installed, depending on the product's individual release cycle. For more
+information, contact the support team for your specific integration.
+
+
+## 0.26.3 (July 19, 2024)
+
+ENHANCEMENTS:
+
+- `imports/collection/maps`: Added a `set` helper to the `collection/maps`
+import, providing the ability to set values deeply within a map.
+- `imports/collection/maps`: Added a `pick` helper to the `collection/maps`
+import, providing the ability to select a series of values from a map.
+
+## 0.26.2 (June 19, 2024)
+
+ENHANCEMENTS:
+
+- `imports/collection`: Added a `filter` helper to the `collection` import,
+providing a functional approach to filtering collections.
+
+## 0.26.1 (May 22, 2024)
+
+NOTES:
+
+- This release changes lower-level components and there are no user-facing changes.
+
+## 0.26.0 (May 15, 2024)
+
+FEATURES:
+
+- `imports/collection`, `imports/collection/lists` and `import/collection/maps`:
+Adding new helper imports for dealing with collections (lists, maps), helping
+avoid complexities within policy code.
+- `sentinel/eval`: Fixed an issue with per-policy parameters where parameters
+may leak from one policy into the following policy.
+
+## 0.25.1 (Apr 18, 2024)
+
+BREAKING CHANGES:
+
+- `imports/http`: The default `Accept` header has been changed to `*/*`, removing a redundant
+extension that some servers may not accept by default.
+
+## 0.25.0 (Apr 8, 2024)
+
+ENHANCEMENTS:
+
+- `cmd/apply`: JSON results will return an additional `duration` field for individual policies as well as the evaluation as a whole.
+- `cmd/test`: `sentinel test` will return an additional `duration` field in the JSON output.
+
+BUG FIXES:
+
+- `cmd`: Removed redundant debug logging for custom plugins.
+
+## 0.24.4 (Mar 21, 2024)
+
+ENHANCEMENTS:
+
+- `runtime/format`: Null values will now print correctly for rule value outputs.
+- `config`: Some internal changes to configuration parsing workflow.
+
+## 0.24.3 (Feb 9, 2024)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.24.2 (Jan 31, 2024)
+
+BUG FIXES:
+
+- `imports/static`: Fixed an issue where `nil` values provided to the built-in
+static import were being treated as `undefined` in policy.
+
+## 0.24.1 (Jan 19, 2024)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where the Sentinel cache was unstable due to concurrent
+tests.
+
+## 0.24.0 (Dec 7, 2023)
+
+ENHANCEMENTS:
+
+- `cmd/apply`, `cmd/test`: Custom import plugins can now be fetched from remote
+sources.
+- `cmd/apply`, `cmd/test`: Static imports can now be fetched from remote
+sources.
+- `features/terraform`: Added support for `resource_drift` in the `tfplan/v2` import.
+
+## 0.23.1 (Oct 19, 2023)
+
+BUG FIXES:
+
+- `cmd/apply`: Warnings and errors are included in the JSON output if the json flag is enabled.
+
+## 0.23.0 (Sept 5, 2023)
+
+FEATURES:
+
+- `features/apply_all`: Adds the `apply-all` feature, allowing for all policies to evaluate regardless of result, instead of exiting on first failure.
+
+BUG FIXES:
+
+- `features/terraform`: The `tfplan/v1` import correctly handles nested attribute schemas.
+
+## 0.22.1 (June 22, 2023)
+
+BUG FIXES:
+
+- `sentinel/eval`: Under certain conditions, per-policy parameters would cause
+ Sentinel to panic. This has been resolved.
+
+## 0.22.0 (May 31, 2023)
+
+- `config`: Configuration now supports a `sentinel` block to manage the
+ Sentinel runtime.
+
+## 0.21.1 (May 8, 2023)
+
+BUG FIXES:
+
+- `config`: An issue with certain identifiers being treated as incorrectly invalid
+ has been resolved.
+
+## 0.21.0 (March 8, 2023)
+
+BREAKING CHANGES:
+
+- `lang/ast`: `is empty` and `is not empty` are now treated as `ast.UnaryExpr`
+ expressions, with `ast.IsEmptyExpr` being removed.
+- `lang/ast`: You can now assert if a value is defined or not using the `is defined`
+ and `is not defined` syntax.
+
+FEATURES:
+
+- `config`: Parameter values can now be provided for individual policies within
+ a policy block.
+
+## 0.20.0 (February 16, 2023)
+
+ENHANCEMENTS:
+
+- `cmd/testcmd`: Allows sentinel tests to run concurrently by default. Sequential style testing can
+ be enabled by running with the `-maxConcurrency=1` option.
+- `cmd/testcmd`: Allows sentinel test command to timeout after a certain duration. This can be provided
+ by the user or will default to 5 minutes.
+- `cmd/apply`: The policy enforcement level is now included in the JSON output.
+- `lang/ast`: Functions can now be declared as named statements, providing
+ a safer function declaration.
+
+BREAKING CHANGES:
+
+- `cmd/apply`: Policies provided directly to the apply command will now default their enforcement
+ level to `advisory`, aligning with the `policy` configuration block.
+- `sentinel`: JSON results will no longer return `allowed_failure` or `can_override` fields.
+- `sentinel/result`: A new package has been added which provides additional methods to return
+ supplemental data about the evaluation result.
+
+## 0.19.5 (February 9, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `SelectorExpr` will correctly rewrite `X`.
+
+## 0.19.4 (February 8, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `Compile` function will correctly set the `Original`
+ field when rewriting a `CallExpr` as an `ImportExpr`.
+
+## 0.19.3 (February 3, 2023)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.19.2 (January 17, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `Compile` function will no longer modify `ast.CallExpr`
+ arguments in place.
+
+## 0.19.1 (December 14, 2022)
+
+BUG FIXES:
+
+- `lang/ast`: The `Rewrite` function will no longer modify the provided Node
+ in place.
+
+ENHANCEMENTS:
+
+- `lang/semantic`: The Break semantic check uses the AST walker.
+
+## 0.19.0 (December 6, 2022)
+
+BREAKING CHANGES:
+
+- `config`: Attempts to override a standard import with a custom import will
+ now be met with an error.
+- `imports`: All plugin imports now return `sdk.Plugin` instead of `sdk.Import`,
+ as per the SDK update to v0.4.0.
+
+ENHANCEMENTS:
+
+- `runtime/importer`: An `Import` and `ImportInstance` interface have been
+ added to improve the import capabilities.
+- `runtime/importer`: The `Importer` interface now returns a `Import` interface
+ instead of a `sdk.Import`.
+- `lang/semantic`: The Import Assignment semantic check uses the AST walker.
+- `lang/ast`: Added an AST Walker so the AST rewriter does not need to be used for basic AST tasks.
+
+FEATURES:
+
+- `config`: Configuration syntax now allows for configuration of imports within
+ the standard library.
+- `config`: A new configuration syntax for defining modules and plugins is now
+ available.
+- Static data can now be added via `import` configuration.
+- `cmd/apply`: The `apply` command now supports multiple primary configuration
+ files by default, loading all configuration files within the working
+ directory.
+- `cmd/apply`: The `apply` command now accepts applying configuration overrides
+ to primary configuration.
+
+## 0.18.13 (October 31, 2022)
+
+BUG FIXES:
+
+- `cmd/apply`: `sentinel apply` no longer aborts on advisory policy failure.
+
+## 0.18.12 (September 15, 2022)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where nested `CallExpr` were not rewritten.
+
+## 0.18.11 (June 8, 2022)
+
+NOTES:
+
+- `config`: The `enforcement_level` key for policies is now optional, defaulting to `"advisory"` when not supplied.
+
+## 0.18.10 (May 20, 2022)
+
+NOTES:
+
+- `runtime/initwd`: Improvements to the installer, including timeouts and filesize limits, as well as disabling support
+ for symlinks within `git` or `hg` repositories.
+
+## 0.18.9 (March 23, 2022)
+
+NOTES:
+
+- This release fixes an issue with the Docker container build pipeline. There are no changes to the
+Sentinel runtime or CLI.
+
+## 0.18.8 (March 22, 2022)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue in the installer for Windows paths.
+
+## 0.18.7 (February 24, 2022)
+
+NOTES:
+
+- **Darwin arm64**. This release will be our first to include Darwin arm64 architecture.
+
+BUG FIXES:
+
+- `lang/ast`: Fixed issues where `IndexSpec` and `RuleLit` nodes where returning incorrect end positions.
+- `lang/ast`: Fixed an issue where `ParamSpec` nodes where returning incorrect end positions.
+
+## 0.18.6 (February 2, 2022)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.18.5 (January 14, 2022)
+
+IMPROVEMENTS:
+
+- `runtime/initwd`: Changed the sum algorithm used during remote installation from `md5` to `sha256` to reduce chance of collision.
+
+## 0.18.4 (July 20, 2021)
+
+FEATURES:
+
+- `imports/static`: Improved handling of struct tags when performing encoding, allowing `-` to mark as ignored.
+
+## 0.18.3 (June 1, 2021)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue for remote source installation where duplicate sub-directories resulted in incorrect installation.
+- `imports/static`: Fixed an issue where global configuration that contained an empty map could not be accessed without raising selector issues.
+
+## 0.18.2 (May 18, 2021)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where import expressions inside lists and maps were not being evaluated correctly.
+
+## 0.18.1 (May 11, 2021)
+
+BUG FIXES:
+
+- `imports/json`: Fixed an issue where empty maps where failing when passed at any level to `marshal`.
+
+## 0.18.0 (March 25, 2021)
+
+FEATURES:
+
+- `imports/http`: Added support for POST actions within the `http` import
+ through addition of the `post` function. Additionally, support for adding
+ a body to the `request` object has been added via `with_body`.
+
+## 0.17.4 (February 2, 2021)
+
+BUG FIXES:
+
+- `runtime/format`: Fixed a nesting issue with the rule printer where nesting
+ state leaks were leading to all collection values eventually being truncated,
+ regardless of their nesting level.
+
+## 0.17.3 (January 15, 2021)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue where local file installation failed on Windows
+ when using `sentinel test`.
+
+## 0.17.2 (January 13, 2021)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where duplicate log messages were being printed with
+ `sentinel test`.
+
+## 0.17.1 (January 7, 2021)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where hard-coded path separator caused `sentinel
+ test` issues in Windows.
+
+## 0.17.0 (January 5, 2021)
+
+LANGUAGE CHANGES:
+
+- **Map Expression** `map`. A new quantifier expression, `map` has been added.
+ `map` takes a collection and applies an operation to each element in the
+ collection, returning a list of results with the resulting values.
+- **Emptiness checks** `is empty` and `is not empty`. Two new expressions,
+ `is empty` and `is not empty` have been added. As they are aliases to the
+ `length` built-in, only `string`, `map`, `list` or `undefined` is
+ supported.
+- **Comparable maps** Maps (the data type) are now comparable. Maps are equal if
+ they are of equal length and both their corresponding keys and values are
+ comparable and equal.
+- **Rich Return Types** Rules can now process and return non-boolean data.
+ Scalar, list, and map types are supported. When non-boolean values are used,
+ the presence of a non-zero or non-zero-length `main` rule determines policy
+ failure. More details can be found on
+ https://docs.hashicorp.com/sentinel/language#main.
+
+FEATURES:
+
+- **CLI**: `sentinel apply` and `sentinel test` now have options for JSON
+ output. For more details, see the CLI documentation.
+- `imports/base64`: Added a new import, `base64`, to assist with encoding and
+ decoding base64 strings.
+
+## 0.16.1 (October 21, 2020)
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where `contains` to ensure any instance of `undefined` would correctly result in `undefined`.
+- `runtime/eval`: Fixed an issue in `any` and `all` expressions to ensure correct boolean logic when dealing with a collection containing `undefined`.
+- `imports`: Fixed an issue where import processes were not killed, which caused non-graceful closures for the clients.
+- `lang/scanner`: Fixed an issue where inline `#` style comments were not being consumed.
+
+## 0.16.0 (October 14, 2020)
+
+BREAKING CHANGES:
+
+- `cmd/doc`: Removal of the deprecated `sentinel doc` command.
+- `sentinel`: Replace `whitelist` and `blacklist` with `allowlist` and `denylist`, as per inclusive language changes.
+
+FEATURES:
+
+- `imports/version`: Added a new import, `version`, which supplies helpers for dealing with semantic versioning.
+- `cmd/apply`: Added an `HCL` based configuration file, which will eventually deprecate the legacy `JSON` configuration.
+- `cmd/apply`: Added support for download remote policies and modules.
+- `cmd/test`: Added support for downloading remote test modules.
+- `cmd/apply`: Added support for evaluating a policy based on it's key within the configuration.
+- `cmd/apply`: Added support for running all policies in a configuration by default.
+
+## 0.15.6 (July 7, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.15.5 (May 20, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel integrations. There are no user-facing changes.
+
+## 0.15.4 (May 13, 2020)
+
+BUG FIXES:
+
+- `imports`: Fixed an issue with the standard imports that was affecting the handling of null data within lists.
+
+## 0.15.3 (April 16, 2020)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where `IndexExpr` were not rewritten, as well as ensuring `SelectorExpr` uses any nested rewrites.
+- `lang/printer`: Fixed issue printing deeply nested structures and/or loops.
+
+IMPROVEMENTS:
+
+- `imports/decimal`: Added alias methods to improve consistency of method names within the `decimal` import. The alias methods are `is_nan` for `isnan`, as well as both `is_inf` and `is_infinite` for `isinfinite`.
+- `command/apply`: `sentinel apply` will now output the policy description when a trace is forced via `-trace`.
+- `sentinel`: Improved `String` output formatting of Policy docstring for `EvalPolicyResult`.
+
+## 0.15.2 (April 2, 2020)
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue with the `printer` that was causing a panic
+ when a `#` line comment was used with no text following it.
+- `imports/types`: Fixed an issue with the `types` import where calls to
+ `type_of` for undefined types were incorrectly returning as map types. These
+ will now be correctly be identified as undefined as expected.
+
+## 0.15.1 (March 6, 2020)
+
+BUG FIXES:
+
+- `sentinel`: Fixed how modules are managed internally in the runtime to correct
+ race conditions in concurrent scenarios and to allow for per-evaluation module
+ restrictions. This functionality is only of interest to embedded applications
+ with long-lived runtimes and is currently not implemented in any HashiCorp
+ integration.
+
+## 0.15.0 (March 5, 2020)
+
+NOTES:
+
+- **Windows Digital Signature**. Our windows releases for this version and up will be signed
+ and verified according to Microsoft's requirements.
+
+FEATURES:
+
+- `cmd/config`: Modules can now be loaded off of local disk using the Sentinel
+ CLI. For information on how to do so, see the Sentinel documentation.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: Changed modules so that they now have singleton scope when
+ loaded. Importing a module under two different aliases, or within a nested
+ module, will now share scopes - modification of state in one will alter the
+ other.
+- `lang/object`: Introduced an interface to allow the duplication of various
+ types, like collections.
+- `runtime/eval`: Changed how collection data is returned from modules. This
+ data is now duplicated to prevent accidental or deliberate circumvention of
+ the prohibition on assignment to import data, and brings behavior to parity
+ with binary imports.
+
+## 0.14.4 (February 6, 2020)
+
+IMPROVEMENTS:
+
+- `imports/time`: Changed the `add` timespace function in the `time` import to
+ allow it to take float types as durations. These values will be truncated to
+ the appropriate integer-value duration.
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where out-of-place right-hand braces were being
+ parsed as empty statements, instead of raising syntax errors.
+
+## 0.14.3 (January 22, 2020)
+
+IMPROVEMENTS:
+
+- `cmd/test`: Fail when an assertion is not found in a trace.
+- `cmd/test`: Added a message to notify when no rules were evaluated in a test.
+
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue where import aliases (the `as baz` in `import
+ "foo/bar" as baz`) were being removed when formatting with `sentinel fmt`.
+
+- `imports/http`: Fixed an issue with the http import where the client library's
+ debug logs were being printed to standard error.
+
+## 0.14.2 (January 15, 2020)
+
+NOTES:
+
+With this release, we are discontinuing support for MacOS 32-bit. 64-bit builds
+are now the only builds available for MacOS.
+
+IMPROVEMENTS:
+
+- `lang/printer`: A newline is now added to files formatted via `sentinel fmt`.
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue in printing where multi-line expressions would
+ indent infinitely. They will now only indent once at the most. This is mostly
+ seen when using `sentinel fmt`.
+- `runtime/encoding`: Fixed an issue where if a plugin returned a deeply
+ embedded undefined value, the value was instead decoded into a map.
+
+## 0.14.1 (January 6, 2020)
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where reserved words could not be used as
+ selectors when the selector expression was the last lexeme on a line.
+
+## 0.14.0 (December 18, 2019)
+
+LANGUAGE CHANGES:
+
+- **Filter Expression** `filter`. A new quantifier expression, `filter` has been added.
+ `filter` returns a subset of the provided collection based on a boolean condition that
+ is asserted for each element.
+
+NOTES:
+
+- **Apple Notarization**. Our darwin releases for this version and up will be signed
+ and notarized according to Apple's requirements.
+
+## 0.13.1 (November 25, 2019)
+
+BUG FIXES:
+
+- `runtime/eval`: Parameters are no longer allowed in mock files. Adding one to
+ a mock will result in a runtime error.
+- `runtime/eval`: Fixed an issue where import calls from within mocks were
+ failing under certain circumstances.
+- `imports/decimal`: `int` should now correctly provide the truncated integer
+ representation of a decimal number, not the rounded one.
+
+## 0.13.0 (November 15, 2019)
+
+LANGUAGE CHANGES:
+
+- **Policy Parameters**. The new [policy
+ parameters](https://docs.hashicorp.com/sentinel/language/parameters) feature
+ allows authors to use a `param` declaration at the top of a policy to supply
+ values that are expected to come from outside of a policy. See the linked
+ documentation for more details.
+
+FEATURES:
+
+- `imports/http`: The [`http`
+ import](https://docs.hashicorp.com/sentinel/imports/http) is a new addition
+ to the standard library enabling the use of HTTP-accessible data from outside
+ the runtime in policy rules.
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where loading other imports before a mocked
+ import would cause those imports to no longer be visible from within the
+ policy.
+
+## 0.12.0 (October 7, 2019)
+
+LANGUAGE CHANGES:
+
+- **New `case` statement**. This statement is a selection control mechanism to
+ conditionally execute a branch based on expression equality, allowing
+ simplification of complex conditional chains that may otherwise need to be
+ written with `else if`. See the [Case
+ Statements](https://docs.hashicorp.com/sentinel/language/spec#case-statements)
+ section of the Sentinel language specification for more details.
+
+IMPROVEMENTS:
+
+- `lang/semantic`: Added a semantic check to ensure usage of append is not using
+ a return value.
+
+BUG FIXES:
+
+- `runtime/eval`: Corrected an issue where calling a method on an import object
+ value that was the result of a method call on another import object value
+ would have erroneously tried to call an import of the name of the "parent"
+ import object value. Example: in `a = subject.new(); b = a.call(); c =
+ b.call()`, `b.call()` would attempt to call a method named `call` on the root
+ namespace for an import named `a`. This has now been corrected so that
+ `b.call()` will now correctly call the `call` method for the respective object
+ namespace residing in the import named `subject`.
+- `imports`: Some standard imports may have been returning null for some unknown
+ keys in objects when they should have been returning undefined. This was due
+ to an SDK issue which was corrected in SDK version 0.3.2, which has now been
+ corrected.
+- `cmd/test`: `sentinel test` will now correctly fail a policy if it encounters
+ an error.
+- `cmd/test`: `sentinel test` will now correctly display errors and other output
+ that were missing due to a formatting issue.
+- `runtime/eval`: The `append` builtin now correctly returns undefined for all
+ calls, as called for by the Specification. Note that in most cases, the
+ semantic check outlined above will trigger an error if the return value is
+ used.
+
+## 0.11.0 (September 5, 2019)
+
+LANGUAGE CHANGES:
+
+- **New builtin function:** `range()`. This function existed in the spec in
+ earlier versions but was removed as it lacked an implementation. This has now
+ been implemented and re-added to the spec. See the
+ [Range](https://docs.hashicorp.com/sentinel/language/spec#range) section of
+ the Sentinel Specification for more details.
+- Lists are now comparable. Lists are equal if their corresponding elements are
+ comparable and equal.
+- Method calls on values returned by imports are now supported. See the
+ [Imports](https://docs.hashicorp.com/sentinel/language/spec#imports) section
+ of the Sentinel Specification for more details.
+
+FEATURES:
+
+- `imports/decimal`: This is a new import designed to do exact precision
+ mathematical calculations.
+- `runtime/eval`: Compound call expressions that refer to imports (example:
+ foo.bar().baz() when `foo` is a loaded import) will now function as expected.
+ Previously, this was only supported up to the first call (example:
+ `foo.bar()`).
+- `imports/time`: Timespaces returned by calls such as `time.now` are now
+ callable. Example: `t = time.now; t.after(some_previous_time)` will now
+ function.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: The implementation of comparison of non-comparable types has
+ now changed. Rather than triggering a runtime error, non-comparable types will
+ now return false when attempting to compare.
+
+## 0.10.4 (August 15, 2019)
+
+BUG FIXES:
+
+- `runtime/encoding`: Fixed an issue with conversion of null values that could
+ lead to crashes in imports.
+
+## 0.10.3 (July 15, 2019)
+
+BUG FIXES:
+
+- `command/config`: Parsing a configuration file where malformed data is
+ encountered after apparently properly-formed data will now report an error. An
+ example would be a situation where misplaced braces would cause a JSON object
+ to only parse part of the configuration file.
+
+## 0.10.2 (June 25, 2019)
+
+BUG FIXES:
+
+- `lang/parser`: Corrected an issue where parsing certain compound binary
+ expressions where the negation predicate (`not`) was in use would cause the
+ negation to have no effect. Example: `foo else "bar" not in "baz"` would have
+ been parsed and evaluated as `foo else "bar" in "baz"`, effectively producing
+ the opposite result.
+
+## 0.10.1 (May 9, 2019)
+
+BUG FIXES:
+
+- `command/test`: `sentinel test` now displays policies without
+tests as an unknown result with [no test files], instead of the somewhat
+erroneous behavior of displaying it as a PASS. A full test run with a mixture
+of passing tests and no tests still results in an overall successful result.
+
+## 0.10.0 (April 18, 2019)
+
+LANGUAGE CHANGES:
+
+- Mixed-number arithmetic operations are now allowed. Addition (`+`),
+ subtraction (`-`), multiplication (`*`), division (`/`), and remainder
+ operations (`%`) are no longer restricted to number values of the same
+ type, and can mix integer and floating point. The result of these operations
+ is always a floating-point number.
+- Remainder (modulo, `%`) operations are now allowed on floating-point numbers.
+
+
+BUG FIXES:
+
+- `lang/parser`: Fixed a bug that affected the use of `not` with `contains`,
+ `matches`, or `in` in a compound expression.
+- `runtime/eval`: Fixed error messages on evaluation errors with `contains` and
+ `in` to indicate that strings are an allowed type along with lists and maps.
+
+
+## 0.9.2 (March 15, 2019)
+
+This is a dependency update related to the changes mentioned in 0.9.1. No other
+changes have been made.
+
+## 0.9.1 (March 14, 2019)
+
+This is a patch release that is required to integrate with the latest versions
+of the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). No other
+changes have been made.
+
+## 0.9.0 (January 28, 2019)
+
+FEATURES:
+
+- `imports/strings`: Added a `join` function to the `strings` import. This can
+ be used to join a list into a string with a specific separator.
+ Multi-dimensional lists and all Sentinel primitives are supported.
+
+## 0.8.1 (January 17, 2019)
+
+BUG FIXES:
+
+- `lang/eval`: Fixed a bug that prevents the effective use of `return`
+ statements in `for` loops.
+
+## 0.8.0 (January 14, 2019)
+
+FEATURES:
+
+- Mocks can now be represented by Sentinel code. This allows for the mocking of
+ functions and other complex data structures that cannot be represented in
+ JSON. For more information on using this feature via mocks, click
+ [here](https://docs.hashicorp.com/sentinel/commands/config#mocking-with-sentinel-code).
+
+
+IMPROVEMENTS:
+
+- Import validation has now been moved to the semantic checking phase. This
+ should result in better reporting of validation errors. In addition, import
+ validation will now enforce the use of an `as` identifier when an import path
+ is not a valid identifier on its own (example: `import "foo/bar"`).
+
+## 0.7.0 (December 12, 2018)
+
+BUG FIXES:
+
+- There have been changes to the runtime in how scope is handled over multiple
+ policy executions. Scope is now correctly unique per single policy execution,
+ and values set or builtins that are overridden in one policy will no longer
+ affect those values within another.
+
+## 0.6.0 (November 30, 2018)
+
+FEATURES:
+
+- `imports/runtime`: This new import allows for one to check various aspects of
+ the Sentinel runtime as it may be embedded in the simulator or a specific
+ implementation. For now, it allows the version to be checked.
+
+## 0.5.1 (November 28, 2018)
+
+IMPROVEMENTS:
+
+- `imports/time`: Added the `zone` and `zone_string` attributes to assist with
+ validation of a timespace's zone.
+- `command/fmt`: Added a new -check flag. This option does not commit changes,
+ but instead checks to see what files need formatting and outputs them on
+ stdout.
+
+BUG FIXES:
+
+- `command/test`: Ensure that passing test results are correctly output one per
+ line. Tests are also now run in a deterministic fashion based on
+ lexicographical (alphabetical) order.
+- `imports/time`: `month_name` and `weekday_name` will now show up correctly in
+ a returned timespace result.
+
+
+## 0.5.0 (November 5, 2018)
+
+IMPROVEMENTS:
+
+- `spec`: Selectors can now contain any reserved word (example: `rule`) or
+ keyword operator (example: `any`, `all`, `is`, `not`). This only works for the
+ _selector_ part of the expression (after the first period) - the first primary
+ expression (before the first period) still needs to be an identifier that does
+ not conflict with reserved words.
+
+BUG FIXES:
+
+- The simulator should now display import function call names correctly in
+ import errors.
+
+## 0.4.0 (October 1, 2018)
+
+FEATURES:
+
+- `builtin`: Added the `bool` built-in type conversion function. Booleans will
+ also now accepted as conversion into other values as well, with the full list
+ of behaviors available in the spec.
+
+## 0.3.2 (September 27, 2018)
+
+FEATURES:
+
+- `command/apply`: `sentinel apply` now prints out messages output by the
+ `print()` function when a trace is output on policy failure, or when a trace
+ is forced with `-trace`.
+- `imports/time`: Added the `month_name` and `weekday_name` keys to the
+ timespace, which return full-English names for the month and day of the week.
+
+
+BUG FIXES:
+
+- `command/fmt`: `sentinel fmt -` Will no longer print out the filter status
+ message on the output stream when `-write=false` Is not explicitly stated.
+ This brings the behavior of the command in line with the help text.
+- `runtime`: Index operations on the right-hand-side that have negative indexes
+ that go out of range (example: `length(list) * -1 - 1`) now correctly return
+ `undefined`. left-hand-side index assignments with a out-of-range negative
+ index still return runtime errors.
+
+## 0.3.1 (August 3, 2018)
+
+BUG FIXES:
+
+- `runtime`: Basic index assignment has been implemented as per the spec.
+- `runtime`: Index expressions for lists with negative indexes will no longer panic if the
+ list index is less than `length(list) * -1`.
+
+## 0.3.0 (July 20, 2018)
+
+FEATURES:
+
+- **New standard import: `types`.** This can be used to dynamicaly detect the
+ type of some value.
+
+## 0.2.0 (April 11, 2018)
+
+FEATURES:
+
+- **New standard import: `json`.** Marshal and unmarshal JSON documents and
+ access their contents as native Sentinel values.
+- **`break` and `continue`.** These are now both specified and implemented.
+ `break` allows loop exiting and `continue` allows immediate execution of the
+ next iteration.
+
+IMPROVEMENTS:
+
+- runtime: `print()` map values are now ordered alphabetically by keys.
+
+BUG FIXES:
+
+- command/test: If no `test` block exists, test behaves like it is asserting
+ `main: true`.
+- runtime: default maximum stack depth to 500
+- runtime: `print()` map values now appear like more typical maps.
+- runtime: division by zero is an error, not a crash
+- runtime: plugins that send map values with `null` values now decode properly
+ into native Sentinel values.
+
+## 0.1.0 (September 19, 2017)
+
+Initial release.
+
+
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/commands/apply.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/commands/apply.mdx
new file mode 100644
index 0000000000..c5e81d6d65
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/commands/apply.mdx
@@ -0,0 +1,63 @@
+---
+page_title: 'Command: apply'
+sidebar_current: docs-commands-apply
+description: >-
+ The `sentinel apply` command is used to execute a policy locally for
+ development purposes.
+layout: docs
+---
+
+# Command: `apply`
+
+The `sentinel apply` command is used to execute a policy locally for development
+purposes.
+
+## Usage
+
+Usage: `sentinel apply [options] POLICY`
+
+This command executes the policy file at the path specified by POLICY. In
+addition to a path to a policy, POLICY can reference a label of a
+[policy](/sentinel/configuration#policies) entry in the configuration.
+
+Use the exit code of this command to determine the exact status of the policy
+evaluation. `0` is pass, `1` is fail, `2` is undefined (fail, but because the
+result was undefined), and `3` is a runtime error. Errors unrelated to the
+policy status itself are returned with an exit status of `9`.
+
+To control the behavior of the `apply` command, create a [configuration
+file](/sentinel/configuration). With this, you can define available
+[import plugins](/sentinel/concepts/imports), mock data, and global values.
+This can help you simulate a policy embedded within an application.
+
+As of the 0.16 release, POLICY is optional. When it is not supplied,
+`sentinel apply` will run **all** policies in the supplied configuration.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-config=path` - Path to a configuration file specifying available imports,
+ mock data, globals, etc. The default is `sentinel.[hcl|json]`.
+
+- `-json` Enable JSON output. Using this mode, all other output will be
+ suppressed and the full detail of the apply will be output in a
+ machine-readable format. Enabling this implies `-trace`. See the section on
+ [tracing JSON output](/sentinel/writing/tracing#json-output) for more details.
+
+- `-json-rule=RULE` Enable JSON output, outputting only the value of the
+ specified rule. When running against a policy set, the policy must be supplied
+ to enable per-rule filtering. Implies `-json`.
+
+- `-global key=value` - Set global values. This is the same as setting `global`
+ in the configuration file, and will override any of these respective values
+ set in the configuration. The value is either a string, or a JSON number,
+ array, or object. To force strings, use quotes.
+
+- `-param key=value` - Set parameters, the same as setting `param` in the
+ configuration file. Values are handled in the same way they are with the
+ `-global` flag.
+
+- `-trace` - Always show the execution trace. This shows intermediate
+ boolean expression values. This always shows for failed policies.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/commands/fmt.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/commands/fmt.mdx
new file mode 100644
index 0000000000..8297316d8b
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/commands/fmt.mdx
@@ -0,0 +1,33 @@
+---
+page_title: 'Command: fmt'
+sidebar_current: docs-commands-fmt
+description: The `sentinel fmt` command formats a policy source to a canonical format.
+layout: docs
+---
+
+# Command: `fmt`
+
+The `sentinel fmt` command formats a policy source to a canonical format.
+
+## Usage
+
+Usage: `sentinel fmt [options] FILE ...`
+
+This command formats all the specified policy files to a canonical format.
+
+By default, policy files are overwritten in place. This behavior can be
+changed with the `-write` flag. If a specified FILE is `-` then stdin is
+read and the output is always written to stdout.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-write=true` - Write formatted policy to the named source file. If false,
+ output will go to stdout. If multiple files are specified, the output will
+ be concatenated directly.
+
+- `-check=false` - Don't format, only check if formatting is necessary. Files
+ that require formatting are printed, and a non-zero exit code is returned if
+ changes are required.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/commands/index.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/commands/index.mdx
new file mode 100644
index 0000000000..7177093cec
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/commands/index.mdx
@@ -0,0 +1,23 @@
+---
+page_title: Commands (CLI)
+sidebar_current: docs-commands
+description: >-
+ Sentinel provides a `sentinel` command-line interface (CLI) for developing and
+ testing policies.
+layout: docs
+---
+
+# Sentinel CLI Commands
+
+The Sentinel command-line interface (CLI) allows for the developing and testing
+of policies outside of a particular Sentinel implementation. Having a standard
+workflow to develop policies is critical for our mission of [policy as
+code](/sentinel/concepts/policy-as-code).
+
+The CLI takes a subcommand to execute. The complete list of subcommands is in
+the navigation to the left.
+
+The Sentinel CLI is a well-behaved command line application. In erroneous cases,
+a non-zero exit status will be returned. It also responds to -h and --help as
+you'd expect. To view a list of the available commands at any time, just run
+`sentinel` with no arguments.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/commands/test.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/commands/test.mdx
new file mode 100644
index 0000000000..ed50aacc22
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/commands/test.mdx
@@ -0,0 +1,53 @@
+---
+page_title: 'Command: test'
+sidebar_current: docs-commands-test
+description: The `sentinel test` command runs policies against the Sentinel test framework.
+layout: docs
+---
+
+# Command: `test`
+
+The `sentinel test` command runs policies against the Sentinel test framework.
+
+To learn more about testing, see the [testing
+policies](/sentinel/writing/testing) page.
+
+## Usage
+
+Usage: `sentinel test [options] [PATH] ...`
+
+This command runs the defined test cases against a set of policies.
+
+The test cases are expected to live at the path `test//*.[hcl|json]` relative
+to the policy being tested. `` should be the name of the policy
+excluding the file extension. The files should be in the [configuration
+file structure](/sentinel/configuration). The `test` key is used for
+assertions. Test cases ignore the root level configuration file and must have
+all required configuration provided in each test case.
+
+The PATH arguments specified may be a list of zero or more files or directories.
+When no arguments are given, it defaults to the current directory. When a
+directory is given, all policies ending with the extension `.sentinel` are
+tested.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-json` - Suppress normal output and output test results as a JSON document.
+ See the [testing JSON output](/sentinel/writing/testing#test-json-output)
+ section for more details.
+
+- `-run=regexp` - Run only tests matching the regular expression pattern.
+ A `/` splits policy name and test case name.
+
+- `-verbose` - Show tracing and log output for tests that succeed in addition
+ to tests that fail. By default, traces and logs are only shown for tests that
+ fail.
+
+- `-maxConcurrency` - Allows users to specify the number of tests to be run concurrently.
+ Defaults to the number of logical CPUs if not provided. To run tests sequentially, use `-maxConcurrency=1`
+
+- `-timeout` - Allows users to specify a timeout after which the test command will stop running.
+Defaults to 5 minutes if not provided. The timeout needs to be specified as a Duration type, eg `100ms, 5s etc`
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/concepts/enforcement-levels.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/concepts/enforcement-levels.mdx
new file mode 100644
index 0000000000..fc4b8062d0
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/concepts/enforcement-levels.mdx
@@ -0,0 +1,43 @@
+---
+page_title: Enforcement Levels
+sidebar_current: docs-concepts-levels
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Enforcement Levels
+
+Enforcement levels are a first class concept in Sentinel allowing pass/fail
+behavior to be associated separately from the policy logic. This enables
+any policy to be a warning, allow overrides, or be absolutely mandatory.
+Because this level is not part of the policy body itself, different uses of
+the same policy can have different enforcement levels.
+
+Sentinel has three enforcement levels:
+
+- **Advisory:** The policy is allowed to fail. However, a warning should be
+ shown to the user or logged. Advisory is the default enforcement level.
+
+- **Soft Mandatory:** The policy must pass unless an override is specified.
+ The semantics of "override" are specific to each Sentinel-enabled application.
+ The purpose of this level is to provide a level of privilege separation
+ for a behavior. Additionally, the override provides non-repudiation since
+ at least the primary actor was explicitly overriding a failed policy.
+
+- **Hard Mandatory:** The policy must pass no matter what. The only way to
+ override a hard mandatory policy is to explicitly remove the policy.
+ It should be used in situations where an override is not possible.
+
+## Configuring Enforcement Levels
+
+Enforcement levels are configured when a policy is deployed to a
+Sentinel-enabled application. The exact mechanism that the level is specified
+is determined by each application. Please reference the documentation for
+your Sentinel-enabled application for more information.
+
+Enforcement levels are not configured and are not known by the policy body
+itself. All policies should be written to describe exactly the behavior
+they're attempting to control. For example, a policy that restricts deploys
+to business hours should be written exactly like so. When that policy is
+configured on an application, the operator may specify that it is advisory,
+soft, or hard mandatory.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/concepts/imports.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/concepts/imports.mdx
new file mode 100644
index 0000000000..1306e1512e
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/concepts/imports.mdx
@@ -0,0 +1,32 @@
+---
+page_title: Imports
+sidebar_current: docs-concepts-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom imports](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only
+local context for making policy decisions.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that the
+import calendar allows access to engineer calendars. This import only exists
+for the purpose of this example and at the time of writing is not a real
+available import.
+
+```json sentinel
+{
+ "policy": "import \"calendar\"\n// Get the calendar for Bob for today\nbob_calendar = calendar.of(\"bob\").today\n\n// Allow this policy to pass if Bob is not on vacation.\nmain = rule { not bob_calendar.has_event(\"vacation\") }",
+ "mocks": {
+ "calendar": "has_event = func(event) {\n\treturn true\n}\nof = func(name) {\n\treturn { \"today\": { \"has_event\": has_event } }\n}"
+ }
+}
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/concepts/index.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/concepts/index.mdx
new file mode 100644
index 0000000000..ce7ac51454
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/concepts/index.mdx
@@ -0,0 +1,15 @@
+---
+page_title: Basic Concepts
+sidebar_current: docs-concepts
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Basic Concepts
+
+This section covers some high level basic concepts that are important
+to understand for day to day Sentinel usage. Every page in
+this section is recommended reading for anyone consuming
+or extending Sentinel.
+
+Please use the navigation to the left to learn more about a topic.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/concepts/language.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/concepts/language.mdx
new file mode 100644
index 0000000000..ce91f3eb8f
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/concepts/language.mdx
@@ -0,0 +1,91 @@
+---
+page_title: Policy Language
+sidebar_current: docs-concepts-language
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Policy Language
+
+Sentinel defines and uses its own [policy language](/sentinel/language).
+
+The language was designed to be approachable by non-programmers, since
+there are many use cases where the individual defining policy may not
+be a developer. However, the language includes constructs that
+are familiar to developers to enable powerful policies.
+
+To learn more about the language, please see the
+[writing policy section](/sentinel/writing)
+or the [language reference](/sentinel/language).
+
+An example of the policy language is shown below:
+
+```sentinel playground
+import "time"
+
+# Validate time is between 8 AM and 4 PM
+valid_time = rule { time.now.hour >= 8 and time.now.hour < 16 }
+
+# Validate day is M - Th
+valid_day = rule {
+ time.now.weekday_name in ["Monday", "Tuesday", "Wednesday", "Thursday"]
+}
+
+main = rule { valid_time and valid_day }
+```
+
+## Why?
+
+To explain why Sentinel defines and uses its own language, the question
+can be more easily split into roughly two types of languages: _configuration_ languages
+and _programming_ languages.
+
+### Configuration Languages
+
+Configuration languages are formats such as JSON, YAML, XML, etc. Many applications
+use configuration languages as their ACL format. Configuration languages
+are good for static, declarative information. ACL systems are a good fit
+for this. ACL systems are focused, providing the limiting options necessary
+to restrict access to a system.
+
+Configuration languages are not good for dynamic or logical rules. They
+typically only contain limited conditions, loops, or functions. Further
+configuration is often declarative, which can introduce complexities to
+logical statements that depend on ordering.
+
+The use cases that Sentinel was built for require the ability to perform
+complex behavior that didn't model well into a configuration format or a
+declarative form.
+
+### Programming Languages
+
+The Sentinel language was designed with the following goals:
+
+- **Non-programmer friendly.** Sentinel was built to be used by non-programmers.
+ For the use cases we discovered, non-programmers needed the ability to
+ enforce certain rules within a system. For example, a person responsible for
+ compliance may need to insert rules into a system.
+
+- **Programmer friendly.** At the same time, the language needed to support
+ programmer-friendly constructs such as conditionals, loops, and functions
+ for complex policies that a programmer may be writing.
+
+- **Embeddable.** Sentinel is embedded in existing software. The language
+ itself needs to be easily embeddedable. Further, Sentinel was designed
+ to be embedded in [HashiCorp](https://www.hashicorp.com) software
+ written in Go, so it needed to be easily embeddable in Go.
+
+- **Safe.** The language is used in highly security-sensitive environments.
+ It must not be able to crash its host system or access system resources
+ without explicit approval.
+
+Prior to building our own language, Sentinel evaluated other languages.
+Some languages worked quite well. As we continued to develop Sentinel, we
+found that the flexibility and control over designing our own language
+outweighed the costs.
+
+Because the language itself doesn't need to be general purpose, building
+a language focused on policy proved to be the best route for Sentinel.
+It allows us to build powerful tracing capabilities for debugging, make
+interesting performance choices, and extend the language as needed in the
+future.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/concepts/policy-as-code.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/concepts/policy-as-code.mdx
new file mode 100644
index 0000000000..6dac593e18
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/concepts/policy-as-code.mdx
@@ -0,0 +1,72 @@
+---
+page_title: Policy as Code
+sidebar_current: docs-concepts-pac
+description: >-
+ Policy as code is the idea of writing code in a high-level language to manage
+ and automate policies. By representing policies as code in text files, proven
+ software development best practices can be adopted such as version control,
+ automated testing, and automated deployment.
+layout: docs
+---
+
+# Policy as Code
+
+Policy as code is the idea of writing code in a high-level language to
+manage and automate policies. By representing policies as code in text files,
+proven software development best practices can be adopted such as version
+control, automated testing, and automated deployment.
+
+Many existing policy or ACL systems do not practice policy as code. Many
+policies are set by clicking in a GUI, which isn't easily repeatable nor
+versionable. They usually don't provide any system for testing policies
+other than testing an action that would violate the policy. This makes it
+difficult for automated testing. And the policy language itself varies by
+product.
+
+Sentinel is built around the idea and provides all the benefits of policy as code.
+
+## Benefits
+
+Policy as code provides a number of benefits:
+
+- **Sandboxing.** Policies provide the guardrails for other automated systems.
+ As the number of automated systems grow, there is also a growing need to
+ protect those automated systems from performing dangerous actions. Manual
+ verification is too slow; policies need to be represented as code to
+ keep up with other automated systems.
+
+- **Codification.** By representing policy logic as code, the information
+ and logic about a policy is directly represented in code and can be augmented
+ with comments rather than relying on oral tradition to learn about the
+ reason for policies.
+
+- **Version Control.** Policies are encouraged to be stored as simple text
+ files managed by a version control system. This lets you gain all the
+ benefits of a modern VCS such as history, diffs, pull requests, and more.
+
+- **Testing.** Policies are just code. Their syntax and behavior can be
+ [easily validated with Sentinel](/sentinel/commands/test). This also encourages
+ automated testing such as through a CI. Paired with a VCS system, this
+ allows a pull request workflow to verify that a policy keeps the system
+ behavior as expected before merging.
+
+- **Automation.** With all policies as code in simple text files, various
+ automation tools can be used. For example, it is trivial to create tools
+ to automatically deploy the policies into a system.
+
+## Sentinel and Policy as Code
+
+Sentinel fully embraces policy as code in a number of ways:
+
+- **Language.** All Sentinel policies are written using the
+ [Sentinel language](/sentinel/concepts/language). This language is
+ made to be inputted directly to text files. As an additional benefit,
+ all Sentinel-enabled applications share the same policy language.
+
+- **Development.** Sentinel provides a [CLI](/sentinel/commands)
+ for development and testing. This local CLI can be used to verify policies
+ before deploying them to a system.
+
+- **Testing.** Sentinel provides a [test framework](/sentinel/commands/test)
+ designed specifically for automation. This allows developers and CI systems
+ to further verify policies.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/configuration/index.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/configuration/index.mdx
new file mode 100644
index 0000000000..c7d4a8e3bd
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/configuration/index.mdx
@@ -0,0 +1,422 @@
+---
+page_title: Sentinel CLI Configuration File Syntax
+sidebar_current: docs-configuration
+description: >-
+ The Sentinel CLI's configuration file can be used to control the
+ behavior of the simulator during apply and test operations.
+layout: docs
+---
+
+# Sentinel CLI Configuration File Syntax
+
+The Sentinel CLI's configuration file can be used to control the behavior
+of the simulator during [`apply`](/sentinel/commands/apply) and
+[`test`](/sentinel/commands/test) operations.
+
+## Usage
+
+The configuration file is used in different ways depending on what operation you
+are trying to execute.
+
+### Apply
+
+When using `sentinel apply`, configuration files that have the `.hcl`
+extension are loaded into a single configuration. This allows for configuration
+to be split across multiple files, which is useful for large policy sets. To
+supply a single configuration file, the `-config=FILE` flag can be used, where
+`FILE` is the path to the configuration file. This argument also allows for a
+`.json` configuration file to be supplied. The default is `sentinel.[hcl|json]`.
+
+See the [apply command](/sentinel/commands/apply) reference for more details.
+
+### Test
+
+When using `sentinel test`, each file matching `test//*.[hcl|json]` is a
+configuration file representing a single test case, where `` is the name
+of the policy being tested. Configure assertions for each [test
+case](#test-cases) using the test section.
+
+See the [test command](/sentinel/commands/test) reference for more details.
+
+#### Remote sources
+
+When declaring a `policy` or `module` block within the configuration, a
+`source` attribute must be supplied. This `source` should declare the location
+of the resource, which can be either a local file or a URL pointing to a remote
+file. Examples of local `source` attributes are detailed both in the
+[Policies](#policies) and [Modules](#modules) sections of this page. Below
+are a few examples of remote `source` attributes.
+
+Example:
+
+```hcl
+policy "foo" {
+ source = "git::https://github.com/hashicorp/sentinel-example.git//main.sentinel"
+ enforcement_level = "hard-mandatory"
+}
+```
+
+The above example will fetch the policy from the provided URL and place it into
+the cache ready for use. Be sure to read the
+[Remote Sources](/sentinel/configuration/remote-sources) page for an
+in-depth overview.
+
+## Configuration File Reference
+
+The format of the configuration file is either JSON or HCL. The available
+blocks are:
+
+- [`mock`](#mock-imports) - A mock import specification.
+- [`policy`](#policies) - Configuration for a [policy](/sentinel/writing).
+- [`import`](#imports) - Configuration for a [module](/sentinel/extending/modules), [standard import](/sentinel/imports), [plugin](/sentinel/extending/plugins) or
+ [static import](/sentinel/extending/static-imports).
+- [`global`](#globals) - Data that is inserted into the global scope.
+- [`param`](#parameters) - Values for [parameters](/sentinel/language/parameters) defined in the policy.
+- [`test`](#test-cases) - Test cases for the [test command](/sentinel/commands/test).
+- [`sentinel`](#sentinel) - Configuration to manage the Sentinel runtime
+
+### Mock Imports
+
+Mock imports allow running a policy with an
+[import](/sentinel/concepts/imports) that you may not have access to or is
+too difficult to run. For example, it can be easier to mock data for [Terraform
+Enterprise](https://www.terraform.io/docs/enterprise/sentinel/index.html)
+locally instead of having to run a workspace through a plan/policy check cycle.
+
+Mock imports are specified using the `mock` block, labelled by the import
+name that you want to mock. For example, if you wanted to mock the `time`
+import, you could create an entry with the `time` label pointing to the data
+you want to mock.
+
+The data can take one of two forms:
+
+#### Mocking static data
+
+Static data can be mocked directly via HCl using the `data` attribute on the
+`mock` block.
+
+Example:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ hour = 9
+ minute = 42
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"time\"\n\nmain = rule { time.now.hour is 9 }",
+ "mocks": {
+ "time": "now = { \"hour\": 9, \"minute\": 42 }"
+ }
+}
+```
+
+#### Mocking with Sentinel code
+
+There are some Sentinel types that raw data cannot mock, such as functions,
+and maps that don't have string keys. To mock this data, you can use
+a Sentinel file itself. In this case, you can set the `module` attribute to
+a file with the Sentinel code in it, relative to the current working directory in
+the event of `apply`, or relative to the configuration file location in the
+event of `test`.
+
+Note that `main` is not required in a mock data file.
+
+Example:
+
+```hcl
+mock "foo" {
+ module {
+ source = "mock-foo.sentinel"
+ }
+}
+```
+
+`mock-foo.sentinel` would contain:
+
+```sentinel
+bar = func() {
+ return "baz"
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"foo\"\n\nmain = rule { foo.bar() is \"baz\" }",
+ "mocks": {
+ "foo": "bar = func() { return \"baz\" }"
+ }
+}
+```
+
+### Policies
+
+The `policy` block allows for the the configuration of policies to assist
+with integrating into other commands.
+
+Each `policy` block has a label to identify the policy, with the block
+providing configuration of the policy. The available configuration options for
+policy are `source`, `enforcement_level` and an optional `params` attribute. The
+`source` key provides the location of the policy, while `enforcement_level` is
+currently used by integrations such as [HCP Terraform](/terraform/cloud-docs/policy-enforcement/manage-policy-sets#enforcement-levels).
+For more information on the `source` value, see [Policy and Module Sources](#policy-and-module-sources).
+
+The optional `params` attribute is used to provide values to [parameters](/sentinel/language/parameters)
+defined within the policy file.
+
+```hcl
+policy "foo" {
+ source = "foo.sentinel"
+ enforcement_level = "hard-mandatory"
+ params = {
+ "name" = "Sample"
+ }
+}
+```
+
+If `enforcement_level` is not supplied, it will fallback to the `advisory` level.
+
+### Imports
+
+Import configuration allows you to configure [modules](/sentinel/extending/modules),
+[standard imports](/sentinel/imports) and [import plugins](/sentinel/extending/plugins).
+
+The `import` is a multi-label block, with the first label determining the kind of import
+being configured. Currently the supported values for this label are:
+
+* [`"module"`](#import-modules) for configuring import modules
+* [`"plugin"`](#import-plugins) for configuring standard imports and import plugins
+* [`"static"`](#static-imports) for configuring static data files as imports
+
+#### Import Modules
+
+The `import "module"` block allows you to map a Sentinel import to a
+[module](/sentinel/extending/modules). The Sentinel CLI will parse the module
+source and make it available for evaluation with the policy.
+
+Each block has a label aligning to the name of the import, with
+the block set to the configuration object for the module. Currently, the only
+value within the configuration object is `source`, which points to the
+location of the module. For more information on the `source` value, see
+[Policy and Module Sources](#policy-and-module-sources).
+
+```hcl
+import "module" "foo" {
+ source = "modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "modules/bar.sentinel"
+}
+```
+
+Note when using [`sentinel test`](/sentinel/commands/test), module paths must be
+specified relative to the location of the configuration file.
+
+```hcl
+import "module" "foo" {
+ source = "../../modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "../../modules/bar/sentinel"
+}
+```
+
+See [Writing and Using a
+Module](/sentinel/extending/modules#writing-and-using-a-module) for more details
+on using a module with this configuration.
+
+### Import Plugins
+
+Import `"plugin"` configuration allows you to configure both [standard imports](/sentinel/imports)
+as well as [import plugins](/sentinel/extending/plugins) that can be used within
+a policy. If defining a custom import plugin, the Sentinel CLI will launch the
+plugin, connect to it, configure it, and execute it as needed by the policy.
+
+The available configuration attributes for an `import "plugin"` are:
+
+- `source` (string, required) - Path to the import plugin executable.
+- `args` (list of string, optional) - A list of arguments to pass to the
+ executable when starting it.
+- `env` (map of string to string, optional) - A set of environmental variables
+ to set when launching the plugin.
+- `config` (map, optional) - Configuration for the plugin itself. This is
+ specific to each individual plugin. Please reference the documentation for
+ the plugin for more information.
+
+Standard import example:
+
+```hcl
+import "plugin" "time" {
+ config = { "timezone": "Australia/Brisbane" }
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"time\"\n\nmain = time.now.zone_string is \"+10:00\"",
+ "mocks": {
+ "time": "now = { \"zone_string\": \"+10:00\" }"
+ }
+}
+```
+
+Custom plugin example:
+
+```hcl
+# flipper receives a boolean and returns the opposite
+import "plugin" "flipper" {
+ source = "/path/to/flipper"
+}
+```
+
+```json sentinel
+{
+ "policy": "import \"flipper\"\n\nmain = flipper.flip(false)",
+ "mocks": {
+ "flipper": "flip = func(input) { return not input }"
+ }
+}
+```
+
+#### Static Imports
+
+Static import configuration allows you to supply a data file and make
+it available to a policy via an import statement.
+
+The available configuration attributes for an `import "static"` are:
+
+- `source` (string, required) - Path to the static data file.
+- `format` (string, required) - The format of the static data. Currently, only
+ the "json" value is supported.
+
+Static import example:
+
+```hcl
+import "static" "people" {
+ source = "./data/people.json"
+ format = "json"
+}
+```
+
+```json sentinel
+{
+ "policy": "import \"people\"\n\nmain = length(people.names) > 0",
+ "mocks": {
+ "people": "names = [\"John Smith\", \"Jane Smith\"]"
+ }
+}
+```
+
+### Globals
+
+Global data is injected directly into the global scope of the policy.
+This can be used to simulate global data that may be injected by a
+Sentinel-enabled application.
+
+Global data is specified by the `global` key. The value is a map of
+variables to inject directly into the running policy.
+
+Example:
+
+```hcl
+global "time" {
+ value = {
+ now = {
+ day = 31
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass. Notice
+that we don't have to do any `import`. The values of `global` are
+injected directly into the global scope.
+
+```json sentinel
+{
+ "policy": "main = time.now.day == 31",
+ "globals": {
+ "time": {
+ "now": {
+ "day": 31
+ }
+ }
+ }
+}
+```
+
+### Parameters
+
+The `param` section allows you to supply values for the
+[parameters](/sentinel/language/parameters) found in a policy. Values
+entered here will satisfy required parameters in a policy, in addition to
+override any defaults. Note that any of the manual parameter value methods
+supported by `sentinel apply` will override the values set here.
+
+```hcl
+param "foo" {
+ value = "bar"
+}
+```
+
+### Test Cases
+
+Test cases specify the test cases for the [test command](/sentinel/commands/test).
+
+Tests are specified by the `test` key. The value of this is a map of
+string to boolean. The key is the name of a rule and the value is the
+expected value of that rule. If a rule is not specified, than any value is
+allowed.
+
+Example:
+
+```hcl
+test {
+ rules = {
+ main = true
+ days = []
+ }
+}
+```
+
+For the policy:
+
+```json sentinel
+{
+ "policy": "valid_days = rule {\n\tfilter days as d {\n\td is \"monday\"\n\t}\n}\n\nmain = rule { valid_days is empty }",
+ "globals": {
+ "days": []
+ }
+}
+```
+
+For more information, read about the [test command](/sentinel/commands/test).
+
+### Sentinel
+
+The `sentinel` block provides configuration specific to the Sentinel runtime.
+
+An example of how the `sentinel` block can be used to enable the [terraform](/sentinel/features/terraform)
+feature is as follows:
+
+```hcl
+sentinel {
+ features = {
+ terraform = true
+ }
+}
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/configuration/overrides.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/configuration/overrides.mdx
new file mode 100644
index 0000000000..ca1a6842b1
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/configuration/overrides.mdx
@@ -0,0 +1,80 @@
+---
+page_title: Override Files
+sidebar_current: docs-configuration-overrides
+description: >-
+ The Sentinel CLI's configuration can be overriden using override files.
+ Override files merge additional settings into existing configuration.
+layout: docs
+---
+
+# Override Files
+
+As outlined in the configuration [overview](/sentinel/configuration#Apply),
+the normal behavior of Sentinel is to load all `.hcl` files into a single
+configuration object.
+
+In addition to appending multiple configuration files, Sentinel allows for the
+rare case of overriding configuration through override files. Override files
+are any files that match either `_override.{hcl,json}` or `override.{hcl,json}`.
+
+Sentinel will parse override files after it has loaded the primary
+configuration, and then process each override file in turn (in lexicographical
+order). All top level blocks other than [test](/sentinel/configuration#test-cases)
+require the block to already be defined in the primary configuration. It will
+then attempt to merge the override block into the existing configuration.
+
+## Example
+
+If you had a Sentinel configuration `sentinel.hcl` that contained the following:
+
+```hcl
+policy "main" {
+ source = "./main.sentinel"
+ enforcement_level = "advisory"
+}
+```
+
+And you created a file `override.hcl` that contained the following:
+
+```hcl
+policy "main" {
+ enforcement_level = "soft-mandatory"
+}
+```
+
+Sentinel will merge the contents of the override into the former, providing the
+following configuration:
+
+```hcl
+policy "main" {
+ source = "./main.sentinel"
+ enforcement_level = "soft-mandatory"
+}
+```
+
+## Merging Behavior
+
+The general rule, which applies in most cases, is:
+
+- A top-level block in an override file merges with a block in a normal
+ configuration file that has the same block header. The block header is the
+ block type and any quoted labels that follow it.
+- Within a top-level block, an attribute argument within an override block
+ replaces any argument of the same name in the original block.
+- Within a top-level block, any nested blocks within an override block replace
+ all blocks of the same type in the original block. Any block types that do not
+ appear in the override block remain from the original block.
+- The contents of nested configuration blocks are not merged.
+- The resulting merged block must still comply with any validation rules that
+ apply to the given block type.
+
+If more than one override file defines the same top-level block, the overriding
+effect is compounded, with later blocks taking precedence over earlier blocks.
+Overrides are processed in order first by filename (in lexicographical order)
+and then by position in each file.
+
+## Required Attributes
+
+It is important to note that all attributes inside an override file are
+considered to be optional, meaning that an override file can itself fail
+validation rules for block types.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/configuration/remote-sources.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/configuration/remote-sources.mdx
new file mode 100644
index 0000000000..b64e4d3abc
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/configuration/remote-sources.mdx
@@ -0,0 +1,165 @@
+---
+page_title: Remote Sources
+sidebar_current: docs-configuration-remote-sources
+description: >-
+ There are a number of options for formatting the URL provided to the
+ source attribute on policy and module blocks.
+layout: docs
+---
+
+# Remote Sources
+
+There are a number of options for formatting the URL provided to the
+`source` attribute on `policy` and `import "module"` blocks. This flexibility
+should solve most use cases and allow for reusable policies and modules.
+
+### Supported Protocols
+
+-> **NOTE:** All protocols are supported on the CLI, however each production
+Sentinel integration may not. Be sure to read the appropriate integration
+documentation to check the supported protocols.
+
+- Local filesystem
+- Git
+- Mercurial
+- HTTP
+- Amazon S3
+- Google GCP
+
+### Forced Protocols
+
+Due to the ambiguous nature of URLs, it is possible to force a particular
+protocol to be used during the fetch. To achieve this, simply prefix the url
+with the appropriate protocol as listed below:
+
+- `file` - Local filesystem
+- `git` - Git
+- `hg` - Mercurial
+- `http` or `https` - HTTP
+- `s3` - Amazon S3
+- `gcp` - Google GCP
+
+Example:
+
+```
+git::http://github.com/hashicorp/sentinel.git
+```
+
+The above would download the provided HTTP URL using the Git protocol.
+
+### Protocol Options
+
+In addition to some global options, there are options available to specific
+protocols to perform features such as authentication.
+
+#### Subdirectories / File Pathing
+
+When supplying URLs that point to a directory (eg. `git`), a subdirectory and
+file can be provided by suffixing the URL with `//` and the path. For example,
+if we have a git repository that has a policy, `main.sentinel` in the root, we
+could directly fetch the file by using the following:
+
+```
+git::https://github.com/hashicorp/sentinel-docs-example.git//main.sentinel
+```
+
+Or, if the file was nested in the `policies` folder:
+
+```
+git::https://github.com/hashicorp/sentinel-docs-example.git//policies/main.sentinel
+```
+
+#### Checksumming
+
+For downloads of any protocol, you can automatically verify a checksum. To
+checksum a file, append a `checksum` query parameter to the URL. The parameter
+value can be in the format of type:value or just value, where type is "md5",
+"sha1", "sha256", "sha512" or "file" . The "value" should be the actual
+checksum value or download URL for "file". When type part is omitted, type will
+be guessed based on the length of the checksum string.
+
+```
+./foo.sentinel?checksum=md5:b7d96c89d09d9e204f5fedc4d5d55b21
+
+./foo.sentinel?checksum=b7d96c89d09d9e204f5fedc4d5d55b21
+
+./foo.sentinel?checksum=file:./foo.txt.sha256sum
+```
+
+#### Unarchiving
+
+An `archive` query parameter can be supplied to explicitly state what format
+to attempt to extract, if required. The supported formats are:
+
+- `tar.gz` and `tgz`
+- `tar.bz2` and `tbz2`
+- `tar.xz` and `txz`
+- `zip`
+- `gz`
+- `bz2`
+- `xz`
+
+If a URL has an extension that matches one of the supported formats, it will
+use that format to unarchive.
+
+```
+./file.zip
+```
+
+The above would use the `zip` format to unarchive.
+
+```
+./path/to/file?archive=zip
+```
+
+Would force the `zip` format. If for some reason you required archiving to be
+disabled, this can also be achieved by using the `false` value.
+
+```
+./path/to/file?archive=false
+```
+
+#### Git (`git`)
+
+- `ref` - The Git ref to checkout. This is a ref, so it can point to a commit
+ SHA, a branch name, etc. If it is a named ref such as a branch name, it will
+ be updated to the latest on each get.
+- `sshkey` - An SSH private key to use during clones. The provided key must be
+ a base64-encoded string. For example, to generate a suitable sshkey from a
+ private key file on disk, you would run base64 -w0 \.
+ **Note:** Git 2.3+ is required to use this feature.
+- `depth` - The Git clone depth. The provided number specifies the last `n`
+ revisions to clone from the repository.
+
+The git getter accepts both URL-style SSH addresses like
+`git::ssh://git@example.com/foo/bar`, and "scp-style" addresses like
+`git::git@example.com/foo/bar`. In the latter case, omitting the `git::` forced
+prefix is allowed if the username prefix is exactly git@.
+
+The "scp-style" addresses cannot be used in conjunction with the `ssh://`
+scheme prefix, because in that case the colon is used to mark an optional port
+number to connect on, rather than to delimit the path from the host.
+
+#### Mercurial (`hg`)
+
+- `rev` - The Mercurial revision to checkout.
+
+#### HTTP (`http` or `https`)
+
+Basic Authentication
+
+To use HTTP basic authentication, simply prepend `username:password@` to the
+hostname in the URL such as `https://Aladdin:OpenSesame@www.example.com/index.html`.
+All special characters, including the username and password, must be URL
+encoded.
+
+#### S3 (`s3`)
+
+The following query parameters are available and will take priority when
+authenticating against an S3 bucket.
+
+- `aws_access_key_id` - AWS access key.
+- `aws_access_key_secret` - AWS access key secret.
+- `aws_access_token` - AWS access token if this is being used.
+- `aws_profile` - Use this profile from local ~/.aws/ config. Takes priority
+ over the other three.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/consul.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/consul.mdx
new file mode 100644
index 0000000000..7a3aaaa697
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/consul.mdx
@@ -0,0 +1,47 @@
+---
+page_title: Consul and Sentinel
+sidebar_current: docs-app-consul
+description: >-
+ Consul Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies are applied during writes to
+ the KV Store.
+layout: docs
+---
+
+# Consul
+
+[Consul Enterprise](https://www.hashicorp.com/products/consul/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies are applied during writes to the KV Store.
+
+Sentinel policies have access to the key/value being written. They can be used to
+allow or deny the modification. The information that Sentinel policies have access
+to will expand over time.
+
+The Consul integration with Sentinel is documented in depth in the
+[Consul Enterprise documentation](https://www.consul.io/docs/guides/sentinel.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Input validation depending on the name of the key.**
+
+```sentinel
+main = rule { valid_key() }
+
+required = [
+ ["port", "\\d+"], # ports must be integers
+ ["name", "\\w+"], # name must be a word
+]
+
+valid_key = func() {
+ for required as v {
+ if key is v[0] {
+ return value matches v[1]
+ }
+ }
+
+ return false
+}
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/extending/index.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/extending/index.mdx
new file mode 100644
index 0000000000..a39cb0384d
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/extending/index.mdx
@@ -0,0 +1,52 @@
+---
+page_title: Extending Sentinel
+sidebar_current: docs-extending
+description: Learn how to create your own Sentinel imports to extend Sentinel's functionality.
+layout: docs
+---
+
+# Extending Sentinel
+
+-> **NOTE:** Sentinel's extensibility is currently undergoing large
+improvements - watch this space for future updates!
+
+Sentinel can be extended by writing additional
+[imports](/sentinel/language/imports). These imports can bring new functionality
+to Sentinel by allowing access to new data or by the addition of new functions.
+This section is designed to show you how to accomplish this extensibility and
+describe how it works.
+
+Currently, the only usable way to add additional imports is via
+[modules](#modules). While [plugins](#plugins) currently work under the Sentinel
+CLI, no Sentinel integration currently supports their use.
+
+-> You may also be interested in advanced details on [import
+internals](/sentinel/extending/internals).
+
+## Modules
+
+Modules allow you to re-use Sentinel code as an import. Modules are loaded
+external of policies and mapped to imports. This is usually configured via a
+file such as the [CLI configuration file](/sentinel/configuration).
+
+See the [modules](/sentinel/extending/modules) section for more details.
+
+## Plugins
+
+-> **NOTE:** Plugins are currently only supported on the CLI and not in any
+production Sentinel integration, with the exception of existing configuration
+in [Nomad](https://www.nomadproject.io/docs/configuration/sentinel).
+
+Imports can also be implemented as plugins when Sentinel code itself is too
+restrictive to represent the import, or if you need access to features not
+normally available to the Sentinel runtime.
+
+See the [plugins](/sentinel/extending/plugins) section for more details.
+
+## Static Imports
+
+Static imports provide support for loading data files into the Sentinel runtime,
+making them available as an import.
+
+See the [static imports](/sentinel/extending/static-imports) section for more
+details.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/extending/internals.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/extending/internals.mdx
new file mode 100644
index 0000000000..7133e6df1b
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/extending/internals.mdx
@@ -0,0 +1,252 @@
+---
+page_title: >-
+ Extending Sentinel: Internals
+sidebar_current: docs-extending-internals
+description: This Section covers some details about Sentinel's import internals.
+layout: docs
+---
+
+# Extending Sentinel: Internals
+
+-> **Advanced Topic!** This page covers low-level technical details of Sentinel.
+You don't need to understand these details to effectively use Sentinel. The
+details are documented here for those who wish to learn about them.
+
+This section covers some of the internal details of Sentinel's import system.
+The goal of this section is to help remove notion of "magic" from Sentinel, to
+allow you to trust and understand what Sentinel is doing with imports, and also
+to help practitioners and developers alike understand the differences between
+the ways that imports can be loaded into Sentinel.
+
+## Language and Runtime
+
+Understanding the import system in Sentinel generally requires understanding of
+two key concepts within Sentinel: the _language_, and the _runtime
+implementation_ of the language.
+
+The Sentinel language and the rules governing it are detailed in the [Sentinel
+Language Specification](/sentinel/language/spec). A runtime implementation must
+conform to the rules laid out in the specification at a minimum. However, to
+meet the design goals of a particular implementation, the runtime may implement
+features on top of the language. The subtle implementation details within the
+runtime may also vary while still being compliant with the specification.
+
+The core runtime included within the [Sentinel CLI](/sentinel/commands) is
+sometimes referred to as the _reference implementation_ of the Sentinel
+language. This runtime is also the main runtime embedded within each
+Sentinel-enabled HashiCorp product such as HCP Terraform, Terraform Enterprise, Vault
+Enterprise, Nomad Enterprise, and Consul Enterprise. The runtime includes an API
+to allow these integrations implement Sentinel in as robust or as restrictive of
+a way as possible, so depending on the implementation, your experience may vary.
+
+## Sentinel's Import Model
+
+The concept of Imports within Sentinel is a _language_ feature. You can see the
+exact rules governing imports within the
+[Imports](/sentinel/language/spec#imports) section of the specification.
+
+Within the runtime, there are currently two major classifications of imports:
+
+- **Modules:** The mapping of Sentinel code to an import, essentially mapping a
+ scope of values to a particular package.
+- **Binary Imports:** A grouping of embedded and external plugins written using
+ the Sentinel SDK. These are comprised of internal or embedded imports (usually
+ the [standard imports](/sentinel/imports) and imports included by an
+ integration) and import plugins.
+
+Below is a diagram explaining in brief the relationship between the langauge
+and runtime features.
+
+
+
+This kind of topology ultimately minimizes the visibility of implementation
+internals to the actual policy language. This allows us to keep the Sentinel
+language itself simple and keep concerns such as module or plugin management,
+validation, and configuration out of the language. These kinds of things would
+impact the readability of a policy, possibly present security challenges, and
+would ultimately be of no use to more minimal embedded applications.
+
+### Implementation Differences Between Import Types
+
+As one would imagine, both modules and binary imports are loaded in much
+different ways, and the methods that they expose data to Sentinel differ as
+well. The effect is generally the same however across both, and we take care to
+ensure that both modules and binary imports behave as close as possible to each
+other, however there are some subtle differences:
+
+- Modules currently support the exporting of rules, but do not support function
+ calls with object receiver data (as seen in some standard imports such as
+ [`decimal`](/sentinel/imports/decimal), [`http`](/sentinel/imports/http), and
+ [`time`](/sentinel/imports/time)). This will be coming in a later release.
+- Binary imports cannot export rules. However, they do support object receiver
+ data (see [`framework.New`](/sentinel/extending/plugins#framework-new) in
+ [Extending Sentinel: Plugins](/sentinel/extending/plugins)).
+
+## Modules
+
+_Modules_ are essentially Sentinel code that is intended to be re-used in multiple
+policies as an import.
+
+The mechanism of module loading is fairly straightforward: during initialization
+of the Sentinel runtime, modules are parsed along with policies. The parsed
+_abstract syntax tree_ (AST) for each module is loaded into the runtime under
+the specified _import path_. These are then passed to the lower-level
+interpreter during the evaluation of each policy.
+
+As with policy code, during evaluation, a module's AST is walked to execute the
+code within that specific module. The module data is then stored within an
+specific scope mapped to the import name. The module data can then be accessed
+through the import via selector expressions and function calls (e.g.,
+`foo.some_map` or `foo.some_func()` for a module loaded via `import "foo"`).
+
+The AST for a module is only walked if a policy imports it, and it is _only
+walked once_. That means if a policy and a module import the same module, or a
+policy imports the same module twice (using [import
+aliases](/sentinel/language/imports#aliases)), those instances will share state.
+If you call a function that alters a singleton variable within the module, that
+singleton will be modified for all instances of the import.
+
+-> **Fun fact!** When you [mock an import with Sentinel
+code](/sentinel/configuration#mocking-with-sentinel-code), you are actually
+using a module. The module is loaded with a flag that allows it to override an
+existing import, which would normally give a runtime error.
+
+### Map and List Cloning for Module Calls
+
+It's also worthwhile noting that map and list values are cloned for modules.
+
+Consider the case where you have a module with a map singleton.
+
+```sentinel
+// modules/foo.sentinel
+a_map = {"a": "b"}
+```
+
+Normally, assignment to the singleton, even when using an index expression, is
+blocked, so `foo.a_map["c"] = "d"` would not pass semantic checking.
+
+However, even when you try to do assignment via an intermediary, you will still
+be only modifying the copy, and the original will not be affected:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+a_map_copy = foo.a_map
+a_map_copy["c"] = "d" // Only modifies copy, does not modify module singleton
+print(foo.a_map) // Still only prints {"a": "b"}
+```
+
+This is to ensure that modules are consistent with binary imports in how return
+data is handled, and the expectation that most non-object data within an import
+is read-only, with the exception of modification of singleton data via
+functions. As return of complex object and collection data in a binary import is
+always via copy, it's not possible to modify any value elements within the
+import in a similar fashion.
+
+There are also some other implications of this cloning that are of note:
+
+- Rules are not cloned, either within a list or map, or by themselves. This is
+ to ensure that [memoization](/sentinel/language/rules#lazy-and-memoized)
+ functions correctly. As only a module can export rules at this time, there is no
+ parity for this in binary imports.
+- Functions are _not_ cloned. This mainly has implications for scope - for
+ example, if you assign a function to another value, or assign an object to a
+ value that has a method that utilizes a global module variable, those calls will
+ reference and/or modify those values.
+
+Functions, while represented differently in binary imports, generally follow the
+same logic here - as they would be invoked in the same package within the binary
+import, any singleton values they accessed there would be affected in a similar
+fashion. As rules are pseudo-functions, this generally makes sense here too.
+
+## Binary Imports
+
+_Binary imports_ is a grouping referring to all imports that are designed with
+the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). These can be
+either internally embedded, or executed via a plugin.
+
+All imports found in the [standard library](/sentinel/imports) are binary
+imports, embedded internally.
+
+The main difference between internally embedded imports and external plugins is
+whether or not the runtime needs to execute a plugin binary as part of
+[lifecycle management](#lifecycle), and whether or not it needs to
+[communicate](#communication) over RPC. Internal binary imports do not require
+these steps, so their execution model is somewhat simpler; however, technically,
+they still are the same as external plugins in terms of design model and value
+conversion. Most standard imports are even tested using the [SDK test
+suite](/sentinel/extending/plugins#testing), which builds the import as an
+external plugin.
+
+The remainder of this section discusses topics mostly relevant to external
+plugins. A large amount of technical detail regarding binary import development
+(also applicable to embedded binary imports) can be seen on the
+[Plugins](/sentinel/extending/plugins) page.
+
+-> **NOTE:** At this time, plugins can only be written for the Sentinel CLI, and
+cannot be used with any of Sentinel's integrations.
+
+### Plugin Basics
+
+Sentinel plugins are built on top of the HashiCorp [go-plugin
+system](https://github.com/hashicorp/go-plugin). This is the same plugin system
+powering all pluggable HashiCorp tools such as
+[Terraform](https://www.terraform.io), [Vault](https://www.vaultproject.io), and
+more. go-plugin is a system that has been used in production for millions of
+users for over 5 years.
+
+Plugins are executable binaries. Sentinel is responsible for launching and
+managing the lifecycle of plugins. Once a plugin is running, Sentinel
+communicates with the plugin via [gRPC](https://grpc.io/). The [protocol is open
+source](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/import.proto)
+to allow anyone to write a Sentinel plugin.
+
+### Lifecycle
+
+Sentinel launches plugins and is responsible for plugin lifecycle.
+
+The runtime optimizes for latency by launching the plugin as soon as it is
+configured, rather than when a policy requires it. This ensures that the plugin
+is ready to be used immediately. A plugin is only closed when Sentinel is
+reconfigured to no longer allow that plugin or if the Sentinel-enabled
+application is closing.
+
+When a plugin is removed from the configuration, Sentinel may wait to shut down
+the plugin until all currently executing policies that are using that plugin
+complete. New policy executions will not be allowed to use the old plugins.
+
+Plugins are automatically restarted if they shut down unexpectedly. Policies
+that were executing while this happens may fail. The Sentinel system is
+improving to more gracefully understand locations where it is safe to retry an
+execution.
+
+### Communication
+
+An [initial
+handshake](https://github.com/hashicorp/go-plugin/blob/master/docs/internals.md)
+is done via stdout to determine the main communication location. Following the
+handshake, communication will either occur on a local Unix domain socket or via
+a local-only TCP socket. This communication is done mostly over the low-level
+[`Get`](https://github.com/hashicorp/sentinel-sdk/blob/2b4db07dd12b55142f0c016f301af80aa4422520/proto/import.proto#L12)
+RPC call.
+
+#### Value Conversion
+
+As can generally be seen in [Developing an Import
+Plugin](/sentinel/extending/plugins#developing-an-import-plugin), the Sentinel
+type system is not exposed to binary imports. While explicit values for null and
+undefined exist, values are mostly converted over the wire from their Go values
+to their Sentinel equivalents.
+
+This has a few implications:
+
+- As discussed in [Map and List Cloning for Module
+ Calls](#map-and-list-cloning-for-module-calls), Map and list data returned from
+ binary import value lookups or function calls is always cloned. These can be
+ freely modified without affecting anything within the binary import.
+- It's currently impossible to represent certain Sentinel types such as
+ [rules](/sentinel/language/rules) within a binary import. This is not a major
+ issue at this point in time as practitioners generally do not look to binary
+ imports for rules; we may examine this as a later time if this situation
+ changes.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/extending/modules.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/extending/modules.mdx
new file mode 100644
index 0000000000..9846d263e5
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/extending/modules.mdx
@@ -0,0 +1,135 @@
+---
+page_title: >-
+ Extending Sentinel: Modules
+sidebar_current: docs-extending-modules
+description: >-
+ Modules allow you to re-use Sentinel code as an import.
+layout: docs
+---
+
+# Extending Sentinel: Modules
+
+Modules allow you to re-use Sentinel code as an import. This allows for the
+export of any general construct that Sentinel supports, including values,
+functions, and even rules. As such, it's a great way to package code that is
+useful across multiple policies, reducing boilerplate and making final policy
+code simpler.
+
+You may want to use modules for:
+
+- **Writing a simple re-usable helper library.** If you mostly know Sentinel or
+ have helpers that you have written in Sentinel, moving these helpers to a module
+ will offer a low-friction way of facilitating their re-use.
+- **Writing re-usable rules.** If you have a set of
+ [rules](/sentinel/language/rules) you know you want to use across multiple
+ policies, bundling them into a module is a great way of abstracting the logic
+ away.
+- **Abstracting existing Sentinel imports.** Using a module is a great way to
+ extend existing Sentinel imports by abstracting the common functionality that
+ you use within an import to your own workflow.
+
+This page describes how you can use modules within the context of the Sentinel
+CLI. For instructions for a particular integration, see the documentation for
+that product.
+
+-> **Not all Sentinel-enabled applications support modules.** Please refer
+to the documentation of the specific Sentinel-enabled application. Some
+applications disable modules for security reasons.
+
+## Configuring a Module
+
+A module is configured within the Sentinel CLI by adding an entry for it in
+within the `imports` section of the [CLI configuration
+file](/sentinel/configuration). The key for each entry specifies the path that
+the module is imported as.
+
+```hcl
+import "module" "foo" {
+ source = "modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "modules/bar.sentinel"
+}
+```
+
+In this example, we have two modules:
+
+- Import path `foo`, being loaded from the code within `modules/foo.sentinel`.
+- Import path `bar`, being loaded from the code within `modules/bar.sentinel`.
+
+See the [Import Modules](/sentinel/configuration#import-modules) section within
+the CLI configuration file syntax page for more details.
+
+### Remote Modules
+
+In addition to loading from a local source, modules can be loaded from a remote
+location. This can increase reusability and allow for sharing modules across
+multiple configurations.
+
+### Testing Using Modules
+
+As with [mocks](/sentinel/configuration#mocking-with-sentinel-code), modules
+are loaded relative to the configuration file location when using `sentinel test`. As such, you need to account for this in your test configuration files:
+
+```hcl
+import "module" "foo" {
+ source = "../../modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "../../modules/bar.sentinel"
+}
+```
+
+This could be a test file for a policy (example: `policy.sentinel`), residing
+under the correct test file directory for this policy (example:
+`test/policy/pass.json`).
+
+## Writing and Using a Module
+
+Writing a module is nearly the same as writing a Sentinel policy, except for the
+following two exceptions:
+
+- Modules do not require [`main`](/sentinel/writing/basic#the-simplest-policy)
+ to be present. It can be, but it will not carry any extra significance as it
+ does within a policy.
+- [`param`](/sentinel/language/parameters) declarations cannot be present in a module. If they are encountered,
+ a runtime error is given.
+
+Once you have a complete module ready for use and configured, using the module
+is as simple as importing it.
+
+Building on the above [configuration example](#configuring-a-module), we can
+export a simple test function in the module `foo` by adding this code to
+`modules/foo.sentinel`:
+
+```sentinel
+// modules/foo.sentinel
+hello = func() {
+ print("hello world!")
+ return undefined
+}
+```
+
+We can then import it within our policy and use it:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+// prints "hello world" in the trace
+foo.hello()
+
+main = true
+```
+
+Note that modules are considered [imports](/sentinel/language/spec#imports) by
+the Sentinel runtime and as such conform to the specification. This means that
+you cannot directly assign values to anything behind a module scope. You can,
+however, use functions to change state.
+
+-> **NOTE:** At this time, modules do not support object receiver data in the
+way that some imports do, like [`time`](/sentinel/imports/time),
+[`decimal`](/sentinel/imports/decimal), and [`http`](/sentinel/imports/http).
+Support for this will be coming in later versions of Sentinel.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/extending/plugins.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/extending/plugins.mdx
new file mode 100644
index 0000000000..e80de5d4b1
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/extending/plugins.mdx
@@ -0,0 +1,521 @@
+---
+page_title: >-
+ Extending Sentinel: Plugins
+sidebar_current: docs-extending-plugins
+description: >-
+ Plugins allow you to write imports as standalone executables, allowing you to
+ extend Sentinel in ways not normally possible with policy code alone.
+layout: docs
+---
+
+# Extending Sentinel: Plugins
+
+-> **NOTE:** Plugins are currently only supported on the CLI and not in any
+production Sentinel integration, with the exception of existing configuration
+in [Nomad](https://www.nomadproject.io/docs/configuration/sentinel).
+
+Plugins allow you to write imports as standalone executables, allowing you to
+extend Sentinel in ways not normally possible with policy code alone.
+
+You might want to write or use a plugin when you need to:
+
+- **Use a provider or API integration for Sentinel.** In this case, you will
+ probably be working with a provider SDK, or other libraries that will be making
+ complex network requests to external services. Sentinel only provides limited
+ capabilities for these kinds of operations in the standard library, so writing
+ this kind of import as a module is not optimal.
+- **Introduce novel functionality not currently supported by Sentinel.**
+ Sentinel's policy language is limited by design to encourage simple policies,
+ reduce its runtime footprint, and to keep it efficient and safe. This will
+ mean that some functionality may be difficult or impossible to express as
+ Sentinel code, and would require a plugin to write.
+- **Extend a complex module.** Additionally, modules are restricted to a single
+ file of Sentinel code, so these will naturally become more and more unwieldy as
+ you continue to add to them. Eventually, there might be a time where a plugin is
+ better suited for the functionality, especially if it's a general-purpose
+ library.
+
+This section details how import plugins are currently configured in the Sentinel
+CLI, and some detail on how they are written. As we continue to build on the
+ability to use import plugins, we will extend this section with more details.
+
+-> **Not all Sentinel-enabled applications will support plugins.** Some
+applications will disable plugins for security reasons. For those that do enable
+plugins, the method to configure plugins will vary.
+
+## Installing Plugins
+
+Sentinel plugins are standalone executables. Sentinel-enabled applications such
+as the CLI launch these plugins and communicate with them via
+[RPC](https://en.wikipedia.org/wiki/Remote_procedure_call). To install a plugin,
+the first step is to download the plugin executable.
+
+After downloading the plugin, you must add it in the CLI configuration file,
+setting its name, path, and any arguments, environment variables, or
+configuration. An example is below:
+
+```hcl
+import "plugin" "custom_time" {
+ source = "/path/to/sentinel-import-time"
+ config = { "fixed_time": 1504155600 }
+}
+```
+
+-> **NOTE:** We are using "custom_time" as standard import paths cannot be
+overriden. See [Configuration File Syntax](/sentinel/configuration#imports) for
+more details.
+
+After configuring the plugin, it will be available on the next `sentinel apply`
+or `sentinel test`.
+
+For more details on configuration, see the
+[plugins](/sentinel/configuration#plugins) section of the [CLI configuration
+file syntax](/sentinel/configuration).
+
+## Debugging Plugins
+
+The Sentinel CLI logs when it launches, configures, and closes a plugin. The
+plugin may also log additional information when it is running.
+
+To debug issues with a plugin, you can usually refer to these logs. Depending on
+the plugin configuration, the logs you see may vary - refer to the plugin
+documentation on how to enable logs for a particular plugin.
+
+## Developing an Import Plugin
+
+Anyone can develop a Sentinel import plugin using the [Sentinel
+SDK](https://github.com/hashicorp/sentinel-sdk). The SDK contains a high-level
+framework for writing plugins in [Go](https://golang.org) including a test
+framework. Additionally, the SDK contains the low-level [gRPC protocol buffers
+definition](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/plugin.proto)
+for writing plugins in other languages.
+
+The rest of this page will document how to write a new Sentinel plugin in Go
+using the Sentinel SDK and the high-level framework provided. You are expected
+to already know the basics of Go and have a properly configured Go installation.
+
+Throughout this page, we'll be developing a simple plugin to access time values.
+
+-> **NOTE:** The example here is not reflective of the actual implementation of
+the [`time`](/sentinel/imports/time) standard import, so make sure to not
+get the two confused.
+
+### Plugin Framework
+
+The Sentinel SDK provides a high-level
+[framework](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework)
+for writing plugins. We recommend using this framework.
+
+#### Basic Concepts
+
+This framework works by implementing the correct Go interfaces.
+
+As a general overview:
+[`framework.Plugin`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Plugin)
+implements the
+[`sdk.Plugin`](https://godoc.org/github.com/hashicorp/sentinel-sdk#Plugin)
+interface and therefore is a valid import plugin. You must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface to configure the plugin. The root may then delegate nested access to
+various
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+implementations. Other interfaces are implemented to provide functionality past
+what is provided by the basic `framework.Namespace` implementation - these are
+documented below.
+
+This all may sound like a lot of interfaces, but each interface typically only
+requires a single function implementation and you're only required to implement
+a single namespace (`framework.Namespace`).
+
+As we move on, we'll use real examples to make this clear.
+
+#### Implementing framework.Root
+
+To begin, you must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface. This is the interface representing the root of your plugin. The root
+itself must be implement
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace),
+which allows values to be accessed. Note that the root can optionally implement
+other interfaces, but they're more advanced and omitted here for simplicity.
+
+For our custom time example, our root may look like this:
+
+```sentinel
+type root struct {
+ time time.Time
+}
+
+// framework.Root impl.
+func (m *root) Configure(raw map[string]interface{}) error {
+ if _, ok := raw["timestamp"]; !ok {
+ raw["timestamp"] = time.Now().Unix()
+ }
+
+ v := raw["timestamp"]
+ timestamp, ok := v.(int)
+ if !ok {
+ return fmt.Errorf("invalid timestamp type %T", v)
+ }
+
+ m.time = time.Unix(timestamp, 0).UTC()
+ return nil
+}
+
+// framework.Namespace impl.
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "minute":
+ return m.time.Minute(), nil
+ }
+
+ return nil, nil
+}
+```
+
+This example implements `framework.Root` and `framework.Namespace` and would
+enable the following within a Sentinel policy once the completed plugin is
+installed.
+
+```sentinel
+import "custom_time"
+
+print(custom_time.minute)
+main = true
+```
+
+#### framework.Namespace
+
+The
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+interface implements a namespace of values. You saw above that
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+must itself be a namespace. However, a namespace `Get` implementation may
+further return namespaces to access nested values.
+
+This enables behavior such as `custom_time.month.string` vs. `custom_time.month.index`.
+Notice each of these examples accesses a nested value within `month`. This can
+be modeled as namespaces within the framework.
+
+In the example below, we return a new namespace for `month` to do just this:
+
+```sentinel
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "month":
+ return &namespaceMonth{Month: m.time.Month()}, nil
+ }
+
+ // ...
+}
+
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil nil
+}
+```
+
+#### Primitive Types and Structs
+
+A namespace may also return any primitive Go type as well as structs. The
+framework automatically exposes primitive values as you would expect. For
+structs, exported fields are lowercased and exposed to the policies. The
+`sentinel` struct tag can be used to control how Sentinel can access the field.
+
+In the example below, we expose a struct directly from the root namespace:
+
+```sentinel
+type Location struct {
+ Name string
+ TimeZone string `sentinel:"time_zone"`
+}
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "location":
+ return &Location{Name: "somewhere", TimeZone: "some zone"}, nil
+ }
+
+ // ...
+}
+```
+
+This makes the following values work within a Sentinel policy:
+`time.location.name`, `time.location.time_zone`.
+
+If a field has an empty `sentinel` struct tag (example: `sentinel:""`),
+then that field will not be accessible from a policy.
+
+#### Optional Interfaces
+
+The following are optional interfaces that can be implemented on top of
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace).
+All of these interfaces can be implemented at any level, except for
+[`framework.New`](#framework-new), which only works at the root level.
+
+##### `framework.Call`
+
+The
+[`framework.Call`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Call)
+interface can be implemented to support function calls.
+
+Implementing this interface is very similar to attribute access, except instead
+of returning the attribute value, you return a function. The framework uses Go
+reflection to determine the argument and result types and calls it. If the
+argument types do not match or the signature is otherwise invalid, an error is
+returned.
+
+For example, let's implement a function to add months to the current month:
+
+```sentinel
+func (m *root) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *root) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.time.AddDate(0, n, 0).Month()}
+}
+```
+
+You can now call the function. If today was in the month of September,
+the policy below would pass:
+
+```sentinel
+import "custom_time"
+
+main = custom_time.add_month(4).string == "January"
+```
+
+##### `framework.Map`
+
+The optional
+[`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+interface allows an alternative method to to present a namespace back to a
+Sentinel policy as a map.
+
+`framework.Map` is best paired with
+[`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys),
+which allows you to quickly fetch the necessary data via `Get` calls on the
+namespace:
+
+```
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil, nil
+}
+
+func (m *namespaceMonth) Map() (map[string]interface{}, error) {
+ return framework.MapFromKeys(m, []string{"string", "index"})
+}
+```
+
+##### `framework.New`
+
+The optional
+[`framework.New`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#New)
+interface can be implemented on a root namespace to add _methods_ to your
+namespaces.
+
+Data returned from a namespace is memoized and returned as a map, and is
+normally not callable - to call a function to operate on the data, you would
+need to create a new namespace from the top-level of the plugin, and then make a
+function call on the result within the same expression.
+
+Let's consider the [root example](#implementing-framework-root). Let's say,
+instead of hard-coding the time value at configuration, we wanted to load it
+on-demand using a `time.now` key, and allow `add_month` to be callable on that
+value. Our root and de-coupled time namespace would now look like:
+
+```
+type root struct{}
+
+func (m *root) Configure(raw map[string]interface{}) error { return nil }
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "now":
+ return &namespaceTime{Time: time.Now()}
+ }
+
+ return nil
+}
+
+func (m *root) New(data map[string]interface{}) (framework.Namespace, error) {
+ if v, ok := data["unix"]; ok {
+ if t, ok := v.(int64); !ok {
+ return &namespaceTime{Time: time.Unix(t, 0)}
+ }
+
+ return nil, fmt.Errorf("expected timestamp to be int64, got %T", v)
+ }
+
+ return nil, nil
+}
+
+type namespaceTime struct {
+ Time time.Time
+}
+
+func (m *namespaceTime) Get(key string) interface{} {
+ switch key {
+ case "unix":
+ return m.Time.Unix()
+
+ // case ...
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) Get(key string) (interface{}, error) {
+ return framework.MapFromKeys(m, []string{"unix", "..."})
+}
+
+func (m *namespaceTime) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.Time.AddDate(0, n, 0).Month()}
+}
+```
+
+Via this example, we can now create and assign a `namespaceTime` using `t = custom_time.now`, and then call `t.add_month(months_to_add)` to return a
+`namespaceMonth` for the corresponding month.
+
+Methods on a namespace can also _modfiy_ the receiver data. So you can, for
+example, add a method named `increment_month` that takes no arguments, but
+increments the time stored in `namespaceTime.Time` by a month. Subsequent
+statements using the receiver would see the new value.
+
+Note that there are some restrictions and guidelines that you should take into
+account when using `framework.New`:
+
+- Only data that makes it back to a policy can be used as receiver data to
+ instantiate new namespaces. As such it's recommended to make use of
+ [`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+ and
+ [`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys)
+ to return all of the attribute data you need to construct new objects.
+- `framework.New` is only supported on the root namespace and has no effect on
+ namespaces below the root. Check all possible cases of receiver data within
+ the top-level `New` function and return the appropriate namespace based on the
+ input data.
+- Do not return general errors from your `New` method. When an unknown lookup is
+ made off of memoized data, it will hit your `New` method for possible
+ instantiation and key calls. This will ensure `undefined` is correctly
+ returned for these cases.
+- `framework.New` is designed to add utility to plugins where calling methods on
+ a value is more intuitive than just making a function call, or just returning
+ all of a data set as a memoized map. Use it sparingly and avoid using it on
+ recursively complex data sets.
+
+### Testing
+
+The SDK exposes a testing framework to verify your plugin works as
+expected. This test framework integrates directly into `go test` so it
+is part of a familiar workflow.
+
+The test framework works by dynamically building your plugin and
+running it against the `sentinel` CLI to verify it behaves as expected.
+This ensures that your plugin builds, your plugin communicates via RPC
+correctly, and that the behavior within a policy is also correct.
+
+The example below shows a complete ready table-driven test for our time plugin.
+You can run this with a normal `go test`.
+
+```sentinel
+package main
+
+import (
+ "os"
+ "testing"
+
+ "github.com/hashicorp/sentinel-sdk"
+ plugintesting "github.com/hashicorp/sentinel-sdk/testing"
+)
+
+func TestMain(m *testing.M) {
+ exitCode := m.Run()
+ plugintesting.Clean()
+ os.Exit(exitCode)
+}
+
+func TestPlugin(t *testing.T) {
+ cases := []struct {
+ Name string
+ Data map[string]interface{}
+ Source string
+ }{
+ {
+ "month",
+ map[string]interface{}{"timestamp": 1495483674},
+ `main = subject.month.string == "September"`,
+ },
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.Name, func(t *testing.T) {
+ plugintesting.TestPlugin(t, plugintesting.TestPluginCase{
+ Config: tc.Data,
+ Source: tc.Source,
+ })
+ })
+ }
+}
+```
+
+### Building Your Plugin
+
+To build your plugin, you must first implement the `main` function.
+The main function should just use the `rpc.Serve` method to serve the
+plugin over the Sentinel RPC layer:
+
+```sentinel
+package main
+
+import (
+ "github.com/hashicorp/sentinel-sdk"
+ "github.com/hashicorp/sentinel-sdk/rpc"
+)
+
+func main() {
+ rpc.Serve(&rpc.ServeOpts{
+ PluginFunc: func() sdk.Plugin {
+ return &framework.Plugin{Root: &root{}}
+ },
+ })
+}
+```
+
+You can then build this using `go build`. The output can be named
+anything. Once your plugin is built, [install it](#installing-plugins)
+and try it out!
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/extending/static-imports.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/extending/static-imports.mdx
new file mode 100644
index 0000000000..cb51beb5d8
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/extending/static-imports.mdx
@@ -0,0 +1,48 @@
+---
+page_title: >-
+ Extending Sentinel: Static Imports
+sidebar_current: docs-extending-static-imports
+description: >-
+ Static imports allow you to write imports that use static data files.
+layout: docs
+---
+
+# Extending Sentinel: Static Imports
+
+Static imports allow you to write imports that use static data files. This can
+provide methods of consuming data in ways that are not available via
+[modules](./modules) or [plugins](./plugins).
+
+### Configuring a Static Import
+
+The [configuration page](/sentinel/configuration#static-imports) details how to
+configure static imports.
+
+### Usage
+
+Using a static import is not that different to using a plugin or module. The
+import must first be introduced to the policy via an import statement.
+
+```sentinel
+import "people"
+```
+
+From here, the static data can be used throughout the policy as you would any
+normal Sentinel value.
+
+```sentinel
+admins = filter people as person {
+ person.role is "Admin"
+}
+```
+
+One differentiator for static imports is that you can directly reference the
+import without the use of selectors. For example, to print the value;
+
+```sentinel
+print(people)
+```
+
+This is due to the static data being directly loaded and converted into a
+Sentinel value, ready for use. Static imports, like modules and plugins, cannot
+be assigned a new value.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/features/index.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/features/index.mdx
new file mode 100644
index 0000000000..e035be978c
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/features/index.mdx
@@ -0,0 +1,28 @@
+---
+page_title: Features
+sidebar_current: docs-features
+description: Learn how features can enhance the Sentinel runtime by enabling further capabilities.
+layout: docs
+---
+
+# Features
+
+Features are a set of capabilities that enhance the runtime experience. They
+are enabled through the [`sentinel`](/sentinel/configuration#sentinel) block of the
+configuration, using the features attribute. An example of how to enable features
+is as follows:
+
+```hcl
+sentinel {
+ features = {
+ apply-all = true
+ terraform = true
+ }
+}
+```
+
+The current set of features are:
+
+- [terraform](/sentinel/features/terraform) - Allowing the Sentinel runtime
+ to interact with Terraform data.
+- apply-all - Force Sentinel to evaluate all policies within a policy set without prematurely terminating on failures.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/features/terraform/index.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/features/terraform/index.mdx
new file mode 100644
index 0000000000..a810dbe478
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/features/terraform/index.mdx
@@ -0,0 +1,24 @@
+---
+page_title: terraform
+sidebar_current: docs-features-terraform
+description: Learn about the terraform feature and its capabilities.
+layout: docs
+---
+
+# terraform feature
+
+The `terraform` feature enhances the Sentinel runtime to work with Terraform
+data. It will add the following imports to the standard library:
+
+- [tfplan/v1](/sentinel/features/terraform/tfplan-v1)
+- [tfplan/v2](/sentinel/features/terraform/tfplan-v2)
+- [tfconfig/v1](/sentinel/features/terraform/tfconfig-v1)
+- [tfconfig/v2](/sentinel/features/terraform/tfconfig-v2)
+- [tfstate/v1](/sentinel/features/terraform/tfstate-v1)
+- [tfstate/v2](/sentinel/features/terraform/tfstate-v2)
+
+-> **NOTE:** The above imports will only work with Terraform 0.12 and above,
+as they rely on the output from the `terraform show -json` command.
+
+It is recommended that the `/v2` suffixed imports are used, as they provide
+the best experience when interacting with the underlying data structures.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/features/terraform/tfconfig-v1.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/features/terraform/tfconfig-v1.mdx
new file mode 100644
index 0000000000..dfe7093b94
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/features/terraform/tfconfig-v1.mdx
@@ -0,0 +1,945 @@
+---
+page_title: tfconfig/v1 - terraform - Features
+sidebar_current: docs-features-terraform-config-v1
+description: The tfconfig/v1 import provides access to a Terraform configuration.
+layout: docs
+---
+
+# Import: tfconfig/v1
+
+The `tfconfig/v1` import provides access to a Terraform configuration.
+
+The Terraform configuration is the set of `*.tf` files that are used to
+describe the desired infrastructure state. Policies using the `tfconfig/v1`
+import can access all aspects of the configuration: providers, resources,
+data sources, modules, and variables.
+
+Some use cases for `tfconfig/v1` include:
+
+* **Organizational naming conventions**: requiring that configuration elements
+ are named in a way that conforms to some organization-wide standard.
+* **Required inputs and outputs**: organizations may require a particular set
+ of input variable names across all workspaces or may require a particular
+ set of outputs for asset management purposes.
+* **Enforcing particular modules**: organizations may provide a number of
+ "building block" modules and require that each workspace be built only from
+ combinations of these modules.
+* **Enforcing particular providers or resources**: an organization may wish to
+ require or prevent the use of providers and/or resources so that configuration
+ authors cannot use alternative approaches to work around policy
+ restrictions.
+
+Note with these use cases that this import is concerned with object _names_
+in the configuration. Since this is the configuration and not an invocation
+of Terraform, you can't see values for variables, the state, or the diff for
+a pending plan. If you want to write policy around expressions used
+within configuration blocks, you likely want to use the
+[`tfplan/v1`](/sentinel/features/terraform/tfplan-v1) import.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfconfig/v1" {
+ config = {
+ "plan": "./path/to/plan.json"
+ }
+}
+```
+
+## Namespace Overview
+
+The following is a tree view of the import namespace. For more detail on a
+particular part of the namespace, see below.
+
+-> **Note:** The root-level alias keys shown here (`data`, `modules`,
+`providers`, `resources`, and `variables`) are shortcuts to a [module
+namespace](#namespace-module) scoped to the root module. For more details, see
+the section on [root namespace aliases](#root-namespace-aliases).
+
+```
+tfconfig/v1
+├── module() (function)
+│ └── (module namespace)
+│ ├── data
+│ │ └── TYPE.NAME
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── provisioners
+│ │ └── NUMBER
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── type (string)
+│ ├── modules
+│ │ └── NAME
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ ├── source (string)
+│ │ └── version (string)
+│ ├──outputs
+│ │ └── NAME
+│ │ ├── depends_on (list of strings)
+│ │ ├── description (string)
+│ │ ├── sensitive (boolean)
+│ │ ├── references (list of strings)
+│ │ └── value (value)
+│ ├── providers
+│ │ └── TYPE
+│ │ ├── alias
+│ │ │ └── ALIAS
+│ │ │ ├── config (map of keys)
+│ │ | ├── references (map of keys)
+│ │ │ └── version (string)
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── version (string)
+│ ├── resources
+│ │ └── TYPE.NAME
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── provisioners
+│ │ └── NUMBER
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── type (string)
+│ └── variables
+│ └── NAME
+│ ├── default (value)
+│ └── description (string)
+├── module_paths ([][]string)
+│
+├── data (root module alias)
+├── modules (root module alias)
+├── outputs (root module alias)
+├── providers (root module alias)
+├── resources (root module alias)
+└── variables (root module alias)
+```
+
+### `references` Overview
+
+As an example, consider the following resource block:
+
+```hcl
+resource "local_file" "accounts" {
+ content = "some text"
+ filename = "${var.subdomain}.${var.domain}/accounts.txt"
+}
+```
+
+In this example, one might want to ensure `domain` and `subdomain` input
+variables are used within `filename` in this configuration.
+
+-> Any non-static values (such as interpolated strings) are not present within the
+configuration value and `references` should be used instead:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+# filename_references is a list of string values containing the references used in the expression
+filename_references = tfconfig.resources.local_file.accounts.references.filename
+
+main = rule {
+ filename_references contains "var.domain" and
+ filename_references contains "var.subdomain"
+}
+```
+
+The `references` value is present in any namespace where non-constant
+configuration values can be expressed. This is essentially every namespace
+which has a `config` value as well as the `outputs` namespace.
+
+-> **Note:** Remember, this import enforces policy around the literal Terraform
+configuration and not the final values as a result of invoking Terraform. If
+you want to write policy around the _result_ of expressions used within
+configuration blocks (for example, if you wanted to ensure the final value of
+`filename` above includes `accounts.txt`), you likely want to use the
+[`tfplan/v1`](/sentinel/features/terraform/tfplan-v1) import.
+
+## Namespace: Root
+
+The root-level namespace consists of the values and functions documented below.
+
+In addition to this, the root-level `data`, `modules`, `providers`, `resources`,
+and `variables` keys all alias to their corresponding namespaces within the
+[module namespace](#namespace-module).
+
+
+
+### Function: `module()`
+
+```
+module = func(ADDR)
+```
+
+* **Return Type:** A [module namespace](#namespace-module).
+
+The `module()` function in the [root namespace](#namespace-root) returns the
+[module namespace](#namespace-module) for a particular module address.
+
+The address must be a list and is the module address, split on the period (`.`),
+excluding the root module.
+
+Hence, a module with an address of simply `foo` (or `root.foo`) would be
+`["foo"]`, and a module within that (so address `foo.bar`) would be read as
+`["foo", "bar"]`.
+
+[`null`][ref-null] is returned if a module address is invalid, or if the module
+is not present in the configuration.
+
+[ref-null]: /sentinel/language/spec#null
+
+As an example, given the following module block:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+If the module contained the following content:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { subject.module(["foo"]).resources.null_resource.foo.config.triggers[0].foo is "bar" }
+```
+
+
+
+### Value: `module_paths`
+
+* **Value Type:** List of a list of strings.
+
+The `module_paths` value within the [root namespace](#namespace-root) is a list
+of all of the modules within the Terraform configuration.
+
+Modules not present in the configuration will not be present here, even if they
+are present in the diff or state.
+
+This data is represented as a list of a list of strings, with the inner list
+being the module address, split on the period (`.`).
+
+The root module is included in this list, represented as an empty inner list.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The value of `module_paths` would be:
+
+```
+[
+ [],
+ ["foo"],
+]
+```
+
+And the following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.module_paths contains ["foo"] }
+```
+
+#### Iterating Through Modules
+
+Iterating through all modules to find particular resources can be useful. This
+[example][iterate-over-modules] shows how to use `module_paths` with the
+[`module()` function](#function-module-) to find all resources of a
+particular type from all modules using the `tfplan` import. By changing `tfplan`
+in this function to `tfconfig`, you could make a similar function find all
+resources of a specific type in the Terraform configuration.
+
+[iterate-over-modules]: https://developer.hashicorp.com/terraform/cloud-docs/policy-enforcement/sentinel#sentinel-imports
+
+## Namespace: Module
+
+The **module namespace** can be loaded by calling [`module()`](#root-function-module)
+for a particular module.
+
+It can be used to load the following child namespaces:
+
+* `data` - Loads the [resource namespace](#namespace-resources-data-sources),
+ filtered against data sources.
+* `modules` - Loads the [module configuration
+ namespace](#namespace-module-configuration).
+* `outputs` - Loads the [output namespace](#namespace-outputs).
+* `providers` - Loads the [provider namespace](#namespace-providers).
+* `resources` - Loads the [resource
+ namespace](#namespace-resources-data-sources), filtered against resources.
+* `variables` - Loads the [variable namespace](#namespace-variables).
+
+### Root Namespace Aliases
+
+The root-level `data`, `modules`, `providers`, `resources`, and `variables` keys
+all alias to their corresponding namespaces within the module namespace, loaded
+for the root module. They are the equivalent of running `module([]).KEY`.
+
+
+
+## Namespace: Resources/Data Sources
+
+The **resource namespace** is a namespace _type_ that applies to both resources
+(accessed by using the `resources` namespace key) and data sources (accessed
+using the `data` namespace key).
+
+Accessing an individual resource or data source within each respective namespace
+can be accomplished by specifying the type and name, in the syntax
+`[resources|data].TYPE.NAME`.
+
+In addition, each of these namespace levels is a map, allowing you to filter
+based on type and name. Some examples of multi-level access are below:
+
+* To fetch all `aws_instance` resources within the root module, you can specify
+ `tfconfig.resources.aws_instance`. This would give you a map of resource
+ namespaces indexed from the names of each resource (`foo`, `bar`, and so
+ on).
+* To fetch all resources within the root module, irrespective of type, use
+ `tfconfig.resources`. This is indexed by type, as shown above with
+ `tfconfig.resources.aws_instance`, with names being the next level down.
+
+As an example, perhaps you wish to deny use of the `local_file` resource
+in your configuration. Consider the following resource block:
+
+```hcl
+resource "local_file" "foo" {
+ content = "foo!"
+ filename = "${path.module}/foo.bar"
+}
+```
+
+The following policy would fail:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.resources not contains "local_file" }
+```
+
+Further explanation of the namespace will be in the context of resources. As
+mentioned, when operating on data sources, use the same syntax, except with
+`data` in place of `resources`.
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+The `config` value within the [resource
+namespace](#namespace-resources-data-sources) is a map of key-value pairs that
+directly map to Terraform config keys and values.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#resources-value-references) should be used instead.
+
+As an example, consider the following resource block:
+
+```hcl
+resource "local_file" "accounts" {
+ content = "some text"
+ filename = "accounts.txt"
+}
+```
+
+In this example, one might want to access `filename` to validate that the correct
+file name is used. Given the above example, the following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule {
+ tfconfig.resources.local_file.accounts.config.filename is "accounts.txt"
+}
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [resource namespace](#namespace-resources-data-sources)
+contains the identifiers within non-constant expressions found in [`config`](#resources-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+
+
+### Value: `provisioners`
+
+* **Value Type:** List of [provisioner namespaces](#namespace-provisioners).
+
+The `provisioners` value within the [resource namespace](#namespace-resources)
+represents the [provisioners][ref-tf-provisioners] within a specific resource.
+
+Provisioners are listed in the order they were provided in the configuration
+file.
+
+While the `provisioners` value will be present within data sources, it will
+always be an empty map `null` (in Terraform 0.12) since data sources cannot
+actually have provisioners.
+
+The data within a provisioner can be inspected via the returned [provisioner
+namespace](#namespace-provisioners).
+
+[ref-tf-provisioners]: https://developer.hashicorp.com/terraform/language/resources/provisioners/syntax
+
+## Namespace: Provisioners
+
+The **provisioner namespace** represents the configuration for a particular
+[provisioner][ref-tf-provisioners] within a specific resource.
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+The `config` value within the [provisioner namespace](#namespace-provisioners)
+represents the values of the keys within the provisioner.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#provisioners-value-references) should be used instead.
+
+As an example, given the following resource block:
+
+```hcl
+resource "null_resource" "foo" {
+ # ...
+
+ provisioner "local-exec" {
+ command = "echo ${self.private_ip} > file.txt"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule {
+ tfconfig.resources.null_resource.foo.provisioners[0].config.command is "echo ${self.private_ip} > file.txt"
+}
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [provisioner namespace](#namespace-provisioners)
+contains the identifiers within non-constant expressions found in [`config`](#provisioners-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+
+
+### Value: `type`
+
+* **Value Type:** String.
+
+The `type` value within the [provisioner namespace](#namespace-provisioners)
+represents the type of the specific provisioner.
+
+As an example, in the following resource block:
+
+```hcl
+resource "null_resource" "foo" {
+ # ...
+
+ provisioner "local-exec" {
+ command = "echo ${self.private_ip} > file.txt"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.resources.null_resource.foo.provisioners[0].type is "local-exec" }
+```
+
+## Namespace: Module Configuration
+
+The **module configuration** namespace displays data on _module configuration_
+as it is given within a `module` block. This means that the namespace concerns
+itself with the contents of the declaration block (example: the `source`
+parameter and variable assignment keys), not the data within the module
+(example: any contained resources or data sources). For the latter, the module
+instance would need to be looked up with the [`module()`
+function](#root-function-module).
+
+
+
+### Value: `source`
+
+* **Value Type:** String.
+
+The `source` value within the [module configuration
+namespace](#namespace-module-configuration) represents the module source path as
+supplied to the module configuration.
+
+As an example, given the module declaration block:
+
+```hcl
+module "foo" {
+ source = "./foo"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.modules.foo.source is "./foo" }
+```
+
+
+
+### Value: `version`
+
+* **Value Type:** String.
+
+The `version` value within the [module configuration
+namespace](#namespace-module-configuration) represents the [version
+constraint][module-version-constraint] for modules that support it, such as
+modules within the [Terraform Module Registry][terraform-module-registry] or the
+[HCP Terraform private module registry][tfe-private-registry].
+
+[module-version-constraint]: https://developer.hashicorp.com/terraform/language/modules#module-versions
+
+[terraform-module-registry]: https://registry.terraform.io/
+
+[tfe-private-registry]: https://developer.hashicorp.com/terraform/cloud-docs/registry
+
+As an example, given the module declaration block:
+
+```hcl
+module "foo" {
+ source = "foo/bar"
+ version = "~> 1.2"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.modules.foo.version is "~> 1.2" }
+```
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#modules-value-references) should be used instead.
+
+The `config` value within the [module configuration
+namespace](#namespace-module-configuration) represents the values of the keys
+within the module configuration. This is every key within a module declaration
+block except [`source`](#modules-value-source) and [`version`](#modules-value-version), which
+have their own values.
+
+As an example, given the module declaration block:
+
+```hcl
+module "foo" {
+ source = "./foo"
+
+ bar = "baz"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.modules.foo.config.bar is "baz" }
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [module configuration namespace](#namespace-module-configuration)
+contains the identifiers within non-constant expressions found in [`config`](#modules-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+## Namespace: Outputs
+
+The **output namespace** represents _declared_ output data within a
+configuration. As such, configuration for the [`value`](#outputs-value-value) attribute
+will be in its raw form, and not yet interpolated. For fully interpolated output
+values, see the [`tfstate` import][ref-tfe-sentinel-tfstate].
+
+[ref-tfe-sentinel-tfstate]: /sentinel/features/terraform/tfstate-v1
+
+This namespace is indexed by output name.
+
+
+
+### Value: `depends_on`
+
+* **Value Type:** A list of strings.
+
+The `depends_on` value within the [output namespace](#namespace-outputs)
+represents any _explicit_ dependencies for this output. For more information,
+see the [depends_on output setting][ref-depends_on] within the general Terraform
+documentation.
+
+[ref-depends_on]: https://developer.hashicorp.com/terraform/language/values/outputs#depends_on
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ depends_on = ["null_resource.bar"]
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.depends_on[0] is "null_resource.bar" }
+```
+
+
+
+### Value: `description`
+
+* **Value Type:** String.
+
+The `description` value within the [output namespace](#namespace-outputs)
+represents the defined description for this output.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ description = "foobar"
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.description is "foobar" }
+```
+
+
+
+### Value: `sensitive`
+
+* **Value Type:** Boolean.
+
+The `sensitive` value within the [output namespace](#namespace-outputs)
+represents if this value has been marked as sensitive or not.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ sensitive = true
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { subject.outputs.id.sensitive }
+```
+
+
+
+### Value: `value`
+
+* **Value Type:** Any primitive type, list or map.
+
+The `value` value within the [output namespace](#namespace-outputs) represents
+the defined value for the output as declared in the configuration. Primitives
+will bear the implicit type of their declaration (string, int, float, or bool),
+and maps and lists will be represented as such.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#outputs-value-references) should be used instead.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ value = "foo"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.value is "foo" }
+```
+
+
+
+### Value: `references`
+
+* **Value Type:**. List of strings.
+
+The `references` value within the [output namespace](#namespace-outputs)
+contains the names of any referenced identifiers when [`value`](#outputs-value-value)
+is a non-constant expression.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.references contains "null_resource.foo.id" }
+```
+
+## Namespace: Providers
+
+The **provider namespace** represents data on the declared providers within a
+namespace.
+
+This namespace is indexed by provider type and _only_ contains data about
+providers when actually declared. If you are using a completely implicit
+provider configuration, this namespace will be empty.
+
+This namespace is populated based on the following criteria:
+
+* The top-level namespace [`config`](#providers-value-config) and
+ [`version`](#providers-value-version) values are populated with the configuration and
+ version information from the default provider (the provider declaration that
+ lacks an alias).
+* Any aliased providers are added as namespaces within the
+ [`alias`](#providers-value-alias) value.
+* If a module lacks a default provider configuration, the top-level `config` and
+ `version` values will be empty.
+
+
+
+### Value: `alias`
+
+* **Value Type:** A map of [provider namespaces](#namespace-providers), indexed
+ by alias.
+
+The `alias` value within the [provider namespace](#namespace-providers)
+represents all declared [non-default provider
+instances][ref-tf-provider-instances] for a specific provider type, indexed by
+their specific alias.
+
+[ref-tf-provider-instances]: https://developer.hashicorp.com/terraform/language/providers/configuration#alias-multiple-provider-configurations
+
+The return type is a provider namespace with the data for the instance in
+question loaded. The `alias` key will not be available within this namespace.
+
+As an example, given the following provider declaration block:
+
+```hcl
+provider "aws" {
+ alias = "east"
+ region = "us-east-1"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.providers.aws.alias.east.config.region is "us-east-1" }
+```
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#providers-value-references) should be used instead.
+
+The `config` value within the [provider namespace](#namespace-providers)
+represents the values of the keys within the provider's configuration, with the
+exception of the provider version, which is represented by the
+[`version`](#providers-value-version) value. [`alias`](#providers-value-alias) is also not included
+when the provider is aliased.
+
+As an example, given the following provider declaration block:
+
+```hcl
+provider "aws" {
+ region = "us-east-1"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.providers.aws.config.region is "us-east-1" }
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [provider namespace](#namespace-providers)
+contains the identifiers within non-constant expressions found in [`config`](#providers-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+
+
+### Value: `version`
+
+* **Value Type:** String.
+
+The `version` value within the [provider namespace](#namespace-providers)
+represents the explicit expected version of the supplied provider. This includes
+the pessimistic operator.
+
+As an example, given the following provider declaration block:
+
+```hcl
+provider "aws" {
+ version = "~> 1.34"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.providers.aws.version is "~> 1.34" }
+```
+
+## Namespace: Variables
+
+The **variable namespace** represents _declared_ variable data within a
+configuration. As such, static data can be extracted, such as defaults, but not
+dynamic data, such as the current value of a variable within a plan (although
+this can be extracted within the [`tfplan` import][ref-tfe-sentinel-tfplan]).
+
+[ref-tfe-sentinel-tfplan]: /sentinel/features/terraform/tfplan-v1
+
+This namespace is indexed by variable name.
+
+
+
+### Value: `default`
+
+* **Value Type:** Any primitive type, list, map, or `null`.
+
+The `default` value within the [variable namespace](#namespace-variables)
+represents the default for the variable as declared in the configuration.
+
+The actual value will be as configured. Primitives will bear the implicit type
+of their declaration (string, int, float, or bool), and maps and lists will be
+represented as such.
+
+If no default is present, the value will be [`null`][ref-sentinel-null] (not to
+be confused with [`undefined`][ref-sentinel-undefined]).
+
+[ref-sentinel-null]: /sentinel/language/spec#null
+
+[ref-sentinel-undefined]: /sentinel/language/undefined
+
+As an example, given the following variable blocks:
+
+```hcl
+variable "foo" {
+ default = "bar"
+}
+
+variable "number" {
+ default = 42
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+default_foo = rule { tfconfig.variables.foo.default is "bar" }
+default_number = rule { tfconfig.variables.number.default is 42 }
+
+main = rule { default_foo and default_number }
+```
+
+
+
+### Value: `description`
+
+* **Value Type:** String.
+
+The `description` value within the [variable namespace](#namespace-variables)
+represents the description of the variable, as provided in configuration.
+
+As an example, given the following variable block:
+
+```hcl
+variable "foo" {
+ description = "foobar"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.variables.foo.description is "foobar" }
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/features/terraform/tfconfig-v2.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/features/terraform/tfconfig-v2.mdx
new file mode 100644
index 0000000000..1fe4fff092
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/features/terraform/tfconfig-v2.mdx
@@ -0,0 +1,441 @@
+---
+page_title: tfconfig/v2 - terraform - Features
+sidebar_current: docs-features-terraform-tfconfig-v2
+description: The tfconfig/v2 import provides access to a Terraform configuration.
+layout: docs
+---
+
+# Import: tfconfig/v2
+
+The `tfconfig/v2` import provides access to a Terraform configuration.
+
+The Terraform configuration is the set of `*.tf` files that are used to
+describe the desired infrastructure state. Policies using the `tfconfig`
+import can access all aspects of the configuration: providers, resources,
+data sources, modules, and variables.
+
+Some use cases for `tfconfig` include:
+
+* **Organizational naming conventions**: requiring that configuration elements
+ are named in a way that conforms to some organization-wide standard.
+* **Required inputs and outputs**: organizations may require a particular set
+ of input variable names across all workspaces or may require a particular
+ set of outputs for asset management purposes.
+* **Enforcing particular modules**: organizations may provide a number of
+ "building block" modules and require that each workspace be built only from
+ combinations of these modules.
+* **Enforcing particular providers or resources**: an organization may wish to
+ require or prevent the use of providers and/or resources so that configuration
+ authors cannot use alternative approaches to work around policy
+ restrictions.
+
+The data in the `tfconfig/v2` import is sourced from the JSON configuration file
+that is generated by the [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) command. For more information on
+the file format, see the [JSON Output Format](https://developer.hashicorp.com/terraform/internals/json-format)
+page.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfconfig/v2" {
+ config = {
+ "plan": "./path/to/plan.json"
+ }
+}
+```
+
+## Import Overview
+
+The `tfconfig/v2` import is structured as a series of _collections_, keyed as a
+specific format, such as resource address, module address, or a
+specifically-formatted provider key.
+
+```
+tfconfig/v2
+├── strip_index() (function)
+├── providers
+│ └── (indexed by [module_address:]provider[.alias])
+│ ├── provider_config_key (string)
+│ ├── name (string)
+│ ├── full_name (string)
+│ ├── alias (string)
+│ ├── module_address (string)
+│ ├── config (block expression representation)
+│ └── version_constraint (string)
+├── resources
+│ └── (indexed by address)
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── provider_config_key (string)
+│ ├── provisioners (list)
+│ │ └── (ordered provisioners for this resource only)
+│ ├── config (block expression representation)
+│ ├── count (expression representation)
+│ ├── for_each (expression representation)
+│ └── depends_on (list of strings)
+├── provisioners
+│ └── (indexed by resource_address:index)
+│ ├── resource_address (string)
+│ ├── type (string)
+│ ├── index (string)
+│ └── config (block expression representation)
+├── variables
+│ └── (indexed by module_address:name)
+│ ├── module_address (string)
+│ ├── name (string)
+│ ├── default (value)
+│ └── description (string)
+├── outputs
+│ └── (indexed by module_address:name)
+│ ├── module_address (string)
+│ ├── name (string)
+│ ├── sensitive (boolean)
+│ ├── value (expression representation)
+│ ├── description (string)
+│ └── depends_on (list of strings)
+└── module_calls
+ └── (indexed by module_address:name)
+ ├── module_address (string)
+ ├── name (string)
+ ├── source (string)
+ ├── config (block expression representation)
+ ├── count (expression representation)
+ ├── depends_on (expression representation)
+ ├── for_each (expression representation)
+ └── version_constraint (string)
+```
+
+The collections are:
+
+* [`providers`](#the-providers-collection) - The configuration for all provider
+ instances across all modules in the configuration.
+* [`resources`](#the-resources-collection) - The configuration of all resources
+ across all modules in the configuration.
+* [`variables`](#the-variables-collection) - The configuration of all variable
+ definitions across all modules in the configuration.
+* [`outputs`](#the-outputs-collection) - The configuration of all output
+ definitions across all modules in the configuration.
+* [`module_calls`](#the-module_calls-collection) - The configuration of all module
+ calls (individual [`module`](/terraform/language/modules) blocks) across
+ all modules in the configuration.
+
+These collections are specifically designed to be used with the
+[`filter`](/sentinel/language/collection-operations#filter-expression)
+quantifier expression in Sentinel, so that one can collect a list of resources
+to perform policy checks on without having to write complex module or
+configuration traversal. As an example, the following code will return all
+`aws_instance` resource types within the configuration, regardless of what
+module they are in:
+
+```
+all_aws_instances = filter tfconfig.resources as _, r {
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+You can add specific attributes to the filter to narrow the search, such as the
+module address. The following code would return resources in a module named
+`foo` only:
+
+```
+all_aws_instances = filter tfconfig.resources as _, r {
+ r.module_address is "module.foo" and
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+### Address Differences Between `tfconfig`, `tfplan`, and `tfstate`
+
+This import deals with configuration before it is expanded into a
+resource graph by Terraform. As such, it is not possible to compute an index as
+the import is building its collections and computing addresses for resources and
+modules.
+
+As such, addresses found here may not always match the expanded addresses found
+in the [`tfplan/v2`](/sentinel/features/terraform/tfplan-v2) and [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2)
+imports, specifically when
+[`count`](https://developer.hashicorp.com/terraform/language/resources#count-multiple-resource-instances-by-count)
+and
+[`for_each`](https://developer.hashicorp.com/terraform/language/resources#for_each-multiple-resource-instances-defined-by-a-map-or-set-of-strings),
+are used.
+
+As an example, consider a resource named `null_resource.foo` with a count of `2`
+located in a module named `bar`. While there will possibly be entries in the
+other imports for `module.bar.null_resource.foo[0]` and
+`module.bar.null_resource.foo[1]`, in `tfconfig/v2`, there will only be a
+`module.bar.null_resource.foo`. As mentioned in the start of this section, this
+is because configuration actually _defines_ this scaling, whereas _expansion_
+actually happens when the resource graph is built, which happens as a natural
+part of the refresh and planning process.
+
+The `strip_index` helper function, found in this import, can assist in
+removing the indexes from addresses found in the `tfplan/v2` and `tfstate/v2`
+imports so that data from those imports can be used to reference data in this
+one.
+
+## The `strip_index` Function
+
+The `strip_index` helper function can be used to remove indexes from addresses
+found in [`tfplan/v2`](/sentinel/features/terraform/tfplan-v2) and [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2),
+by removing the indexes from each resource.
+
+This can be used to help facilitate cross-import lookups for data between plan,
+state, and config.
+
+```
+import "tfconfig/v2" as tfconfig
+import "tfplan/v2" as tfplan
+
+main = rule {
+ all filter tfplan.resource_changes as _, rc {
+ rc.mode is "managed" and
+ rc.type is "aws_instance"
+ } as _, rc {
+ tfconfig.resources[tfconfig.strip_index(rc.address)].config.ami.constant_value is "ami-abcdefgh012345"
+ }
+}
+```
+
+## Expression Representations
+
+Most collections in this import will have one of two kinds of _expression
+representations_. This is a verbose format for expressing a (parsed)
+configuration value independent of the configuration source code, which is not
+100% available to a policy check in HCP Terraform.
+
+```
+(expression representation)
+├── constant_value (value)
+└── references (list of strings)
+```
+
+There are two major parts to an expression representation:
+
+* Any _strictly constant value_ is expressed as an expression with a
+ `constant_value` field.
+* Any expression that requires some degree of evaluation to generate the final
+ value - even if that value is known at plan time - is not expressed in
+ configuration. Instead, any particular references that are made are added to
+ the `references` field. More details on this field can be found in the
+ [expression
+ representation](https://developer.hashicorp.com/terraform/internals/json-format#expression-representation)
+ section of the JSON output format documentation.
+
+For example, to determine if an output is based on a particular
+resource value, one could do:
+
+```
+import "tfconfig/v2" as tfconfig
+
+main = rule {
+ tfconfig.outputs["instance_id"].value.references is ["aws_instance.foo"]
+}
+```
+
+-> **Note:** The representation does not account for
+complex interpolations or other expressions that combine constants with other
+expression data. For example, the partially constant data in `"foo${var.bar}"` would be lost.
+
+### Block Expression Representation
+
+Expanding on the above, a multi-value expression representation (such as the
+kind found in a [`resources`](#the-resources-collection) collection element) is
+similar, but the root value is a keyed map of expression representations. This
+is repeated until a "scalar" expression value is encountered, ie: a field that
+is not a block in the resource's schema.
+
+```
+(block expression representation)
+└── (attribute key)
+ ├── (child block expression representation)
+ │ └── (...)
+ ├── constant_value (value)
+ └── references (list of strings)
+```
+
+As an example, one can validate expressions in an
+[`aws_instance`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance) resource using the
+following:
+
+```
+import "tfconfig/v2" as tfconfig
+
+main = rule {
+ tfconfig.resources["aws_instance.foo"].config.ami.constant_value is "ami-abcdefgh012345"
+}
+```
+
+Note that _nested blocks_, sometimes known as _sub-resources_, will be nested in
+configuration as as list of blocks (reflecting their ultimate nature as a list
+of objects). An example would be the `aws_instance` resource's
+[`ebs_block_device`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#ebs-ephemeral-and-root-block-devices) block:
+
+```
+import "tfconfig/v2" as tfconfig
+
+main = rule {
+ tfconfig.resources["aws_instance.foo"].config.ebs_block_device[0].volume_size < 10
+}
+```
+
+## The `providers` Collection
+
+The `providers` collection is a collection representing the configurations of
+all provider instances across all modules in the configuration.
+
+This collection is indexed by an opaque key. This is currently
+`module_address:provider.alias`, the same value as found in the
+`provider_config_key` field. `module_address` and the colon delimiter are
+omitted for the root module.
+
+The `provider_config_key` field is also found in the `resources` collection and
+can be used to locate a provider that belongs to a configured resource.
+
+The fields in this collection are as follows:
+
+* `provider_config_key` - The opaque configuration key, used as the index key.
+* `name` - The name of the provider, ie: `aws`.
+* `full_name` - The fully-qualified name of the provider, e.g. `registry.terraform.io/hashicorp/aws`.
+* `alias` - The alias of the provider, ie: `east`. Empty for a default provider.
+* `module_address` - The address of the module this provider appears in.
+* `config` - A [block expression
+ representation](#block-expression-representation) with provider configuration
+ values.
+* `version_constraint` - The defined version constraint for this provider.
+
+## The `resources` Collection
+
+The `resources` collection is a collection representing all of the resources
+found in all modules in the configuration.
+
+This collection is indexed by the resource address.
+
+The fields in this collection are as follows:
+
+* `address` - The resource address. This is the index of the collection.
+* `module_address` - The module address that this resource was found in.
+* `mode` - The resource mode, either `managed` (resources) or `data` (data
+ sources).
+* `type` - The type of resource, ie: `null_resource` in `null_resource.foo`.
+* `name` - The name of the resource, ie: `foo` in `null_resource.foo`.
+* `provider_config_key` - The opaque configuration key that serves as the index
+ of the [`providers`](#the-providers-collection) collection.
+* `provisioners` - The ordered list of provisioners for this resource. The
+ syntax of the provisioners matches those found in the
+ [`provisioners`](#the-provisioners-collection) collection, but is a list
+ indexed by the order the provisioners show up in the resource.
+* `config` - The [block expression
+ representation](#block-expression-representation) of the configuration values
+ found in the resource.
+* `count` - The [expression data](#expression-representations) for the `count`
+ value in the resource.
+* `for_each` - The [expression data](#expression-representations) for the
+ `for_each` value in the resource.
+* `depends_on` - The contents of the `depends_on` config directive, which
+ declares explicit dependencies for this resource.
+
+## The `provisioners` Collection
+
+The `provisioners` collection is a collection of all of the provisioners found
+across all resources in the configuration.
+
+While normally bound to a resource in an ordered fashion, this collection allows
+for the filtering of provisioners within a single expression.
+
+This collection is indexed with a key following the format
+`resource_address:index`, with each field matching their respective field in the
+particular element below:
+
+* `resource_address`: The address of the resource that the provisioner was found
+ in. This can be found in the [`resources`](#the-resources-collection)
+ collection.
+* `type`: The provisioner type, ie: `local_exec`.
+* `index`: The provisioner index as it shows up in the resource provisioner
+ order.
+* `config`: The [block expression
+ representation](#block-expression-representation) of the configuration values
+ in the provisioner.
+
+## The `variables` Collection
+
+The `variables` collection is a collection of all variables across all modules
+in the configuration.
+
+Note that this tracks variable definitions, not values. See the [`tfplan/v2`
+`variables` collection](/sentinel/features/terraform/tfplan-v2#the-variables-collection) for variable
+values set within a plan.
+
+This collection is indexed by the key format `module_address:name`, with each
+field matching their respective name below. `module_address` and the colon
+delimiter are omitted for the root module.
+
+* `module_address` - The address of the module the variable was found in.
+* `name` - The name of the variable.
+* `default` - The defined default value of the variable.
+* `description` - The description of the variable.
+
+## The `outputs` Collection
+
+The `outputs` collection is a collection of all outputs across all modules in
+the configuration.
+
+Note that this tracks variable definitions, not values. See the [`tfstate/v2`
+`outputs` collection](/sentinel/features/terraform/tfstate-v2#the-outputs-collection) for the final
+values of outputs set within a state. The [`tfplan/v2` `output_changes`
+collection](/sentinel/features/terraform/tfplan-v2#the-output_changes-collection) also contains a more
+complex collection of planned output changes.
+
+This collection is indexed by the key format `module_address:name`, with each
+field matching their respective name below. `module_address` and the colon
+delimiter are omitted for the root module.
+
+* `module_address` - The address of the module the output was found in.
+* `name` - The name of the output.
+* `sensitive` - Indicates whether or not the output was marked as
+ [`sensitive`](https://developer.hashicorp.com/terraform/language/values/outputs#sensitive-suppressing-values-in-cli-output).
+* `value` - An [expression representation](#expression-representations) for the output.
+* `description` - The description of the output.
+* `depends_on` - A list of resource names that the output depends on. These are
+ the hard-defined output dependencies as defined in the
+ [`depends_on`](https://developer.hashicorp.com/terraform/language/values/outputs#depends_on-explicit-output-dependencies)
+ field in an output declaration, not the dependencies that get derived from
+ natural evaluation of the output expression (these can be found in the
+ `references` field of the expression representation).
+
+## The `module_calls` Collection
+
+The `module_calls` collection is a collection of all module declarations at all
+levels within the configuration.
+
+Note that this is the
+[`module`](https://developer.hashicorp.com/terraform/language/modules#calling-a-child-module) stanza in
+any particular configuration, and not the module itself. Hence, a declaration
+for `module.foo` would actually be declared in the root module, which would be
+represented by a blank field in `module_address`.
+
+This collection is indexed by the key format `module_address:name`, with each
+field matching their respective name below. `module_address` and the colon
+delimiter are omitted for the root module.
+
+* `module_address` - The address of the module the declaration was found in.
+* `name` - The name of the module.
+* `source` - The contents of the `source` field.
+* `config` - A [block expression
+ representation](#block-expression-representation) for all parameter values
+ sent to the module.
+* `count` - An [expression representation](#expression-representations) for the
+ `count` field.
+* `depends_on`: An [expression representation](#expression-representations) for the
+ `depends_on` field.
+* `for_each` - An [expression representation](#expression-representations) for
+ the `for_each` field.
+* `version_constraint` - The string value found in the `version` field of the
+ module declaration.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/features/terraform/tfplan-v1.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/features/terraform/tfplan-v1.mdx
new file mode 100644
index 0000000000..0e0f9436a5
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/features/terraform/tfplan-v1.mdx
@@ -0,0 +1,610 @@
+---
+page_title: tfplan/v1 - terraform - Features
+sidebar_current: docs-features-terraform-tfplan-v1
+description: >-
+ The tfplan/v1 import provides access to a Terraform plan. A Terraform plan is the
+ file created as a result of `terraform plan` and is the input to `terraform
+ apply`. The plan represents the changes that Terraform needs to make to
+ infrastructure to reach the desired state represented by the configuration.
+layout: docs
+---
+
+# Import: tfplan/v1
+
+The `tfplan/v1` import provides access to a Terraform plan. A Terraform plan is the
+file created as a result of `terraform plan` and is the input to `terraform
+apply`. The plan represents the changes that Terraform needs to make to
+infrastructure to reach the desired state represented by the configuration.
+
+In addition to the diff data available in the plan, there is an
+[`applied`](#value-applied) state available that merges the plan with the state
+to create the planned state after apply.
+
+Finally, this import also allows you to access the configuration files and the
+Terraform state at the time the plan was run. See the section on [accessing a
+plan's state and configuration
+data](#accessing-a-plan-39-s-state-and-configuration-data) for more information.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan and schemas file.
+
+```hcl
+import "plugin" "tfplan/v1" {
+ config = {
+ "plan_path": "./path/to/plan.json",
+ "schemas_path": "./path/to/schemas.json"
+ }
+}
+```
+
+## Namespace Overview
+
+The following is a tree view of the import namespace. For more detail on a
+particular part of the namespace, see below.
+
+-> Note that the root-level alias keys shown here (`data`, `path`, and
+`resources`) are shortcuts to a [module namespace](#namespace-module) scoped to
+the root module. For more details, see the section on [root namespace
+aliases](#root-namespace-aliases).
+
+```
+tfplan/v1
+├── module() (function)
+│ └── (module namespace)
+│ ├── path ([]string)
+│ ├── data
+│ │ └── TYPE.NAME[NUMBER]
+│ │ ├── applied (map of keys)
+│ │ └── diff
+│ │ └── KEY
+│ │ ├── computed (bool)
+│ │ ├── new (string)
+│ │ └── old (string)
+│ └── resources
+│ └── TYPE.NAME[NUMBER]
+│ ├── applied (map of keys)
+│ ├── destroy (bool)
+│ ├── requires_new (bool)
+│ └── diff
+│ └── KEY
+│ ├── computed (bool)
+│ ├── new (string)
+│ └── old (string)
+├── module_paths ([][]string)
+├── terraform_version (string)
+├── variables (map of keys)
+│
+├── data (root module alias)
+├── path (root module alias)
+├── resources (root module alias)
+│
+├── config (tfconfig namespace alias)
+└── state (tfstate import alias)
+```
+
+## Namespace: Root
+
+The root-level namespace consists of the values and functions documented below.
+
+In addition to this, the root-level `data`, `path`, and `resources` keys alias
+to their corresponding namespaces or values within the [module
+namespace](#namespace-module).
+
+### Accessing a Plan's State and Configuration Data
+
+The `config` and `state` keys alias to the [`tfconfig`](/sentinel/features/terraform/tfconfig-v1) and
+[`tfstate`](/sentinel/features/terraform/tfstate-v1) namespaces, respectively, with the data sourced from
+the Terraform _plan_ (as opposed to actual configuration and state).
+
+-> Note that these aliases are not represented as maps. While they will appear
+empty when viewed as maps, the specific import namespace keys will still be
+accessible.
+
+### Function: `module()`
+
+```
+module = func(ADDR)
+```
+
+* **Return Type:** A [module namespace](#namespace-module).
+
+The `module()` function in the [root namespace](#namespace-root) returns the
+[module namespace](#namespace-module) for a particular module address.
+
+The address must be a list and is the module address, split on the period (`.`),
+excluding the root module.
+
+Hence, a module with an address of simply `foo` (or `root.foo`) would be
+`["foo"]`, and a module within that (so address `foo.bar`) would be read as
+`["foo", "bar"]`.
+
+[`null`](/sentinel/language/spec#null) is returned if a module address is
+invalid, or if the module is not present in the diff.
+
+As an example, given the following module block:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+If the module contained the following content:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.module(["foo"]).resources.null_resource.foo[0].applied.triggers.foo is "bar" }
+```
+
+### Value: `module_paths`
+
+* **Value Type:** List of a list of strings.
+
+The `module_paths` value within the [root namespace](#namespace-root) is a list
+of all of the modules within the Terraform diff for the current plan.
+
+Modules not present in the diff will not be present here, even if they are
+present in the configuration or state.
+
+This data is represented as a list of a list of strings, with the inner list
+being the module address, split on the period (`.`).
+
+The root module is included in this list, represented as an empty inner list, as
+long as there are changes.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The value of `module_paths` would be:
+
+```
+[
+ [],
+ ["foo"],
+]
+```
+
+And the following policy would evaluate to `true`:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.module_paths contains ["foo"] }
+```
+
+-> Note the above example only applies if the module is present in the diff.
+
+#### Iterating Through Modules
+
+Iterating through all modules to find particular resources can be useful. This
+[example][iterate-over-modules] shows how to use `module_paths` with the
+[`module()` function](#function-module-) to find all resources of a
+particular type from all modules that have pending changes using the `tfplan/v1`
+import.
+
+[iterate-over-modules]: https://developer.hashicorp.com/terraform/cloud-docs/policy-enforcement/sentinel#iterate-over-modules-and-find-resources
+
+### Value: `terraform_version`
+
+* **Value Type:** String.
+
+The `terraform_version` value within the [root namespace](#namespace-root)
+represents the version of Terraform used to create the plan. This can be used to
+enforce a specific version of Terraform in a policy check.
+
+As an example, the following policy would evaluate to `true`, as long as the
+plan was made with a version of Terraform in the 0.11.x series, excluding any
+pre-release versions (example: `-beta1` or `-rc1`):
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.terraform_version matches "^0\\.11\\.\\d+$" }
+```
+
+### Value: `variables`
+
+* **Value Type:** A string-keyed map of values.
+
+The `variables` value within the [root namespace](#namespace-root) represents
+all of the variables that were set when creating the plan. This will only
+contain variables set for the root module.
+
+Note that unlike the [`default`][import-tfconfig-variables-default] value in the
+[`tfconfig` variables namespace][import-tfconfig-variables], primitive values
+here are stringified, and type conversion will need to be performed to perform
+comparison for int, float, or boolean values. This only applies to variables
+that are primitives themselves and not primitives within maps and lists, which
+will be their original types.
+
+[import-tfconfig-variables-default]: /sentinel/features/terraform/tfconfig-v1#value-default
+
+[import-tfconfig-variables]: /sentinel/features/terraform/tfconfig-v1#namespace-variables
+
+If a default was accepted for the particular variable, the default value will be
+populated here.
+
+As an example, given the following variable blocks:
+
+```hcl
+variable "foo" {
+ default = "bar"
+}
+
+variable "number" {
+ default = 42
+}
+
+variable "map" {
+ default = {
+ foo = "bar"
+ number = 42
+ }
+}
+```
+
+The following policy would evaluate to `true`, if no values were entered to
+change these variables:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+default_foo = rule { tfplan.variables.foo is "bar" }
+default_number = rule { tfplan.variables.number is "42" }
+default_map_string = rule { tfplan.variables.map["foo"] is "bar" }
+default_map_int = rule { tfplan.variables.map["number"] is 42 }
+
+main = rule { default_foo and default_number and default_map_string and default_map_int }
+```
+
+## Namespace: Module
+
+The **module namespace** can be loaded by calling
+[`module()`](#function-module-) for a particular module.
+
+It can be used to load the following child namespaces, in addition to the values
+documented below:
+
+* `data` - Loads the [resource namespace](#namespace-resources-data-sources),
+ filtered against data sources.
+* `resources` - Loads the [resource
+ namespace](#namespace-resources-data-sources), filtered against resources.
+
+### Root Namespace Aliases
+
+The root-level `data` and `resources` keys both alias to their corresponding
+namespaces within the module namespace, loaded for the root module. They are the
+equivalent of running `module([]).KEY`.
+
+### Value: `path`
+
+* **Value Type:** List of strings.
+
+The `path` value within the [module namespace](#namespace-module) contains the
+path of the module that the namespace represents. This is represented as a list
+of strings.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The following policy would evaluate to `true` _only_ if the diff had changes for
+that module:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.module(["foo"]).path contains "foo" }
+```
+
+## Namespace: Resources/Data Sources
+
+The **resource namespace** is a namespace _type_ that applies to both resources
+(accessed by using the `resources` namespace key) and data sources (accessed
+using the `data` namespace key).
+
+Accessing an individual resource or data source within each respective namespace
+can be accomplished by specifying the type, name, and resource number (as if the
+resource or data source had a `count` value in it) in the syntax
+`[resources|data].TYPE.NAME[NUMBER]`. Note that NUMBER is always needed, even if
+you did not use `count` in the resource.
+
+In addition, each of these namespace levels is a map, allowing you to filter
+based on type and name.
+
+-> The (somewhat strange) notation here of `TYPE.NAME[NUMBER]` may imply that
+the inner resource index map is actually a list, but it's not - using the square
+bracket notation over the dotted notation (`TYPE.NAME.NUMBER`) is required here
+as an identifier cannot start with a number.
+
+Some examples of multi-level access are below:
+
+* To fetch all `aws_instance.foo` resource instances within the root module, you
+ can specify `tfplan.resources.aws_instance.foo`. This would then be indexed by
+ resource count index (`0`, `1`, `2`, and so on). Note that as mentioned above,
+ these elements must be accessed using square-bracket map notation (so `[0]`,
+ `[1]`, `[2]`, and so on) instead of dotted notation.
+* To fetch all `aws_instance` resources within the root module, you can specify
+ `tfplan.resources.aws_instance`. This would be indexed from the names of
+ each resource (`foo`, `bar`, and so on), with each of those maps containing
+ instances indexed by resource count index as per above.
+* To fetch all resources within the root module, irrespective of type, use
+ `tfplan.resources`. This is indexed by type, as shown above with
+ `tfplan.resources.aws_instance`, with names being the next level down, and so
+ on.
+
+~> When [resource targeting](https://developer.hashicorp.com/terraform/cli/commands/plan#resource-targeting) is
+ in effect, `tfplan.resources` will only include the resources specified as
+ targets for the run. This may lead to unexpected outcomes if a policy expects
+ a resource to be present in the plan.
+
+Further explanation of the namespace will be in the context of resources. As
+mentioned, when operating on data sources, use the same syntax, except with
+`data` in place of `resources`.
+
+### Value: `applied`
+
+* **Value Type:** A string-keyed map of values.
+
+The `applied` value within the [resource
+namespace](#namespace-resources-data-sources) contains a "predicted"
+representation of the resource's state post-apply. It's created by merging the
+pending resource's diff on top of the existing data from the resource's state
+(if any). The map is a complex representation of these values with data going
+as far down as needed to represent any state values such as maps, lists, and
+sets.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if the resource was in the diff:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].applied.triggers.foo is "bar" }
+```
+
+-> Note that some values will not be available in the `applied` state because
+they cannot be known until the plan is actually applied. In Terraform 0.11 or
+earlier, these values are represented by a placeholder (the UUID value
+`74D93920-ED26-11E3-AC10-0800200C9A66`) and in Terraform 0.12 or later they
+are `undefined`. **In either case**, you should instead use the
+[`computed`](#value-computed) key within the [diff
+namespace](#namespace-resource-diff) to determine that a computed value will
+exist.
+
+-> If a resource is being destroyed, its `applied` value is omitted from the
+namespace and trying to fetch it will return undefined.
+
+### Value: `diff`
+
+* **Value Type:** A map of [diff namespaces](#namespace-resource-diff).
+
+The `diff` value within the [resource
+namespace](#namespace-resources-data-sources) contains the diff for a particular
+resource. Each key within the map links to a [diff
+namespace](#namespace-resource-diff) for that particular key.
+
+Note that unlike the [`applied`](#value-applied) value, this map is not complex;
+the map is only 1 level deep with each key possibly representing a diff for a
+particular complex value within the resource.
+
+See the below section for more details on the diff namespace, in addition to
+usage examples.
+
+### Value: `destroy`
+
+* **Value Type:** Boolean.
+
+The `destroy` value within the [resource
+namespace](#namespace-resources-data-sources) is `true` if a resource is being
+destroyed for _any_ reason, including cases where it's being deleted as part of
+a resource re-creation, in which case [`requires_new`](#value-requires_new) will
+also be set.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` when `null_resource.foo` is being
+destroyed:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].destroy }
+```
+
+### Value: `requires_new`
+
+* **Value Type:** Boolean.
+
+The `requires_new` value within the [resource
+namespace](#namespace-resources-data-sources) is `true` if the resource is still
+present in the configuration, but must be replaced to satisfy its current diff.
+Whenever `requires_new` is `true`, [`destroy`](#value-destroy) is also `true`.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if one of the `triggers` in
+`null_resource.foo` was being changed:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].requires_new }
+```
+
+## Namespace: Resource Diff
+
+The **diff namespace** is a namespace that represents the diff for a specific
+attribute within a resource. For details on reading a particular attribute,
+see the [`diff`](#value-diff) value in the [resource
+namespace](#namespace-resources-data-sources).
+
+### Value: `computed`
+
+* **Value Type:** Boolean.
+
+The `computed` value within the [diff namespace](#namespace-resource-diff) is
+`true` if the resource key in question depends on another value that isn't yet
+known. Typically, that means the value it depends on belongs to a resource that
+either doesn't exist yet, or is changing state in such a way as to affect the
+dependent value so that it can't be known until the apply is complete.
+
+-> Keep in mind that when using `computed` with complex structures such as maps,
+lists, and sets, it's sometimes necessary to test the count attribute for the
+structure, versus a key within it, depending on whether or not the diff has
+marked the whole structure as computed. This is demonstrated in the example
+below. Count keys are `%` for maps, and `#` for lists and sets. If you are
+having trouble determining the type of specific field within a resource, contact
+the support team.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+
+resource "null_resource" "bar" {
+ triggers = {
+ foo_id = "${null_resource.foo.id}"
+ }
+}
+```
+
+The following policy would evaluate to `true`, if the `id` of
+`null_resource.foo` was currently not known, such as when the resource is
+pending creation, or is being deleted and re-created:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.bar[0].diff["triggers.%"].computed }
+```
+
+### Value: `new`
+
+* **Value Type:** String.
+
+The `new` value within the [diff namespace](#namespace-resource-diff) contains
+the new value of a changing attribute, _if_ the value is known at plan time.
+
+-> `new` will be an empty string if the attribute's value is currently unknown.
+For more details on detecting unknown values, see [`computed`](#value-computed).
+
+Note that this value is _always_ a string, regardless of the actual type of the
+value changing. [Type conversion][ref-sentinel-type-conversion] within policy
+may be necessary to achieve the comparison needed.
+
+[ref-sentinel-type-conversion]: https://docs.hashicorp.com/sentinel/language/values#type-conversion
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`, if the resource was in the diff
+and each of the concerned keys were changing to new values:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].diff["triggers.foo"].new is "bar" }
+```
+
+### Value: `old`
+
+* **Value Type:** String.
+
+The `old` value within the [diff namespace](#namespace-resource-diff) contains
+the old value of a changing attribute.
+
+Note that this value is _always_ a string, regardless of the actual type of the
+value changing. [Type conversion][ref-sentinel-type-conversion] within policy
+may be necessary to achieve the comparison needed.
+
+If the value did not exist in the previous state, `old` will always be an empty
+string.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "baz"
+ }
+}
+```
+
+If that resource was previously in config as:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].diff["triggers.foo"].old is "bar" }
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/features/terraform/tfplan-v2.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/features/terraform/tfplan-v2.mdx
new file mode 100644
index 0000000000..61fb7b2bf2
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/features/terraform/tfplan-v2.mdx
@@ -0,0 +1,402 @@
+---
+page_title: tfplan/v2 - terraform - Features
+sidebar_current: docs-features-terraform-tfplan-v2
+description: >-
+ The tfplan import provides access to a Terraform plan. A Terraform plan is the
+ file created as a result of `terraform plan` and is the input to `terraform
+ apply`. The plan represents the changes that Terraform needs to make to
+ infrastructure to reach the desired state represented by the configuration.
+layout: docs
+---
+
+# Import: tfplan/v2
+
+The `tfplan/v2` import provides access to a Terraform plan.
+
+A Terraform plan is the file created as a result of `terraform plan` and is the
+input to `terraform apply`. The plan represents the changes that Terraform needs
+to make to infrastructure to reach the desired state represented by the
+configuration.
+
+In addition to the diff data available in the plan, there is a "planned state"
+that is available through this import, via the
+[`planned_values`](#the-planned_values-collection) collection. This collection
+presents the Terraform state as how it might look after the plan data is
+applied, but is not guaranteed to be the final state.
+
+The data in the `tfplan/v2` import is sourced from the JSON configuration file
+that is generated by the [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) command. For more information on
+the file format, see the [JSON Output Format](https://developer.hashicorp.com/terraform/internals/json-format)
+page.
+
+The entirety of the JSON output file is exposed as a Sentinel map via the
+[`raw`](#the-raw-collection) collection. This allows direct, low-level access to
+the JSON data, but should only be used in complex situations where the
+higher-level collections do not serve the purpose.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfplan/v2" {
+ config = {
+ "plan_path": "./path/to/plan.json"
+ }
+}
+```
+
+## Import Overview
+
+The `tfplan/v2` import is structured as a series of _collections_, keyed as a
+specific format depending on the collection.
+
+```
+tfplan/v2
+├── terraform_version (string)
+├── variables
+│ └── (indexed by name)
+│ ├── name (string)
+│ └── value (value)
+├── planned_values
+│ ├── outputs (tfstate/v2 outputs representation)
+│ └── resources (tfstate/v2 resources representation)
+├── resource_changes
+│ └── (indexed by address[:deposed])
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── index (float (number) or string)
+│ ├── provider_name (string)
+│ ├── deposed (string)
+│ └── change (change representation)
+├── resource_drift
+│ └── (indexed by address[:deposed])
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── index (float (number) or string)
+│ ├── provider_name (string)
+│ ├── deposed (string)
+│ └── change (change representation)
+├── output_changes
+│ └── (indexed by name)
+│ ├── name (string)
+│ └── change (change representation)
+└── raw (map)
+```
+
+The collections are:
+
+* [`variables`](#the-variables-collection) - The values of variables that have
+ been set in the plan itself. This collection only contains variables set in
+ the root module.
+* [`planned_values`](#the-planned_values-collection) - The state representation
+ of _planned values_, or an estimation of what the state will look like after
+ the plan is applied.
+* [`resource_changes`](#the-resource_changes-and-resource_drift-collections) - The set of change
+ operations for resources and data sources within this plan.
+* [`resource_drift`](#the-resource_changes-and-resource_drift-collections) - A description of the
+ changes Terraform detected when it compared the most recent state to the prior saved state.
+* [`output_changes`](#the-output_changes-collection) - The changes to outputs
+ within this plan. This collection only contains outputs set in the root
+ module.
+* [`raw`](#the-raw-collection) - Access to the raw plan data.
+
+These collections are specifically designed to be used with the
+[`filter`](/sentinel/language/collection-operations#filter-expression)
+quantifier expression in Sentinel, so that one can collect a list of resources
+to perform policy checks on without having to write complex discovery code. As
+an example, the following code will return all `aws_instance` resource changes,
+across all modules in the plan:
+
+```
+all_aws_instances = filter tfplan.resource_changes as _, rc {
+ rc.mode is "managed" and
+ rc.type is "aws_instance"
+}
+```
+
+You can add specific attributes to the filter to narrow the search, such as the
+module address, or the operation being performed. The following code would
+return resources in a module named `foo` only, and further narrow the search
+down to only resources that were being created:
+
+```
+all_aws_instances = filter tfplan.resource_changes as _, rc {
+ rc.module_address is "module.foo" and
+ rc.mode is "managed" and
+ rc.type is "aws_instance" and
+ rc.change.actions is ["create"]
+}
+```
+
+### Change Representation
+
+Certain collections in this import contain a _change representation_, an object
+with details about changes to a particular entity, such as a resource (within
+the [`resource_changes`](#the-resource_changes-collection) collection), or
+output (within the [`output_changes`](#the-output_changes-collection)
+collection).
+
+```
+(change representation)
+├── actions (list)
+├── before (value, or map)
+├── after (value, or map)
+└── after_unknown (boolean, or map of booleans)
+```
+
+This change representation contains the following fields:
+
+* `actions` - A list of actions being carried out for this change. The order is
+ important, for example a regular replace operation is denoted by `["delete",
+ "create"]`, but a
+ [`create_before_destroy`](https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle#create_before_destroy)
+ resource will have an operation order of `["create", "delete"]`.
+* `before` - The representation of the resource data object value before the
+ action. For create-only actions, this is unset. For no-op actions, this value
+ will be identical with `after`.
+* `after` - The representation of the resource data object value after the
+ action. For delete-only actions, this is unset. For no-op actions, this value
+ will be identical with `before`. Note that unknown values will not show up in
+ this field.
+* `after_unknown` - A deep object of booleans that denotes any values that are
+ unknown in a resource. These values were previously referred to as "computed"
+ values. If the value cannot be found in this map, then its value should be
+ available within `after`, so long as the operation supports it.
+
+#### Actions
+
+As mentioned above, actions show up within the `actions` field of a change
+representation and indicate the type of actions being performed as part of the
+change, and the order that they are being performed in.
+
+The current list of actions are as follows:
+
+* `create` - The action will create the associated entity. Depending on the
+ order this appears in, the entity may be created alongside a copy of the
+ entity before replacing it.
+* `read` - The action will read the associated entity. In practice, seeing this
+ change type should be rare, as reads generally happen before a plan is
+ executed (usually during a refresh).
+* `update` - The action will update the associated entity in a way that alters its state
+ in some way.
+* `delete` - The action will remove the associated entity, deleting any
+ applicable state and associated real resources or infrastructure.
+* `no-op` - No action will be performed on the associated entity.
+
+The `actions` field is a list, as some real-world actions are actually a
+composite of more than one primitive action. At this point in time, this
+is generally only applicable to resource replacement, in which the following
+action orders apply:
+
+* **Normal replacement:** `["delete", "create"]` - Applies to default lifecycle
+ configurations.
+* **Create-before-destroy:** `["create", "delete"]` - Applies when
+ [`create_before_destroy`](https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle#create_before_destroy)
+ is used in a lifecycle configuration.
+
+Note that, in most situations, the plan will list all "changes", including no-op
+changes. This makes filtering on change type crucial to the accurate selection
+of data if you are concerned with the state change of a particular resource.
+
+To filter on a change type, use exact list comparison. For example, the
+following example from the [Import Overview](#import-overview) filters on
+exactly the resources being created _only_:
+
+```
+all_aws_instances = filter tfplan.resource_changes as _, rc {
+ rc.module_address is "module.foo" and
+ rc.mode is "managed" and
+ rc.type is "aws_instance" and
+ rc.change.actions is ["create"]
+}
+```
+
+#### `before`, `after`, and `after_unknown`
+
+The exact attribute changes for a particular operation are outlined in the
+`before` and `after` attributes. Depending on the entity being operated on, this
+will either be a map (as with
+[`resource_changes`](#the-resource_changes-collection)) or a singular value (as
+with [`output_changes`](#the-output_changes-collection)).
+
+What you can expect in these fields varies depending on the operation:
+
+* For fresh create operations, `before` will generally be `null`, and `after`
+ will contain the data you can expect to see after the change.
+* For full delete operations, this will be reversed - `before` will contain
+ data, and `after` will be `null`.
+* Update or replace operations will have data in both fields relevant to their
+ states before and after the operation.
+* No-op operations should have identical data in `before` and `after`.
+
+For resources, if a field cannot be found in `after`, it generally means one of
+two things:
+
+* The attribute does not exist in the resource schema. Generally, known
+ attributes that do not have a value will show up as `null` or otherwise empty
+ in `after`.
+* The attribute is _unknown_, that is, it was unable to be determined at plan
+ time and will only be available after apply-time values have been able to be
+ calculated.
+
+In the latter case, there should be a value for the particular attribute in
+`after_unknown`, which can be checked to assert that the value is indeed
+unknown, versus invalid:
+
+```
+import "tfplan/v2" as tfplan
+
+no_unknown_amis = rule {
+ all filter tfplan.resource_changes as _, rc {
+ rc.module_address is "module.foo" and
+ rc.mode is "managed" and
+ rc.type is "aws_instance" and
+ rc.change.actions is ["create"]
+ } as _, rc {
+ rc.change.after_unknown.ami else false is false
+ }
+}
+```
+
+For output changes, `after_unknown` will simply be `true` if the value won't be
+known until the plan is applied.
+
+## The `terraform_version` Value
+
+The top-level `terraform_version` value in this import gives the Terraform
+version that made the plan. This can be used to do version validation.
+
+```
+import "tfplan/v2" as tfplan
+import "strings"
+
+v = strings.split(tfplan.terraform_version, ".")
+version_major = int(v[1])
+version_minor = int(v[2])
+
+main = rule {
+ version_major is 12 and version_minor >= 19
+}
+```
+
+## The `variables` Collection
+
+The `variables` collection is a collection of the variables set in the root
+module when creating the plan.
+
+This collection is indexed on the name of the variable.
+
+The valid values are:
+
+* `name` - The name of the variable, also used as the collection key.
+* `value` - The value of the variable assigned during the plan.
+
+## The `planned_values` Collection
+
+The `planned_values` collection is a special collection in that it contains two
+fields that alias to state collections with the _planned_ state set. This is the
+best prediction of what the state will look like after the plan is executed.
+
+The two fields are:
+
+* `outputs` - The prediction of what output values will look like after the
+ state is applied. For more details on the structure of this collection, see
+ the [`outputs`](/sentinel/features/terraform/tfstate-v2#the-outputs-collection) collection in the
+ [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2) documentation.
+* `resources` - The prediction of what resource values will look like after the
+ state is applied. For more details on the structure of this collection, see
+ the [`resources`](/sentinel/features/terraform/tfstate-v2#the-resources-collection) collection in the
+ [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2) documentation.
+
+-> **NOTE:** Unknown values are omitted from the `planned_values` state
+representations, regardless of whether or not they existed before. Use
+[`resource_changes`](#the-resource_changes-collection) if awareness of unknown
+data is important.
+
+## The `resource_changes` and `resource_drift` Collections
+
+The `resource_changes` and `resource_drift` collections are a set of change operations for resources
+and data sources within this plan.
+
+The `resource_drift` collection provides a description of the changes Terraform detected
+when it compared the most recent state to the prior saved state.
+
+The `resource_changes` collection includes all resources that have been found in the configuration and state,
+regardless of whether or not they are changing.
+
+~> When [resource targeting](/terraform/cli/commands/plan#resource-targeting) is in effect, the `resource_changes` collection will only include the resources specified as targets for the run. This may lead to unexpected outcomes if a policy expects a resource to be present in the plan. To prohibit targeted runs altogether, ensure [`tfrun.target_addrs`](/terraform/cloud-docs/policy-enforcement/sentinel/import/tfrun#value-target_addrs) is undefined or empty.
+
+This collection is indexed on the complete resource address as the key. If
+`deposed` is non-empty, it is appended to the end, and may look something like
+`aws_instance.foo:deposed-abc123`.
+
+An element contains the following fields:
+
+* `address` - The absolute resource address - also the key for the collection's
+ index, if `deposed` is empty.
+
+* `module_address` - The module portion of the absolute resource address.
+
+* `mode` - The resource mode, either `managed` (resources) or `data` (data
+ sources).
+
+* `type` - The resource type, example: `aws_instance` for `aws_instance.foo`.
+
+* `name` - The resource name, example: `foo` for `aws_instance.foo`.
+
+* `index` - The resource index. Can be either a number or a string.
+
+* `provider_name` - The name of the provider this resource belongs to. This
+ allows the provider to be interpreted unambiguously in the unusual situation
+ where a provider offers a resource type whose name does not start with its own
+ name, such as the `googlebeta` provider offering `google_compute_instance`.
+
+ -> **Note:** Starting with Terraform 0.13, the `provider_name` field contains the
+ _full_ source address to the provider in the Terraform Registry. Example:
+ `registry.terraform.io/hashicorp/null` for the null provider.
+
+* `deposed` - An identifier used during replacement operations, and can be used
+ to identify the exact resource being replaced in state.
+
+* `change` - The data describing the change that will be made to this resource.
+ For more details, see [Change Representation](#change-representation).
+
+## The `output_changes` Collection
+
+The `output_changes` collection is a collection of the change operations for
+outputs within this plan.
+
+Only outputs for the root module are included.
+
+This collection is indexed by the name of the output. The fields in a collection
+value are below:
+
+* `name` - The name of the output, also the index key.
+* `change` - The data describing the change that will be made to this output.
+ For more details, see [Change Representation](#change-representation).
+
+## The `raw` Collection
+
+The `raw` collection exposes the raw, unprocessed plan data.
+
+This is the same data that is produced by [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) on the plan file for the run this
+policy check is attached to.
+
+Use of this data is only recommended in expert situations where the data the
+collections present may not exactly serve the needs of the policy. For more
+information on the file format, see the [JSON Output
+Format](https://developer.hashicorp.com/terraform/internals/json-format) page.
+
+-> **NOTE:** Although designed to be relatively stable, the actual makeup for
+the JSON output format is a Terraform CLI concern and as such not managed by
+Sentinel. Use at your own risk, follow the [Terraform CLI
+project](https://github.com/hashicorp/terraform), and watch the file format
+documentation for any changes.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/features/terraform/tfstate-v1.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/features/terraform/tfstate-v1.mdx
new file mode 100644
index 0000000000..e36d529f2e
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/features/terraform/tfstate-v1.mdx
@@ -0,0 +1,552 @@
+---
+page_title: tfstate/v1 - terraform - Features
+sidebar_current: docs-features-terraform-tfstate-v1
+description: The tfstate/v1 import provides access to a Terraform state.
+layout: docs
+---
+
+# Import: tfstate/v1
+
+The `tfstate/v1` import provides access to the Terraform state.
+
+The _state_ is the data that Terraform has recorded about a workspace at a
+particular point in its lifecycle, usually after an apply. You can read more
+general information about how Terraform uses state [here][ref-tf-state].
+
+[ref-tf-state]: https://developer.hashicorp.com/terraform/language/state
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfstate/v1" {
+ config = {
+ "path": "./path/to/plan.json"
+ }
+}
+```
+
+## Namespace Overview
+
+The following is a tree view of the import namespace. For more detail on a
+particular part of the namespace, see below.
+
+-> Note that the root-level alias keys shown here (`data`, `outputs`, `path`,
+and `resources`) are shortcuts to a [module namespace](#namespace-module) scoped
+to the root module. For more details, see the section on [root namespace
+aliases](#root-namespace-aliases).
+
+```
+tfstate/v1
+├── module() (function)
+│ └── (module namespace)
+│ ├── path ([]string)
+│ ├── data
+│ │ └── TYPE.NAME[NUMBER]
+│ │ ├── attr (map of keys)
+│ │ ├── depends_on ([]string)
+│ │ ├── id (string)
+│ │ └── tainted (boolean)
+│ ├── outputs (root module only in TF 0.12 or later)
+│ │ └── NAME
+│ │ ├── sensitive (bool)
+│ │ ├── type (string)
+│ │ └── value (value)
+│ └── resources
+│ └── TYPE.NAME[NUMBER]
+│ ├── attr (map of keys)
+│ ├── depends_on ([]string)
+│ ├── id (string)
+│ └── tainted (boolean)
+│
+├── module_paths ([][]string)
+├── terraform_version (string)
+│
+├── data (root module alias)
+├── outputs (root module alias)
+├── path (root module alias)
+└── resources (root module alias)
+```
+
+## Namespace: Root
+
+The root-level namespace consists of the values and functions documented below.
+
+In addition to this, the root-level `data`, `outputs`, `path`, and `resources`
+keys alias to their corresponding namespaces or values within the [module
+namespace](#namespace-module).
+
+### Function: `module()`
+
+```
+module = func(ADDR)
+```
+
+* **Return Type:** A [module namespace](#namespace-module).
+
+The `module()` function in the [root namespace](#namespace-root) returns the
+[module namespace](#namespace-module) for a particular module address.
+
+The address must be a list and is the module address, split on the period (`.`),
+excluding the root module.
+
+Hence, a module with an address of simply `foo` (or `root.foo`) would be
+`["foo"]`, and a module within that (so address `foo.bar`) would be read as
+`["foo", "bar"]`.
+
+[`null`][ref-null] is returned if a module address is invalid, or if the module
+is not present in the state.
+
+[ref-null]: /sentinel/language/spec#null
+
+As an example, given the following module block:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+If the module contained the following content:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if the resource was present in
+the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.module(["foo"]).resources.null_resource.foo[0].attr.triggers.foo is "bar" }
+```
+
+### Value: `module_paths`
+
+* **Value Type:** List of a list of strings.
+
+The `module_paths` value within the [root namespace](#namespace-root) is a list
+of all of the modules within the Terraform state at plan-time.
+
+Modules not present in the state will not be present here, even if they are
+present in the configuration or the diff.
+
+This data is represented as a list of a list of strings, with the inner list
+being the module address, split on the period (`.`).
+
+The root module is included in this list, represented as an empty inner list, as
+long as it is present in state.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The value of `module_paths` would be:
+
+```
+[
+ [],
+ ["foo"],
+]
+```
+
+And the following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.module_paths contains ["foo"] }
+```
+
+-> Note the above example only applies if the module is present in the state.
+
+#### Iterating Through Modules
+
+Iterating through all modules to find particular resources can be useful. This
+[example][iterate-over-modules] shows how to use `module_paths` with the
+[`module()` function](#function-module-) to find all resources of a
+particular type from all modules using the `tfplan` import. By changing `tfplan`
+in this function to `tfstate`, you could make a similar function find all
+resources of a specific type in the current state.
+
+[iterate-over-modules]: https://developer.hashicorp.com/terraform/cloud-docs/policy-enforcement/sentinel#sentinel-imports
+
+### Value: `terraform_version`
+
+* **Value Type:** String.
+
+The `terraform_version` value within the [root namespace](#namespace-root)
+represents the version of Terraform in use when the state was saved. This can be
+used to enforce a specific version of Terraform in a policy check.
+
+As an example, the following policy would evaluate to `true` as long as the
+state was made with a version of Terraform in the 0.11.x series, excluding any
+pre-release versions (example: `-beta1` or `-rc1`):
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.terraform_version matches "^0\\.11\\.\\d+$" }
+```
+
+-> **NOTE:** This value is also available via the [`tfplan`](/sentinel/features/terraform/tfplan-v1)
+import, which will be more current when a policy check is run against a plan.
+It's recommended you use the value in `tfplan` until HCP Terraform
+supports policy checks in other stages of the workspace lifecycle. See the
+[`terraform_version`][import-tfplan-terraform-version] reference within the
+`tfplan` import for more details.
+
+[import-tfplan-terraform-version]: /sentinel/features/terraform/tfplan-v1#value-terraform_version
+
+## Namespace: Module
+
+The **module namespace** can be loaded by calling
+[`module()`](#function-module-) for a particular module.
+
+It can be used to load the following child namespaces, in addition to the values
+documented below:
+
+* `data` - Loads the [resource namespace](#namespace-resources-data-sources),
+ filtered against data sources.
+* `outputs` - Loads the [output namespace](#namespace-outputs), which supply the
+ outputs present in this module's state. Note that with Terraform 0.12 or
+ later, this value is only available for the root namespace.
+* `resources` - Loads the [resource
+ namespace](#namespace-resources-data-sources), filtered against resources.
+
+### Root Namespace Aliases
+
+The root-level `data`, `outputs`, and `resources` keys both alias to their
+corresponding namespaces within the module namespace, loaded for the root
+module. They are the equivalent of running `module([]).KEY`.
+
+### Value: `path`
+
+* **Value Type:** List of strings.
+
+The `path` value within the [module namespace](#namespace-module) contains the
+path of the module that the namespace represents. This is represented as a list
+of strings.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The following policy would evaluate to `true`, _only_ if the module was present
+in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.module(["foo"]).path contains "foo" }
+```
+
+## Namespace: Resources/Data Sources
+
+The **resource namespace** is a namespace _type_ that applies to both resources
+(accessed by using the `resources` namespace key) and data sources (accessed
+using the `data` namespace key).
+
+Accessing an individual resource or data source within each respective namespace
+can be accomplished by specifying the type, name, and resource number (as if the
+resource or data source had a `count` value in it) in the syntax
+`[resources|data].TYPE.NAME[NUMBER]`. Note that NUMBER is always needed, even if
+you did not use `count` in the resource.
+
+In addition, each of these namespace levels is a map, allowing you to filter
+based on type and name.
+
+-> The (somewhat strange) notation here of `TYPE.NAME[NUMBER]` may imply that
+the inner resource index map is actually a list, but it's not - using the square
+bracket notation over the dotted notation (`TYPE.NAME.NUMBER`) is required here
+as an identifier cannot start with number.
+
+Some examples of multi-level access are below:
+
+* To fetch all `aws_instance.foo` resource instances within the root module, you
+ can specify `tfstate.resources.aws_instance.foo`. This would then be indexed
+ by resource count index (`0`, `1`, `2`, and so on). Note that as mentioned
+ above, these elements must be accessed using square-bracket map notation (so
+ `[0]`, `[1]`, `[2]`, and so on) instead of dotted notation.
+* To fetch all `aws_instance` resources within the root module, you can specify
+ `tfstate.resources.aws_instance`. This would be indexed from the names of
+ each resource (`foo`, `bar`, and so on), with each of those maps containing
+ instances indexed by resource count index as per above.
+* To fetch all resources within the root module, irrespective of type, use
+ `tfstate.resources`. This is indexed by type, as shown above with
+ `tfstate.resources.aws_instance`, with names being the next level down, and so
+ on.
+
+Further explanation of the namespace will be in the context of resources. As
+mentioned, when operating on data sources, use the same syntax, except with
+`data` in place of `resources`.
+
+### Value: `attr`
+
+* **Value Type:** A string-keyed map of values.
+
+The `attr` value within the [resource
+namespace](#namespace-resources-data-sources) is a direct mapping to the state
+of the resource.
+
+The map is a complex representation of these values with data going as far down
+as needed to represent any state values such as maps, lists, and sets.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if the resource was in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.resources.null_resource.foo[0].attr.triggers.foo is "bar" }
+```
+
+### Value: `depends_on`
+
+* **Value Type:** A list of strings.
+
+The `depends_on` value within the [resource
+namespace](#namespace-resources-data-sources) contains the dependencies for the
+resource.
+
+This is a list of full resource addresses, relative to the module (example:
+`null_resource.foo`).
+
+As an example, given the following resources:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+
+resource "null_resource" "bar" {
+ # ...
+
+ depends_on = [
+ "null_resource.foo",
+ ]
+}
+```
+
+The following policy would evaluate to `true` if the resource was in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.resources.null_resource.bar[0].depends_on contains "null_resource.foo" }
+```
+
+### Value: `id`
+
+* **Value Type:** String.
+
+The `id` value within the [resource
+namespace](#namespace-resources-data-sources) contains the id of the resource.
+
+-> **NOTE:** The example below uses a _data source_ here because the
+[`null_data_source`][ref-tf-null-data-source] data source gives a static ID,
+which makes documenting the example easier. As previously mentioned, data
+sources share the same namespace as resources, but need to be loaded with the
+`data` key. For more information, see the
+[synopsis](#namespace-resources-data-sources) for the namespace itself.
+
+[ref-tf-null-data-source]: https://registry.terraform.io/providers/hashicorp/null/latest/docs/data-sources/data_source
+
+As an example, given the following data source:
+
+```hcl
+data "null_data_source" "foo" {
+ # ...
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.data.null_data_source.foo[0].id is "static" }
+```
+
+### Value: `tainted`
+
+* **Value Type:** Boolean.
+
+The `tainted` value within the [resource
+namespace](#namespace-resources-data-sources) is `true` if the resource is
+marked as tainted in Terraform state.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`, if the resource was marked as
+tainted in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.resources.null_resource.foo[0].tainted }
+```
+
+## Namespace: Outputs
+
+The **output namespace** represents all of the outputs present within a
+[module](#namespace-module). Outputs are present in a state if they were saved
+during a previous apply, or if they were updated with known values during the
+pre-plan refresh.
+
+**With Terraform 0.11 or earlier** this can be used to fetch both the outputs
+of the root module, and the outputs of any module in the state below the root.
+This makes it possible to see outputs that have not been threaded to the root
+module.
+
+**With Terraform 0.12 or later** outputs are available in the top-level (root
+module) namespace only and not accessible within submodules.
+
+This namespace is indexed by output name.
+
+### Value: `sensitive`
+
+* **Value Type:** Boolean.
+
+The `sensitive` value within the [output namespace](#namespace-outputs) is
+`true` when the output has been [marked as sensitive][ref-tf-sensitive-outputs].
+
+[ref-tf-sensitive-outputs]: https://developer.hashicorp.com/terraform/language/values/outputs#sensitive-suppressing-values-in-cli-output
+
+As an example, given the following output:
+
+```hcl
+output "foo" {
+ sensitive = true
+ value = "bar"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.outputs.foo.sensitive }
+```
+
+### Value: `type`
+
+* **Value Type:** String.
+
+The `type` value within the [output namespace](#namespace-outputs) gives the
+output's type. This will be one of `string`, `list`, or `map`. These are
+currently the only types available for outputs in Terraform.
+
+As an example, given the following output:
+
+```hcl
+output "string" {
+ value = "foo"
+}
+
+output "list" {
+ value = [
+ "foo",
+ "bar",
+ ]
+}
+
+output "map" {
+ value = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+type_string = rule { tfstate.outputs.string.type is "string" }
+type_list = rule { tfstate.outputs.list.type is "list" }
+type_map = rule { tfstate.outputs.map.type is "map" }
+
+main = rule { type_string and type_list and type_map }
+```
+
+### Value: `value`
+
+* **Value Type:** String, list, or map.
+
+The `value` value within the [output namespace](#namespace-outputs) is the value
+of the output in question.
+
+Note that the only valid primitive output type in Terraform is currently a
+string, which means that any int, float, or boolean value will need to be
+converted before it can be used in comparison. This does not apply to primitives
+within maps and lists, which will be their original types.
+
+As an example, given the following output blocks:
+
+```hcl
+output "foo" {
+ value = "bar"
+}
+
+output "number" {
+ value = "42"
+}
+
+output "map" {
+ value = {
+ foo = "bar"
+ number = 42
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+value_foo = rule { tfstate.outputs.foo.value is "bar" }
+value_number = rule { int(tfstate.outputs.number.value) is 42 }
+value_map_string = rule { tfstate.outputs.map.value["foo"] is "bar" }
+value_map_int = rule { tfstate.outputs.map.value["number"] is 42 }
+
+main = rule { value_foo and value_number and value_map_string and value_map_int }
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/features/terraform/tfstate-v2.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/features/terraform/tfstate-v2.mdx
new file mode 100644
index 0000000000..9b29aa2c51
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/features/terraform/tfstate-v2.mdx
@@ -0,0 +1,176 @@
+---
+page_title: tfstate/v2 - terraform - Features
+sidebar_current: docs-features-terraform-tfstate-v2
+description: The tfstate/v2 import provides access to a Terraform state.
+layout: docs
+---
+
+# Import: tfstate/v2
+
+The `tfstate/v2` import provides access to a Terraform state.
+
+The _state_ is the data that Terraform has recorded about a workspace at a
+particular point in its lifecycle, usually after an apply. You can read more
+general information about how Terraform uses state
+[here](https://developer.hashicorp.com/terraform/language/state).
+
+The data in the `tfstate/v2` import is sourced from the JSON configuration file
+that is generated by the [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) command. For more information on
+the file format, see the [JSON Output Format](https://developer.hashicorp.com/terraform/internals/json-format)
+page.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfstate/v2" {
+ config = {
+ "path": "./path/to/plan.json"
+ }
+}
+```
+
+## Import Overview
+
+The `tfstate/v2` import is structured as currently two _collections_, keyed in
+resource address and output name, respectively.
+
+```
+(tfstate/v2)
+├── terraform_version (string)
+├── resources
+│ └── (indexed by address)
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── index (float (number) or string)
+│ ├── provider_name (string)
+│ ├── values (map)
+│ ├── depends_on (list of strings)
+│ ├── tainted (boolean)
+│ └── deposed_key (string)
+└── outputs
+ └── (indexed by name)
+ ├── name (string)
+ ├── sensitive (boolean)
+ └── value (value)
+```
+
+The collections are:
+
+* [`resources`](#the-resources-collection) - The state of all resources across
+ all modules in the state.
+* [`outputs`](#the-outputs-collection) - The state of all outputs from the root module in the state.
+
+These collections are specifically designed to be used with the
+[`filter`](/sentinel/language/collection-operations#filter-expression)
+quantifier expression in Sentinel, so that one can collect a list of resources
+to perform policy checks on without having to write complex module traversal. As
+an example, the following code will return all `aws_instance` resource types
+within the state, regardless of what module they are in:
+
+```
+all_aws_instances = filter tfstate.resources as _, r {
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+You can add specific attributes to the filter to narrow the search, such as the
+module address. The following code would return resources in a module named
+`foo` only:
+
+```
+all_aws_instances = filter tfstate.resources as _, r {
+ r.module_address is "module.foo" and
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+## The `terraform_version` Value
+
+The top-level `terraform_version` value in this import gives the Terraform
+version that recorded the state. This can be used to do version validation.
+
+```
+import "tfstate/v2" as tfstate
+import "strings"
+
+v = strings.split(tfstate.terraform_version, ".")
+version_major = int(v[1])
+version_minor = int(v[2])
+
+main = rule {
+ version_major is 12 and version_minor >= 19
+}
+```
+
+## The `resources` Collection
+
+The `resources` collection is a collection representing all of the resources in
+the state, across all modules.
+
+This collection is indexed on the complete resource address as the key.
+
+An element in the collection has the following values:
+
+* `address` - The absolute resource address - also the key for the collection's
+ index.
+
+* `module_address` - The address portion of the absolute resource address.
+
+* `mode` - The resource mode, either `managed` (resources) or `data` (data
+ sources).
+
+* `type` - The resource type, example: `aws_instance` for `aws_instance.foo`.
+
+* `name` - The resource name, example: `foo` for `aws_instance.foo`.
+
+* `index` - The resource index. Can be either a number or a string.
+
+* `provider_name` - The name of the provider this resource belongs to. This
+ allows the provider to be interpreted unambiguously in the unusual situation
+ where a provider offers a resource type whose name does not start with its own
+ name, such as the `googlebeta` provider offering `google_compute_instance`.
+
+ -> **Note:** Starting with Terraform 0.13, the `provider_name` field contains the
+ _full_ source address to the provider in the Terraform Registry. Example:
+ `registry.terraform.io/hashicorp/null` for the null provider.
+
+* `values` - An object (map) representation of the attribute values of the
+ resource, whose structure depends on the resource type schema. When accessing
+ proposed state through the [`planned_values`](/sentinel/features/terraform/tfplan-v2#the-planned_values-collection)
+ collection of the tfplan/v2 import, unknown values will be omitted.
+
+* `depends_on` - The addresses of the resources that this resource depends on.
+
+* `tainted` - `true` if the resource has been explicitly marked as
+ [tainted](https://developer.hashicorp.com/terraform/cli/commands/taint) in the state.
+
+* `deposed_key` - Set if the resource has been marked deposed and will be
+ destroyed on the next apply. This matches the deposed field in the
+ [`resource_changes`](/sentinel/features/terraform/tfplan-v2#the-resource_changes-collection)
+ collection in the [`tfplan/v2`](/sentinel/features/terraform/tfplan-v2) import.
+
+## The `outputs` Collection
+
+The `outputs` collection is a collection of outputs from the root module of the
+state.
+
+Note that no child modules are included in this output set, and there is no way
+to fetch child module output values. This is to encourage the correct flow of
+outputs to the recommended root consumption level.
+
+The collection is indexed on the output name, with the following fields:
+
+* `name`: The name of the output, also the collection key.
+* `sensitive`: Whether or not the value was marked as
+ [sensitive](https://developer.hashicorp.com/terraform/language/values/outputs#sensitive-suppressing-values-in-cli-output)
+ in
+ configuration.
+* `value`: The value of the output.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/functions/append.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/functions/append.mdx
new file mode 100644
index 0000000000..81395154f6
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/functions/append.mdx
@@ -0,0 +1,46 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Append'
+sidebar_current: docs-funcs-append
+description: The built-in function `append` appends a value to the end of a list.
+layout: docs
+---
+
+# Builtin Function: append
+
+The built-in function `append` appends a value to the end of a list. The list
+is modified in-place. The return value will always be [undefined](/sentinel/language/undefined).
+
+`append` called on any value other than a list or undefined will result
+in an immediate error.
+
+Examples:
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+### Why does this function return undefined?
+
+This function returns `undefined` to avoid unintended side effects of the function in the context
+of a [rule](/sentinel/language/rules).
+
+For example, if `append` returned the list value as a result, the following policy would depend
+on the order in which rules are referenced:
+
+```sentinel
+list = []
+a = rule { append(list, 1) contains 1 }
+b = rule { append(list, 2) contains 2 }
+
+// Scenario 1: list becomes [1, 2]
+main = rule { a and b }
+
+// Scenario 2: list becomes [2, 1]
+main = rule { b and a }
+```
+
+This sort of side effect behavior introduces complex and difficult to track logical errors in programs.
+As a result, functions like this return `undefined` and modify values in-place.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/functions/compare.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/functions/compare.mdx
new file mode 100644
index 0000000000..ed55775454
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/functions/compare.mdx
@@ -0,0 +1,42 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Compare'
+sidebar_current: docs-funcs-compare
+description: The built-in function `compare` compares two values.
+layout: docs
+---
+
+# Builtin Function: compare
+
+**_compare(value1, value2)_**
+
+The built-in function `compare` compares two values. The only valid types that
+can be provided are integers, floats or strings. Strings are compared according
+to lexicographic ordering; which is comparable to alphabetical
+ordering, but also takes into account symbols.
+
+The following table provides an overview of the possible return values:
+
+| Result | Description |
+|--------|-------------|
+| -1 | `value1` is less than `value2` |
+| 0 | `value1` is equal to `value2` |
+| +1 | `value1` is greater than `value2` |
+
+## Examples
+
+```sentinel
+// ints
+compare(1, 4) // -1
+compare(1, 4) // 0
+compare(4, 1) // +1
+
+// floats
+compare(1.0, 4.0) // -1
+compare(1.0, 4.0) // 0
+compare(4.0, 1.0) // +1
+
+// strings
+compare("apple", "banana") // -1
+compare("apple", "apple") // 0
+compare("banana", "apple") // +1
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/functions/delete.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/functions/delete.mdx
new file mode 100644
index 0000000000..3b60803997
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/functions/delete.mdx
@@ -0,0 +1,25 @@
+---
+page_title: 'Sentinel Language - Builtin Function: delete'
+sidebar_current: docs-funcs-delete
+description: The built-in function `delete` deletes an element from a map.
+layout: docs
+---
+
+# Builtin Function: delete
+
+The built-in function `delete` deletes an element from a map.
+
+If the element to delete does not exist, the map is left unchanged.
+Calling `delete` on a value that is not a map or
+[undefined](/sentinel/language/undefined)
+is an immediate error. The result of `delete` is always `undefined`.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/functions/error.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/functions/error.mdx
new file mode 100644
index 0000000000..96d253b662
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/functions/error.mdx
@@ -0,0 +1,15 @@
+---
+page_title: 'Sentinel Language - Builtin Function: error'
+sidebar_current: docs-funcs-error
+description: >-
+ The built-in function `error` is used to exit immediately with an error
+ message.
+layout: docs
+---
+
+# Builtin Function: error
+
+The built-in function `error` is used to exit immediately with an error message.
+
+Please see the [page on errors](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/functions/index.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/functions/index.mdx
new file mode 100644
index 0000000000..4e12143ad0
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/functions/index.mdx
@@ -0,0 +1,13 @@
+---
+page_title: Sentinel Language - Builtin Functions
+sidebar_current: docs-funcs
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Language: Builtin Functions
+
+Builtin functions are
+[functions](/sentinel/language/functions)
+that are available in all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in function.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/functions/keys.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/functions/keys.mdx
new file mode 100644
index 0000000000..a96cafe897
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/functions/keys.mdx
@@ -0,0 +1,22 @@
+---
+page_title: 'Sentinel Language - Builtin Function: keys'
+sidebar_current: docs-funcs-keys
+description: The built-in function `keys` returns the keys of a map.
+layout: docs
+---
+
+# Builtin Function: keys
+
+The built-in function `keys` returns the keys of a map.
+
+`keys` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned keys are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/functions/length.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/functions/length.mdx
new file mode 100644
index 0000000000..8b874b14c4
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/functions/length.mdx
@@ -0,0 +1,23 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Length'
+sidebar_current: docs-funcs-length
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Builtin Function: length
+
+The built-in function `length` returns the length of a collection or string.
+
+The length of a string is the number of bytes in the string. It is not
+necessarilly the number of characters. The length of a collection is the
+number of elements in that collection. The length of
+[undefined](/sentinel/language/undefined) is `undefined`.
+
+Examples:
+
+```sentinel
+length("foo") // 3
+length([1, 2, 3, 4]) // 4
+length({ "key": "value" }) // 1
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/functions/print.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/functions/print.mdx
new file mode 100644
index 0000000000..bcf441d09d
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/functions/print.mdx
@@ -0,0 +1,13 @@
+---
+page_title: 'Sentinel Language - Builtin Function: print'
+sidebar_current: docs-funcs-print
+description: The built-in function `print` is used for logging and debugging.
+layout: docs
+---
+
+# Builtin Function: print
+
+The built-in function `print` is used for logging and debugging.
+
+Please see the [page on logging](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/functions/range.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/functions/range.mdx
new file mode 100644
index 0000000000..0f86bfc402
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/functions/range.mdx
@@ -0,0 +1,49 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Range'
+sidebar_current: docs-funcs-range
+description: The built-in function `range` returns a list of numbers in a range.
+layout: docs
+---
+
+# Builtin Function: range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it
+defaults to `1`.
+
+Negative steps are permitted and count down from `start` towards `end`, instead
+of up.
+
+The range ends when the next value given by the sum of the last value and `step`
+would exceed `end`, regardless of if it has been reached.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4] - starts at 1 instead of 0
+range(1, 5, 2) // [1,3] - 3+2 does not satisfy r[-1] < end
+range(0, -3, -1) // [0,-1,-2] - example of negative range
+```
+
+Impossible ranges, or ranges where `start` will never reach `end` given `step`,
+yield an empty list.
+
+```
+range(1, 1) // [] - already starting at 1
+range(0, 1, -1) // [] - negative step will never reach 1
+```
+
+Supplying an undefined value to any argument of `range` yields an undefined
+value back.
+
+A range with a zero step is a runtime error.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/functions/values.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/functions/values.mdx
new file mode 100644
index 0000000000..8f98d3ab56
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/functions/values.mdx
@@ -0,0 +1,22 @@
+---
+page_title: 'Sentinel Language - Builtin Function: values'
+sidebar_current: docs-funcs-values
+description: The built-in function `values` returns the values of a map.
+layout: docs
+---
+
+# Builtin Function: values
+
+The built-in function `values` returns the values of a map.
+
+`values` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned values are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+values(data) // [3, 2]
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/imports/base64.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/imports/base64.mdx
new file mode 100644
index 0000000000..4e58429268
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/imports/base64.mdx
@@ -0,0 +1,46 @@
+---
+page_title: 'Import: base64'
+sidebar_current: docs-imports-base64
+description: The base64 import enables a Sentinel policy to encode and decode Base64 values.
+layout: docs
+---
+
+# Import: base64
+
+The base64 import enables a Sentinel policy to encode and decode Base64 values.
+
+### base64.encode(str)
+
+Encodes the string `str` into a Base64 encoded string, using the standard
+encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-4).
+
+```sentinel
+enc = base64.encode("foo") // "Zm9v"
+```
+
+### base64.decode(str)
+
+Decodes the Base64 encoded string `str`, returning the string result,
+using the standard encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-4).
+
+```sentinel
+dec = base64.decode("Zm9v") // "foo"
+```
+
+### base64.urlencode(str)
+
+Encodes the string `str` into a Base64 encoded string, using the alternate
+encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-5).
+
+```sentinel
+enc = base64.urlencode("foo") // "Zm9v"
+```
+
+### base64.urldecode(str)
+
+Decodes the Base64 encoded string `str`, returning the string result, using the
+alternate encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-5).
+
+```sentinel
+dec = base64.urldecode("Zm9v") // "foo"
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/imports/collection/index.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/imports/collection/index.mdx
new file mode 100644
index 0000000000..ae6db20bbb
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/imports/collection/index.mdx
@@ -0,0 +1,207 @@
+---
+page_title: 'Import: collection'
+sidebar_current: docs-imports-collection
+description: The collection import provides useful helpers for working with maps and lists.
+layout: docs
+---
+
+# Import: collection
+
+The `collection` import provides helpers for working with [maps](/sentinel/language/maps) and [lists](/sentinel/language/lists).
+
+## filter
+
+**_filter(items, predicate)_**
+
+Calls [predicate](#predicates) for each element in collection, returning a list of elements that the
+predicate __does__ return true for.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| items | the list or map to perform the filter against. |
+| predicate | a [predicate](#predicates) function to call for each element in the collection. A response of `true` will add the element to the result list. |
+
+### Examples
+
+Return all even numbers:
+
+```sentinel playground
+import "collection"
+
+items = [2, 3, 4, 5, 6, 7, 8]
+result = collection.filter(items, func(el) {
+ return el % 2 is 0
+})
+main = result is [2, 4, 6, 8]
+```
+
+## find
+
+**_find(items, predicate)_**
+
+Find and return an element within a collection according to the provided [predicate](#predicates). If nothing is found, returns [undefined](/sentinel/language/undefined).
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| items | the list or map to perform the find against. |
+| predicate | a [predicate](#predicates) function to call for each element in the collection. A response of `true` will return the element and complete the find. |
+
+### Examples
+
+Find an element in a collection based on its value:
+
+```sentinel playground
+import "collection"
+
+items = ["foo", "bar", "qux"]
+item = collection.find(items, func(el) {
+ return el is "bar"
+})
+main = item is "bar"
+```
+
+Find an element in a collection based on its key:
+
+```sentinel playground
+import "collection"
+
+items = {"foo": 2, "bar": 4}
+item = collection.find(items, func(el, key) {
+ return key is "bar"
+})
+main = item is 4
+```
+
+## matches
+
+**_matches(items, partial)_**
+
+Compare each element in a collection against a partial map using deep comparison. Returns
+the list of elements that returned true for the partial comparison. If no matches are found, returns an empty list.
+
+### Arguments
+
+| Name | Description |
+|---------|-------------|
+| items | the list or map to perform the match against. |
+| partial | the map used for partial deep comparison against each element in the collection. |
+
+### Examples
+
+Return all items that contain `{"foo": {"bar": "wip"}}`:
+
+```sentinel playground
+import "collection"
+
+items = [
+ # This item should match
+ {
+ "foo": { "bar": "wip"},
+ "baz": "qux",
+ },
+ # This item will not match
+ {
+ "foo": "bar",
+ "baz": "bar",
+ },
+]
+result = collection.matches(items, {"foo": {"bar": "wip"}})
+main = result is [{"foo": {"bar": "wip"}, "baz": "qux"}]
+```
+
+## reduce
+
+**_reduce(items, accumulator[, initial])_**
+
+Call an accumulator function for each element in collection, supplying the
+previous accumulated value as the accumulation parameter.
+
+### Arguments
+
+| Name | Description |
+|-------------|-------------|
+| items | the list or map to perform the reduce against. |
+| accumulator | a function that is called for each element in the collection, used to accumulate the value. The first argument is the current accumulated value, the remaining arguments use the same rules as [predicate](#predicates) functions. |
+| initial | the initial value to use. It is an optional argument, and if not provided the first element in the collection is used as the initial value. |
+
+### Examples
+
+Reduce the collection by adding each element together:
+
+```sentinel playground
+import "collection"
+
+items = [1, 2, 3, 4, 5]
+result = collection.reduce(items, func(acc, el) {
+ return acc + el
+}, 0)
+main = result is 15
+```
+
+## reject
+
+**_reject(items, predicate)_**
+
+Calls [predicate](#predicates) for each element in collection, returning a list of elements that the
+predicate __does not__ return true for.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| items | the list or map to perform the reject against. |
+| predicate | a [predicate](#predicates) function to call for each element in the collection. A response of `false` will add the element to the result list. |
+
+### Examples
+
+Return all odd numbers by rejecting even ones:
+
+```sentinel playground
+import "collection"
+
+items = [2, 3, 4, 5, 6, 7, 8]
+result = collection.reject(items, func(el) {
+ return el % 2 is 0
+})
+main = result is [3, 5, 7]
+```
+
+## Predicates
+
+Some helpers accept a predicate function, which has the purpose of making an
+assertion. Each predicate may accept differing arguments and return differing
+types. If the collection is a list, the parameters will be the element and index.
+If the collection is a Map, the parameters will be the value and the key.
+
+### Examples
+
+A predicate for a list of items:
+
+```sentinel
+// including index
+func(item, index) {
+ return true
+}
+
+// excluding index
+func(item) {
+ return true
+}
+```
+
+A predicate for a map of items:
+```sentinel
+// including key
+func(value, key) {
+ return true
+}
+
+// excluding key
+func(value) {
+ return true
+}
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/imports/collection/lists.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/imports/collection/lists.mdx
new file mode 100644
index 0000000000..7804de05ca
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/imports/collection/lists.mdx
@@ -0,0 +1,124 @@
+---
+page_title: 'Import: collection/lists'
+sidebar_current: docs-imports-collection-lists
+description: The collection/lists import provides useful helpers for working with lists.
+layout: docs
+---
+
+# Import: collection/lists
+
+The `collection/lists` import provides helpers for working with [lists](/sentinel/language/lists).
+
+## concat
+
+**_concat(items, others[, ...additional])_**
+
+Join multiple lists together, returning the resulting list. This helper must
+have at least two arguments supplied. Order is important,
+as the order of arguments is the order that lists will be appended.
+
+### Arguments
+
+| Name | Description |
+|------------|-------------|
+| items | the first list to add to the resulting value |
+| others | the second list to add to the resulting value |
+| additional | any number of additional lists to add to the resulting value |
+
+### Examples
+
+Concatenate two lists:
+
+```sentinel playground
+import "collection/lists" as lists
+
+result = lists.concat([1], [2, 3])
+main = result is [1, 2, 3]
+```
+
+Concatenate many lists:
+```sentinel playground
+import "collection/lists" as lists
+
+result = lists.concat([1, 2], [10, 20], [100, 200])
+main = result is [1, 2, 10, 20, 100, 200]
+```
+
+## sort
+
+**_sort(items, sort_function)_**
+
+Sort will sort a list of elements according to the provided sort function, returning
+a new, sorted list.
+
+### Arguments
+
+| Name | Description |
+|------|-------------|
+| items | the list to sort |
+| sort_function | the function that is used to perform the sort |
+
+### Sort Function
+
+The provided sort function accepts two arguments, which can be considered as the
+`current` and `next` item. The function should return one of the following values:
+
+| Result | Description |
+|--------|-------------|
+| -1 | `current` is less than `next` |
+| 0 | `current` is equal to `next` |
+| +1 | `current` is greater than `next` |
+
+The [compare](/sentinel/functions/compare) built-in method provides a way of
+performing comparisons against integers, floats or strings to return a supported
+value.
+
+### Examples
+
+Sort a list of words:
+
+```sentinel playground
+import "collection/lists" as lists
+
+items = ["zebra", "bat", "horse"]
+result = lists.sort(items, func(x, y) {
+ return compare(x, y)
+})
+main = result is ["bat", "horse", "zebra"]
+```
+
+Sort a list of objects by a key:
+
+```sentinel playground
+import "collection/lists" as lists
+
+items = [{"foo": 8}, {"foo": 4}, {"foo": 100}]
+result = lists.sort(items, func(x, y) {
+ return compare(x.foo, y.foo)
+})
+main = result is [{"foo": 4}, {"foo": 8}, {"foo": 100}]
+```
+
+## sum
+
+**_sum(items)_**
+
+Sum will add all elements within the provided list. The
+list must only contain integers or floats. The return value will always be a float.
+
+### Arguments
+
+| Name | Description |
+|------|-------------|
+| items | the list to sum |
+
+### Examples
+
+Perform a sum on a list of numbers:
+
+```sentinel playground
+import "collection/lists" as lists
+
+items = [2, 5, 10, 500]
+main = lists.sum(items) is 517
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/imports/collection/maps.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/imports/collection/maps.mdx
new file mode 100644
index 0000000000..b8fbb35d55
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/imports/collection/maps.mdx
@@ -0,0 +1,252 @@
+---
+page_title: 'Import: collection/maps'
+sidebar_current: docs-imports-collection-maps
+description: The collection/maps import provides useful helpers for working with maps.
+layout: docs
+---
+
+# Import: collection/maps
+
+The `collection/maps` import provides helpers for working with [maps](/sentinel/language/maps).
+
+## get
+
+**_get(object, path[, default])_**
+
+Get the value from the provided object using the [path](#paths).
+When the path is invalid or the object doesn't contain a value at the path, then the default is returned. The default return value is undefined.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the get against. |
+| path | a [path](#paths) to a key within object to retreive the value |
+| default | an optional value that will return if the path does not exist or returns undefined |
+
+### Examples
+
+Get a value from a simple object:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+item = maps.get(object, "foo")
+main = item is "bar"
+```
+
+Get a nested value from a complex object:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [{"bar": {"baz": 4}}]}
+item = maps.get(object, "foo.0.bar.baz")
+main = item is 4
+```
+
+Get a list of values using [splat](#splat):
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [{"bar": 4}, {"bar": 8}, {"bar": 45}]}
+item = maps.get(object, "foo.*.bar")
+main = item is [4, 8, 45]
+```
+
+Get an invalid path, providing a default:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {}
+item = maps.get(object, "foo", "bar")
+main = item is "bar"
+```
+
+Get an invalid path, not providing a default:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {}
+item = maps.get(object, "foo")
+main = item is not defined
+```
+
+## has
+
+**_has(object, path)_**
+
+Return a boolean value if the object has the path within its structure.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the has against. |
+| path | a [path](#paths) to a key within object to determine if it exists |
+
+### Examples
+
+An object has a valid key:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+main = maps.has(object, "foo")
+```
+
+An object does not have a valid key:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {}
+main = maps.has(object, "foo") is false
+```
+
+## has
+
+**_pick(object, paths)_**
+
+Return a map consisting of each value found in object from the paths provided.
+The original structure of the map is maintained, as seen in the below examples.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the has against. |
+| paths | a list of [paths](#paths), each used to select a value from the object. |
+
+### Examples
+
+Picking a single value:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+main = maps.pick(object, ["foo"]) is {"foo": "bar"}
+```
+
+Picking multiple values:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar", "baz": "qux", "nit": "nat"}
+main = maps.pick(object, ["foo", "baz"]) is {"foo": "bar", "baz": "qux"}
+```
+
+Picking deep values:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {
+ "foo": "bar",
+ "baz": [
+ { "qux": 4 },
+ { "qux": 10 }
+ ],
+ "nit": {
+ "nat": "pak"
+ }
+}
+main = maps.pick(object, ["baz.1.qux", "nit.nat"]) is {"baz": [{"qux": 10}], "nit": {"nat": "pak"}}
+```
+
+## set
+
+**_set(object, path, value)_**
+
+Return a new map, assigning the provided value to the provided path. It will
+not modify the provided map in place.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the set against. |
+| path | the [path](#paths) to assign value |
+| value | the value to be assigned |
+
+### Examples
+
+Set a value on a simple path:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+object = maps.set(object, "foo", "qux")
+main = object is {"foo": "qux"}
+```
+
+Set a value on a deep path:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {}
+object = maps.set(object, "foo.bar.baz", 10)
+main = object is {"foo": { "bar": { "baz": 10 } } }
+```
+
+Set a value on an index in a list:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [ 2, 5 ] }
+object = maps.set(object, "foo.0", 10)
+main = object is {"foo": [ 10, 5 ] }
+```
+
+Set a value on every key within a list key:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [ { "bar": true }, { "bar": false } ] }
+object = maps.set(object, "foo.*.bar", true)
+main = object is {"foo": [ { "bar": true }, { "bar": true } ] }
+```
+
+## Paths
+
+Map helpers often receive a **path** argument that allows for looking up a nested
+key. Generally speaking, a path is a series of keys separated by `.`, however there
+are some additional capabilites that need to be explained.
+
+### Lists
+
+When traversing a nested map that contains a list, a specific index can be retrieved
+by providing the index as the part of the path.
+
+In the following code sample, the path will first enter the key `"foo"` within the
+map, followed by entering the first index of the list.
+
+```sentinel
+path = "foo.0"
+object = {"foo": [1]}
+```
+
+### Splat
+
+To provide advanced capabilities when using paths, you can also use the splat (`*`)
+operator to iterate through **all** elements in a list, with all parts of the path
+following the splat occuring on each entry.
+
+In the following code sample, the path will first enter the key `"foo"` within the map.
+It will then enter each item in the list, entering the `"bar"` key for each nested object.
+
+```sentinel
+path = "foo.*.bar"
+object = {"foo": [{"bar": 1}, {"bar": 2}]}
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/imports/decimal.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/imports/decimal.mdx
new file mode 100644
index 0000000000..a6633a38e0
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/imports/decimal.mdx
@@ -0,0 +1,318 @@
+---
+page_title: 'Import: decimal'
+sidebar_current: docs-imports-decimal
+description: |-
+ The decimal import provides functions for operating on numbers represented
+ as decimals.
+layout: docs
+---
+
+# Import: decimal
+
+The decimal import provides functions for operating on numbers represented
+as decimals.
+
+Binary floating-point numbers provided by the `float` type are unable to
+fully represent numbers like `1.1` and `2.2`. The decimal import can
+represent these numbers exactly, and so should be used when exactness is
+required.
+
+Decimals are created through the `new` function, which takes any type that
+can represent a number. Arguments to the decimal operators can also take
+a value of any of these types. The full list is:
+
+- float
+- int
+- string
+- existing decimal value
+
+### decimal.infinity(sign)
+
+Creates an infinite-value decimal. Infinite-value decimals are always larger
+or smaller, depending on sign, than any non-infinite decimals.
+
+```sentinel
+decimal.infinity(1) // +Infinity
+decimal.infinity(-1) // -Infinity
+```
+
+Also available as `decimal.inf`.
+
+### decimal.nan
+
+A decimal representing a "not-a-number" value. NaNs are not equal to any
+other number, even themselves.
+
+```sentinel
+decimal.new(-1).sqrt() // NaN
+decimal.new(0).mul(decimal.inf(1)) // NaN
+decimal.nan.is(decimal.nan) // false
+```
+
+### decimal.is_infinite(d)
+
+Evaluates to true if the decimal is infinite.
+
+```sentinel
+decimal.is_infinite(100) // false
+decimal.is_infinite("Infinity") // true
+```
+
+Also available as `decimal.is_inf`, `decimal.isinf` and `decimal.isinfinite`.
+
+### decimal.is_nan(d)
+
+Evaluates to true if the decimal is not-a-number.
+
+```sentinel
+decimal.is_nan("NaN") // true
+```
+
+Also available as `decimal.isnan`.
+
+### decimal.new(v)
+
+Constructs a decimal from another value.
+
+```sentinel
+decimal.new("1.1") // Constructs a decimal from a string.
+decimal.new(2.2) // Constructs a decimal from a float.
+decimal.new(3) // Constructs a decimal from an integer.
+decimal.new("1.1234E+400") // Constructs a decimal from a string using E-notation.
+```
+
+## Type: number
+
+Numbers are represented internally by a sign, coefficient, and exponent.
+The value is calculated with the formula `sign * coefficient * 10^exponent`.
+Exponent is limited to 32 bits, and the coefficient is limited by the total
+precision.
+
+The maximum precision a number can represent is 100 digits. This includes
+both before and after the decimal place.
+
+### number.string
+
+A string representation of the decimal.
+
+```sentinel
+decimal.new(1.5).string // 1.5
+```
+
+### number.sign
+
+A number between `-1` and `+1` representing the sign of the decimal.
+
+```sentinel
+decimal.new(1.5).sign // 1
+```
+
+### number.coefficient
+
+The coefficient component of the decimal.
+
+```sentinel
+decimal.new(1.5).coefficient // 15
+```
+
+### number.exponent
+
+The exponent component of the decimal.
+
+```sentinel
+decimal.new(1.5).exponent // -1
+```
+
+### number.float
+
+A floating-point representation of the decimal.
+
+```sentinel
+decimal.new(1.5).float // 1.500000
+```
+
+### number.int
+
+An integer representation of the decimal. This always rounds down to the nearest integer.
+
+```sentinel
+decimal.new(1.5).int // 1
+```
+
+### number.is(v)
+
+Test for equality with another value.
+
+```sentinel
+decimal.new(1.5).is(1) // false
+```
+
+### number.is_not(v)
+
+Test for inequality with another value.
+
+```sentinel
+decimal.new(1.5).is_not(1) // true
+```
+
+### number.less_than(v)
+
+Test that the decimal is less than another value.
+Can also be called as `number.lt(v)`
+
+```sentinel
+decimal.new(1.5).less_than(1) // false
+```
+
+### number.less_than_or_equals(v)
+
+Test that the decimal is less than or equals to another value.
+Can also be called as `number.lte(v)`
+
+```sentinel
+decimal.new(1.5).less_than_or_equals(1) // false
+```
+
+### number.greater_than(v)
+
+Test that the decimal is greater than another value.
+Can also be called as `number.gt(v)`
+
+```sentinel
+decimal.new(1.5).greater_than(1) // true
+```
+
+### number.greater_than_or_equals(v)
+
+Test that the decimal is greater than or equals to another value.
+Can also be called as `number.gte(v)`
+
+```sentinel
+decimal.new(1.5).greater_than_or_equals(1) // true
+```
+
+### number.add(v)
+
+Add another number to the decimal.
+
+```sentinel
+decimal.new(1.5).add(1) // 2.5
+```
+
+### number.subtract(v)
+
+Subtract another number from the decimal.
+Can also be called as `number.sub(v)`
+
+```sentinel
+decimal.new(1.5).subtract(1) // 0.5
+```
+
+### number.multiply(v)
+
+Multiply the decimal by a number.
+Can also be called as `number.mul(v)`
+
+```sentinel
+decimal.new(1.5).multiply(2) // 3.0
+```
+
+### number.divide(v)
+
+Divide the decimal by a number.
+Can also be called as `number.div(v)`
+
+```sentinel
+decimal.new(3.0).divide(1.5) // 2
+```
+
+### number.modulo(v)
+
+Find the remainder after dividing the decimal by a number.
+Can also be called as `mod`, `remainder`, or `rem`.
+
+```sentinel
+decimal.new(1.5).modulo(1) // 0.5
+```
+
+### number.power(v)
+
+Raise the decimal to a power.
+Can also be called as `number.pow(v)`
+
+```sentinel
+decimal.new(1.5).power(2) // 2.25
+```
+
+### number.exp()
+
+The natural exponent of the decimal. This calculates `e^number`.
+
+```sentinel
+decimal.new(1.5).exp() // 4.4816...
+```
+
+### number.loge()
+
+The natural logarithm of the decimal.
+Can also be called as `number.ln()`
+
+```sentinel
+decimal.new(1.5).loge() // 0.4054...
+```
+
+### number.log10()
+
+The base-10 logarithm of the decimal.
+Can also be called as `number.log()`
+
+```sentinel
+decimal.new(1.5).log10() // 0.1760...
+```
+
+### number.square_root()
+
+The square root of the decimal.
+Can also be called as `number.sqrt()`
+
+```sentinel
+decimal.new(1.5).square_root() // 1.2247...
+```
+
+### number.ceiling()
+
+Round the decimal to the smallest integer greater or equal to itself.
+Can also be called as `number.ceil()`
+
+```sentinel
+decimal.new(1.5).ceiling() // 2
+```
+
+### number.floor()
+
+Round the decimal to the largest integer less than or equal to itself.
+
+```sentinel
+decimal.new(1.5).floor() // 1
+```
+
+### number.absolute()
+
+The absolute value of the decimal.
+Can also be called as `number.abs()`
+
+```sentinel
+decimal.new(1.5).absolute() // 1.5
+decimal.new(-1.5).absolute() // 1.5
+```
+
+### number.negate()
+
+The negated value of the decimal. A positive decimal will become negative,
+and a negative decimal will become positive.
+Can also be called as `number.neg()`
+
+```sentinel
+decimal.new(1.5).negate() // -1.5
+decimal.new(-1.5).negate() // 1.5
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/imports/http.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/imports/http.mdx
new file mode 100644
index 0000000000..f94ae98c27
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/imports/http.mdx
@@ -0,0 +1,370 @@
+---
+page_title: 'Import: http'
+sidebar_current: docs-imports-http
+description: The http import enables the use of HTTP-accessible data from outside the runtime in Sentinel policy rules.
+layout: docs
+---
+
+# Import: http
+
+The http import enables the use of HTTP-accessible data from outside
+the runtime in Sentinel policy rules.
+
+The simplest example of the import in use would be:
+
+```sentinel
+import "http"
+
+resp = http.get("https://example.hashicorp.com")
+main = rule { resp.body contains "something" }
+```
+
+By default, any request response that is not a successful "200 OK" HTTP
+response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information and
+how to customize this behavior.
+
+For more advanced requests which require setting request headers, use a
+[`request` type](#type-request):
+
+```sentinel
+import "http"
+import "json"
+
+param token
+
+req = http.request("https://example.hashicorp.com").with_header("Authorization", "token "+token)
+resp = json.unmarshal(http.get(req).body)
+main = rule { resp["some_key"] is true }
+```
+
+The `with_header` function above returns the `request` object with the
+`Authorization` HTTP header set to value of the variable `some_value`. Many
+of the methods within the http import API are designed around this concept
+of method chaining, allowing for complex building of HTTP requests
+encapsulated in functions. The following is an idiomatic example further
+demonstrating this pattern:
+
+```sentinel
+import "http"
+import "json"
+
+param github_user
+param github_token
+
+client = func(req) {
+ return http.with_timeout(5).with_retries(3)
+}
+
+github_request = func(path) {
+ full_url = "https://api.github.com" + path
+ return http.request(full_url).
+ with_basic_auth(github_user, github_token).
+ with_header("Accept", "application/vnd.github.v3+json").
+ with_header("Custom", "foo"),
+}
+
+default_response = client.get(github_request("/example"))
+
+# Override the Custom header for this single request
+custom_header_response = json.unmarshal(
+ client.get(
+ github_request("/example").with_header("Custom", "bar"),
+ ),
+)
+
+# Override the timeout value for a known slower request
+slow_response = json.unmarshal(
+ client.with_timeout(15).get(github_request("/slow-endpoint")),
+)
+
+some_key_is_true = rule { json.unmarshal(default_response.body)["some_key"] is true }
+body_contains_text = rule { custom_header_response.body contains "something" }
+response_is_ok = rule { slow_response.status_code is 200 }
+
+main = rule { some_key_is_true and body_contains_text and response_is_ok }
+```
+
+### http.client
+
+Retrieve the base HTTP client with default settings. The returned client may be
+configured by calling configuration functions on it; all of these configuration
+functions on are also aliased as top-level functions in this namespace. See
+[`client`](#type-client) below.
+
+### http.request(url)
+
+Create a new [`request`](#type-request) to the specified `url` (string). A
+`request` type enables customization of headers and other concerns before then
+providing the constructed `request` to [`get()`](#client-get-url_or_request),
+[`post()`](#client-post-url_or_request) or [`send()`](#client-send-request).
+
+```sentinel
+req = http.request("example.hashicorp.com/foo").with_header("Foo", "bar")
+resp = http.get(req)
+```
+
+### http.methodPost
+
+Returns a string containing the accepted verb for POST requests, `"POST"`.
+
+### http.methodGet
+
+Returns a string containing the accepted verb for GET requests, `"GET"`.
+
+## Type: client
+
+All of the following configuration functions on the default client are also
+aliased as top-level functions in the import. This allows a short-hand (and
+idiomatic) way of configuring a new client without having to directly access
+`http.client` each time:
+
+```sentinel
+# The following two lines are semantically the same:
+resp = http.client.with_timeout(5).get(url)
+resp = http.with_timeout(5).get(url)
+```
+
+### url_or_request
+
+The [`get()`](#client-get-url_or_request) and [`post()`](#client-post-url_or_request)
+functions accept either a URL string or a request object, noted as
+`url_or_request` throughout the documentation.
+
+When `url_or_request` is a string, a request is made with the URL
+specified by the string. To customize HTTP headers and other settings, pass
+a request. By default, a request has a timeout value of 10 seconds and 1
+retry. These settings can be adjusted with [`with_timeout()`](#clientwith_timeoutinteger) and
+[`with_retries()`](#clientwith_retriesinteger), respectively.
+
+### Redirects
+
+If the response is one of the following redirect codes, the client follows the
+redirect, up to a maximum of 10 redirects:
+
+- 301 (Moved Permanently)
+- 302 (Found)
+- 303 (See Other)
+- 307 (Temporary Redirect)
+- 308 (Permanent Redirect)
+
+If the redirect limit is exceeded, the result is a policy error.
+
+By default, after any redirects are followed (up to the maximum), any request
+response that is not a successful "200 OK" response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information
+and how to customize this behavior.
+
+### client.get(url_or_request)
+
+Issue a GET request with a URL string or a `request` type. Returns a `response`
+type.
+
+```sentinel
+import "http"
+
+resp = http.get("https://example.hashicorp.com")
+main = rule { resp.body contains "something" }
+```
+
+### client.post(url_or_request)
+
+Issue a POST request with a URL string or a `request` type. Returns a `response`
+type.
+
+```sentinel
+import "http"
+
+req = http.request("https://example.hashicorp.com")
+ .with_body(json.marshal({"foo": "bar"}))
+resp = http.post(req)
+main = rule { resp.body contains "something" }
+```
+
+### client.send(request)
+
+Make a request using the supplied `request` type. Returns a `response`.
+
+```sentinel
+import "http"
+
+req = http.request("https://example.hashicorp.com")
+resp = http.send(req)
+main = rule { resp.body contains "something" }
+```
+
+### client.accept_status_codes(list)
+
+Configures a client to not automatically cause a policy failure if
+the resulting response's status code is in `list`. Any status code received
+that is not present in this list will trigger a runtime error.
+
+By default, any request response that is not a successful "200 OK" response
+causes a policy error. This is for convenience, as the most common scenario
+by far is to receive a response and immediately signal a policy error if the
+status code is not 200. Use this scoping to specify a list of non-redirect
+HTTP codes that you wish to accept without error and evaluate the response
+yourself.
+
+Redirects are not considered final status codes in this context. That is,
+redirects are always followed up to the maximum allowed value (see [`Redirects`](#redirects))
+and the final response code in the redirect chain is validated against
+`list`.
+
+```sentinel
+resp = http.accept_status_codes([200, 404]).get(url)
+main = rule { resp.status_code is 404 }
+```
+
+### client.accepted_status_codes
+
+Returns a list of the client's accepted status codes.
+
+```sentinel
+client = http.accept_status_codes([200, 404])
+main = rule { client.accepted_status_codes contains 404 }
+```
+
+### client.with_retries(integer)
+
+Sets the maximum number of retries, specified by `integer`, that should be
+attempted on an unsuccessful request. An unsuccessful request is considered
+to be any 5xx response except `501 (Not Implemented)` or a request timeout.
+By default, one retry is attempted.
+
+The maximum number of retries is 10, for a total of 11 request attempts.
+When a request exceeds the maximum number of retries without a response, the
+result is a policy error. Specifying a number larger than this will result
+in a runtime error.
+
+Between each retry, an exponentially increasing delay is observed, allowing
+time for the server to recover. This delay starts at 1 second for the first
+retry and increases each attempt thereafter. The maximum delay for a single
+attempt is 30 seconds.
+
+Retries can be disabled by specifying 0.
+
+### client.retries
+
+Returns the client's configured number of retries as an integer.
+
+### client.with_timeout(integer)
+
+Sets the request timeout to the number of seconds indicated by `integer`.
+
+Each individual request performed by the HTTP client will be limited by the
+given request timeout. This timeout includes connection time, any redirects,
+and reading the response body.
+
+A maximum of 30 seconds is allowed. Specifying a number larger than this
+will result in a runtime error.
+
+By default, requests will time out after 10 seconds.
+
+### client.timeout
+
+Returns the client's configured timeout in whole seconds as an integer.
+
+### client.without_certificate_verification()
+
+Skips verification of TLS certificates during secure HTTP operations,
+allowing for requests to `https://` endpoints which are secured with a TLS
+certificate signed by an untrusted certificate authority. Takes no arguments.
+
+By default, the HTTP client will trigger a runtime error if a TLS certificate
+presented by a server is from an untrusted certificate authority.
+
+Do not change this setting unless you are aware of the risks involved.
+Without verification, TLS accepts any certificate presented by the server
+and any host name in that certificate. In this mode, TLS is susceptible to
+man-in-the-middle attacks. This option should only be used for testing or in
+trusted networks with self-signed certificates.
+
+```sentinel
+http.without_certificate_verification().get(url)
+```
+
+### client.certificate_verification
+
+Returns true if the client requires TLS certificates to be verifiable.
+
+## Type: request
+
+### request.url
+
+Returns the request URL as a string.
+
+### request.headers
+
+Returns the configured request headers as a string-keyed map of strings.
+
+### request.body
+
+Returns the configured body as a string.
+
+### request.with_header(key, value)
+
+Returns a `request` with the header `key` (string) set to `value` (string).
+
+Values are overridden on subsequent calls to the same `key`. To specify
+multiple values, use the existing value when setting the new one.
+
+```sentinel
+original = http.request("http://example.hashicorp.com").with_header("Foo", "bar")
+overridden = original.with_header("Foo", "baz")
+multi_value = original.with_header("Foo", original.headers["Foo"] + ",baz")
+
+main = rule {
+ original.headers["Foo"] is "bar" and
+ overridden.headers["Foo"] is "baz" and
+ multi_value.headers["Foo"] is "bar,baz"
+}
+```
+
+### request.with_basic_auth(username, password)
+
+Returns a `request` with the `Authorization` header set to use HTTP Basic
+Authentication with the provided username and password.
+
+Note that with HTTP Basic Authentication the provided username and password
+are encoded, but not encrypted.
+
+### request.with_body(body)
+
+Returns a `request` with the `body` set. This value will then be sent as a body
+as part of the request. Commonly used along with the [`post()`](#client-post-url_or_request) function.
+
+```sentinel
+req = http.request("http://example.hashicorp.com")
+ .with_header("Content-Type", "application/json")
+ .with_body(json.marshal({ "foo": "bar" }))
+
+resp = http.post(req)
+```
+
+## Type: response
+
+### response.status_code
+
+The response status code as an integer, e.g. `200`.
+
+### response.headers
+
+The response headers as a map.
+
+### response.body
+
+The response body as a string. Callers should unmarshal the content to a useful
+representation as necessary based on the content type. For example, if the
+response is in JSON:
+
+```sentinel
+import "http"
+import "json"
+
+# This example endpoint returns {"some_key": true}
+req = http.request("https://example.hashicorp.com/some-json")
+
+resp = json.unmarshal(http.get(req).body)
+main = rule { keys(resp) contains "some_key" and resp["some_key"] is true }
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/imports/index.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/imports/index.mdx
new file mode 100644
index 0000000000..e45bf81b81
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/imports/index.mdx
@@ -0,0 +1,18 @@
+---
+page_title: Sentinel Language - Standard Imports
+sidebar_current: docs-imports
+description: >-
+ Standard Imports are the built-in imports that are available to all Sentinel
+ policies.
+layout: docs
+---
+
+# Standard Imports
+
+Standard Imports are the built-in [imports](/sentinel/language/imports)
+that are available to all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in import.
+
+Sentinel-embedded applications can choose to allow or deny certain
+standard imports. Please reference the documentation for the Sentinel-enabled
+application you're using to determine if all standard imports are available.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/imports/json.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/imports/json.mdx
new file mode 100644
index 0000000000..3355458f14
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/imports/json.mdx
@@ -0,0 +1,31 @@
+---
+page_title: 'Import: json'
+sidebar_current: docs-imports-json
+description: The json import enables a Sentinel policy to parse and access a JSON document.
+layout: docs
+---
+
+# Import: json
+
+The json import enables a Sentinel policy to parse and access a JSON
+document.
+
+### json.unmarshal(obj)
+
+Unmarshals the JSON object `obj` into a native Sentinel structure.
+
+All native JSON types can be represented perfectly as Sentinel native types.
+
+```sentinel
+// Typically the input for this would come from an external source.
+config = json.unmarshal("{ \"foo\": 42 }")
+config.foo // 42
+config.bar // undefined (as usual for accessing a non-existent map key)
+```
+
+### json.marshal(obj)
+
+Marshals the Sentinel object `obj` into a JSON object encoded as a string.
+
+Functions and `undefined` cannot be natively encoded as JSON. If values of
+either type are found in the structure, this will return undefined.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/imports/runtime.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/imports/runtime.mdx
new file mode 100644
index 0000000000..6493bbbb22
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/imports/runtime.mdx
@@ -0,0 +1,16 @@
+---
+page_title: 'Import: runtime'
+sidebar_current: docs-imports-runtime
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: runtime
+
+The runtime import contains various information about the Sentinel runtime. It
+can be used to get information about the runtime as it's embedded in the CLI or
+a specific integration, such as validating a specific runtime version.
+
+### runtime.version
+
+Returns the version of Sentinel within this implementation.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/imports/sockaddr.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/imports/sockaddr.mdx
new file mode 100644
index 0000000000..a6842ba5f0
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/imports/sockaddr.mdx
@@ -0,0 +1,41 @@
+---
+page_title: 'Import: sockaddr'
+sidebar_current: docs-imports-sockaddr
+description: The sockaddr import makes working with IP addresses easy.
+layout: docs
+---
+
+# Import: sockaddr
+
+The sockaddr import makes working with IP addresses easy.
+
+### sockaddr.is_contained(outer, inner)
+
+The is_contained function returns true if `inner` is an address that
+is contained within `outer`.
+
+```sentinel
+sockaddr.is_contained("192.168.0.0/24", "192.168.0.32") // true
+sockaddr.is_contained("192.168.0.0/24", "192.174.1.1") // false
+```
+
+### sockaddr.is_equal(addr1, addr2)
+
+The is_equal function returns true if addr1 is equal to addr2.
+
+```sentinel
+sockaddr.is_equal("192.168.12.24", "192.168.12.24") // true
+```
+
+### sockaddr.is_ipv4(addr)
+
+The is_ipv4 function returns true if addr is an IPv4 address.
+
+```sentinel
+sockaddr.is_ipv4("192.168.12.24") // true
+sockaddr.is_ipv4("2001:0db8:85a3:0000:0000:8a2e:0370:7334") // false
+```
+
+### sockaddr.is_ipv6(addr)
+
+The is_ipv6 function returns true if addr is an IPv6 address.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/imports/strings.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/imports/strings.mdx
new file mode 100644
index 0000000000..823c5d8f07
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/imports/strings.mdx
@@ -0,0 +1,91 @@
+---
+page_title: 'Import: strings'
+sidebar_current: docs-imports-strings
+description: The strings import exposes common string operations.
+layout: docs
+---
+
+# Import: strings
+
+The strings import exposes common string operations.
+
+### strings.has_prefix(s, prefix)
+
+Returns true if `s` starts with `prefix`.
+
+```sentinel
+strings.has_prefix("billing-id", "billing-") // true
+strings.has_prefix("bill-id", "billing-") // false
+```
+
+### strings.has_suffix(s, suffix)
+
+Returns true if `s` ends with `suffix`.
+
+```sentinel
+strings.has_suffix("billing-id", "id") // true
+strings.has_suffix("billing-name", "id") // false
+```
+
+### strings.join(a, sep)
+
+Joins a list with the string supplied by `sep`.
+
+Multi-dimensional lists are supported, and non-string primitive
+types will be converted to their string equivalents. Maps and
+other complex non-list types are not supported.
+
+```sentinel
+strings.join(["foo", "bar", "baz"], ".") // "foo.bar.baz"
+strings.join([["foo", "bar"], "baz"], ".") // "foo.bar.baz"
+strings.join(["a", 1, 1.01, true], ",") // "a,1,1.01,true"
+```
+
+### strings.trim_prefix(s, prefix)
+
+Trim the prefix from the string `s`. If the string doesn't have the
+prefix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_prefix("billing-id", "billing-") // "id"
+strings.trim_prefix("bill-id", "billing-") // "bill-id"
+```
+
+### strings.trim_suffix(s, suffix)
+
+Trim the suffix from the string `s`. If the string doesn't have the
+suffix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_suffix("billing-id", "-id") // "billing"
+strings.trim_suffix("bill-id", "-foo") // "bill-id"
+```
+
+### strings.to_lower(s)
+
+Lowercase the string s.
+
+```sentinel
+strings.to_lower("FoO") // "foo"
+```
+
+### strings.to_upper(s)
+
+Uppercase the string s.
+
+```sentinel
+strings.to_upper("FoO") // "FOO"
+```
+
+### strings.split(s, sep)
+
+Split the string 's' via a substring 'sep'. If 'sep' is not contained in
+'s', the return value will be a single-element list containing only 's'.
+As a special-case, if the input is already a list, it will be returned
+as-is. This allows calling the split function when not knowing if the input
+is already a list or not.
+
+```sentinel
+strings.split("foo,bar,baz", ",") // ["foo", "bar", "baz"]
+strings.split(["foo", "bar", "baz"], ",") // ["foo", "bar", "baz"]
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/imports/time.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/imports/time.mdx
new file mode 100644
index 0000000000..3a675e2bbd
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/imports/time.mdx
@@ -0,0 +1,257 @@
+---
+page_title: 'Import: time'
+sidebar_current: docs-imports-time
+description: The time import provides access to the execution time and time functions.
+layout: docs
+---
+
+# Import: time
+
+The time import provides access to the execution time and time functions.
+
+During the execution of a policy the time is fixed to the moment of
+execution. This gives the illusion that a policy instantly executes at a
+single moment of time.
+
+The import uses Coordinated Universal Time (UTC).
+
+As an example of this, if a policy accessed `time.now.second` multiple times,
+for each access the same value would be returned even if they are several seconds apart.
+This is the execution `timespace`, which is mapped to `time.now`.
+
+It is also possible to run functions on a custom time by using the
+`time.load()` function to create a new timespace from the specified value.
+See the documentation for available actions on timespaces.
+
+Times specified as the reference time or via the `time.load()` function can
+be specified in a `timeish` fashion. This is either one of:
+
+- The number of seconds since the Unix epoch (Thursday, 1 January 1970,
+ 00:00:00 UTC),
+- A timestamp matching the format outlined in RFC3339, either in the
+ format `2006-01-02T15:04:05+07:00`, which includes a timezone offset, or
+ `2006-01-02T15:04:05Z`, which indicates UTC.
+
+### time.now
+
+Create a `timespace` locked either to execution time or, if set, the
+reference time. In a given execution, this will always produce the same
+value.
+
+```sentinel
+time.now // {"day": 17, "hour": 23, "minute": 19, "month": 11 ... }
+```
+
+### time.load(timeish)
+
+Create a timespace using the given value. Please see the import
+documentation for valid values for "timeish".
+
+```sentinel
+time.load("2019-11-16T01:20:30Z") // {"day": 16, "hour": 1, "minute": 20, "month": 11 ... }
+```
+
+### time.nanosecond
+
+A nanosecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.nanosecond * 1) // 2019-11-16T01:20:30.000000001Z
+```
+
+### time.microsecond
+
+A microsecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.microsecond * 1) // 2019-11-16T01:20:30.000001Z
+```
+
+### time.millisecond
+
+A millisecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.millisecond * 1) // 2019-11-16T01:20:30.001Z
+```
+
+### time.second
+
+A second duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.second * 1) // 2019-11-16T01:20:31Z
+```
+
+### time.minute
+
+A minute for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.minute * 1) // 2019-11-16T01:21:30Z
+```
+
+### time.hour
+
+An hour duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+## Type: timespace
+
+### timespace.hour
+
+The hour from 0 to 23.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").hour // 1
+```
+
+### timespace.minute
+
+The minute from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").minute // 20
+```
+
+### timespace.second
+
+The second from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").second // 30
+```
+
+### timespace.day
+
+The day of the month, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").day // 16
+```
+
+### timespace.month
+
+The month of the year as an integer, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month // 11
+```
+
+### timespace.month_name
+
+The name of the month of the year, in English. Example: `January`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month_name // November
+```
+
+### timespace.weekday
+
+The weekday as an integer, where 0 is Sunday and 6 is Saturday.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday // 6
+```
+
+### timespace.weekday_name
+
+The name of the day of the week, in English. Example: `Sunday`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday_name // Saturday
+```
+
+### timespace.year
+
+The year as an integer.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").year // 2019
+```
+
+### timespace.unix
+
+The number of seconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix // 1573867230
+```
+
+### timespace.unix_nano
+
+The number of nanoseconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix_nano // 1573867230000000000
+```
+
+### timespace.zone
+
+The timezone offset, in seconds. This can be a negative number to indicate
+time west of UTC.
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone // -28800
+```
+
+### timespace.zone_string
+
+The timezone offset, in the format `+HH:MM` (example: `-08:00` for PST).
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone_string // -08:00
+```
+
+### timespace.before(timeish_or_timespace)
+
+Check if the time in the timespace is before the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").before("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.after(timeish_or_timespace)
+
+Check if the time in the timespace is after the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").after("2019-11-15T01:20:30Z") // true
+```
+
+### timespace.equal(timeish_or_timespace)
+
+Check if two times are equivalent
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").equal("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.add(duration)
+
+Add a duration in nanoseconds to the time in the timespace and return a new timespace. The
+duration can be negative. The duration should be specified as an integer
+multiplied with the correct unit.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+### timespace.sub(timeish_or_timespace)
+
+Subtract a time from the time in the timespace and return a duration in nanoseconds.
+
+```sentinel
+// Gives a duration of 3 hours.
+duration = time.load(
+ "2019-11-16T01:20:30Z",
+).sub("2019-11-16T04:20:30Z")
+
+// Returns 2019-11-16T01:20:30Z
+time.load(
+ "2019-11-16T04:20:30Z",
+).add(duration)
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/imports/types.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/imports/types.mdx
new file mode 100644
index 0000000000..fcfdbd2ae5
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/imports/types.mdx
@@ -0,0 +1,35 @@
+---
+page_title: 'Import: types'
+sidebar_current: docs-imports-types
+description: The types import enables a Sentinel policy to parse an object's type.
+layout: docs
+---
+
+# Import: types
+
+The types import enables a Sentinel policy to parse an object's type.
+
+### types.type_of(obj)
+
+Returns the object's type as a string
+
+```sentinel playground
+import "types"
+
+// Typically the inputs would come from an external source.
+is_boolean = rule { types.type_of(true) is "bool" }
+is_string = rule { types.type_of("Hello!") is "string" }
+is_integer = rule { types.type_of(42) is "int" }
+is_float = rule { types.type_of(42.123) is "float" }
+is_null = rule { types.type_of(null) is "null" }
+is_undefined = rule { types.type_of(undefined) is "undefined" }
+
+main = rule {
+ is_boolean and
+ is_string and
+ is_integer and
+ is_float and
+ is_null and
+ is_undefined
+}
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/imports/units.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/imports/units.mdx
new file mode 100644
index 0000000000..e2ad3442f9
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/imports/units.mdx
@@ -0,0 +1,39 @@
+---
+page_title: 'Import: units'
+sidebar_current: docs-imports-units
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: units
+
+The units import provides access to quick calculations for various byte
+units.
+
+Note that this import uses base-2 terminology:
+
+- One kilobyte is 1024 bytes
+- One megabyte is 1024^2 = 1048576 bytes
+- One gigabyte is 1024^3 = 1073741824 bytes
+- One terabyte is 1024^4 = 1099511627776 bytes
+- One petabyte is 1024^5 = 1125899906842624 bytes
+
+### units.kilobyte
+
+The base-2 representation of a kilobyte (1024 bytes).
+
+### units.megabyte
+
+The base-2 representation of a megabyte (1048576 bytes).
+
+### units.gigabyte
+
+The base-2 representation of a gigabyte (1073741824 bytes).
+
+### units.terabyte
+
+The base-2 representation of a terabyte (1099511627776 bytes).
+
+### units.petabyte
+
+The base-2 representation of a petabyte (1125899906842624 bytes).
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/imports/version.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/imports/version.mdx
new file mode 100644
index 0000000000..59366c1087
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/imports/version.mdx
@@ -0,0 +1,151 @@
+---
+page_title: 'Import: version'
+sidebar_current: docs-imports-version
+description: |-
+ The version import provides functions for parsing versions and version constraints,
+ and verifying versions against a set of constraints.
+layout: docs
+---
+
+# Import: version
+
+The version import provides functions for parsing versions and version constraints,
+and verifying versions against a set of constraints.
+
+Versions are created through the `new` function, which accepts a string type in dotted-tri format (i.e. 1.0.0-alpha.1+001) that can represent a version.
+
+### version.new(v)
+
+Constructs a version from string value.
+
+```sentinel
+version.new("1.0.0-alpha.1+001")
+```
+
+### version.major
+
+An integer representation of the Major number for a given version .
+
+```sentinel
+version.new("1.0.0-alpha.1+001").major // 1
+```
+
+### version.minor
+
+An integer representation of the Minor number for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").minor // 0
+```
+
+### version.patch
+
+An integer representation of the Patch number for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").patch // 0
+```
+
+### version.prerelease
+
+A string representation of the Prerelease for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").prerelease // "alpha.1"
+```
+
+### version.metadata
+
+A string representation of the Metadata for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").metadata // "001"
+```
+
+### version.version
+
+A string representation of the version including pre-release and metadata information.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").version // "1.0.0-alpha.1+001"
+```
+
+### version.greater_than(v)
+
+Tests that the version is greater than a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").greater_than("0.12.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").gt("1.0.1-alpha.1+001") // False
+```
+
+### version.greater_than_or_equals(v)
+
+Tests that the version is greater than or equal to a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").greater_than_or_equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").gte("1.0.1-alpha.1+001") // False
+```
+
+### version.less_than(v)
+
+Tests that the version is less than a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").less_than("1.0.1-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").lt("0.12.0-alpha.1+001") // False
+```
+
+### version.less_than_or_equals(v)
+
+Tests that the version is less than or equal to a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").less_than_or_equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").lte("0.12.0-alpha.1+001") // False
+```
+
+### version.less_than_or_equals(v)
+
+Tests that the version equals a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").eq("0.12.0-alpha.1+001") // False
+```
+
+### version.compare(v)
+
+Compares one version with a given version. Compare returns -1, 0, or 1 if the version is less, equal, or greater than the other version, respectively.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").compare("0.12.0-alpha.1+001") // -1
+version.new("1.0.0-alpha.1+001").compare("1.0.0-alpha.1+001") // 0
+version.new("0.12.0-alpha.1+001").cmp("1.0.0-alpha.1+001") // 1
+```
+
+### version.satisfies(v, x)
+
+Tests that a version adheres to one or more version constraints. Satisfies supports the following restriction operators:
+
+- =
+- !=
+- \>
+- <
+- \>=
+- <=
+- ~>
+
+Satisfies supports the following usage scenarios:
+
+- A constraint with a pre-release can only match a pre-release version that contains the same major, minor and patch identifiers.
+- A constraint without a pre-release can only match a version without a pre-release.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").satisfies("> 1.0.0-alpha.1+001") // false
+version.new("1.0.0-alpha.1+001").satisfies(">= 1.0.0-alpha.1+001") // true
+version.new("1.0.0-alpha.1+001").satisfies(">= 1.0.0-alpha.1+001, < 1.0.0-beta+001") // true
+version.new("1.0.0").sat("= 1.0.0") // true
+version.new("0.12.7").sat("~> 0.12.0") // true
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/index.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/index.mdx
new file mode 100644
index 0000000000..4497b40b83
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/index.mdx
@@ -0,0 +1,31 @@
+---
+layout: 'docs'
+page_title: 'Documentation'
+sidebar_current: 'docs-home'
+description: |-
+ Sentinel is a language and framework for policy built to be embedded in existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+[introduction](/sentinel/intro) to understand what Sentinel is, how it
+compares to other software, and more.
+
+
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/language/arithmetic.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/language/arithmetic.mdx
new file mode 100644
index 0000000000..c49f2fd0ce
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/language/arithmetic.mdx
@@ -0,0 +1,67 @@
+---
+page_title: Sentinel Language - Arithmetic
+sidebar_current: docs-language-arith
+description: >-
+ Sentinel supports arithmetic operators for integers and floats. Sentinel
+ supports sum, difference, product, quotient, and remainder.
+layout: docs
+---
+
+# Language: Arithmetic
+
+Sentinel supports arithmetic operators for integers and floats. Sentinel
+supports sum, difference, product, quotient, and remainder.
+
+```plaintext
++ sum
+- difference
+* product
+/ quotient
+% remainder
+```
+
+These operators work in a typical infix style:
+
+```sentinel
+4 + 8 // 12
+8 * 2 // 16
+8 / 4 // 2
+8 / 5 // 1
+8 % 5 // 3
+```
+
+## Order of Operations
+
+Arithmetic follows a standard mathematical order of operations.
+Grouping with parentheses can be used to affect ordering.
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+(4 + 5) * 2 // 18
+```
+
+A full table of operator precendence can be found on the
+[boolean expressions page](/sentinel/language/boolexpr). This
+shows how arithmetic operators relate to other operators.
+
+## Integer Division
+
+Integer division with a remainder rounds down to the nearest integer.
+Example: `8 / 3 is 2`.
+
+If the divisor is zero, an error occurs.
+
+## Mixed Numeric Operations
+
+Mixed numeric operations between integer and floating-point values are
+permitted. The result is a floating-point operation with the integer converted
+to a floating-point value for purposes of calculation.
+
+```sentinel
+import "types"
+
+a = 1.1 + 1 // 2.1
+types.type_of(a) // "float"
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/language/boolexpr.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/language/boolexpr.mdx
new file mode 100644
index 0000000000..f1c518c3bf
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/language/boolexpr.mdx
@@ -0,0 +1,225 @@
+---
+page_title: Sentinel Language - Boolean Expressions
+sidebar_current: docs-language-boolexpr
+description: >-
+ Boolean expressions are expressions that evaluate to a boolean value from the
+ result of a combination of one or more comparisons and logical operators.
+layout: docs
+---
+
+# Language: Boolean Expressions
+
+Boolean expressions are expressions that evaluate to a boolean value
+from the result of a combination of one or more comparisons and logical
+operators. Boolean expressions form the basis of policy since policy can be broken
+down to a set of logical decisions that turn into true or false.
+
+A single boolean expression is the body of a [rule](/sentinel/language/rules).
+Additionally, boolean expressions are used with [conditionals](/sentinel/language/conditionals),
+and more.
+
+## Order of Operations
+
+The precedence of operators is shown below. Operators with higher
+precedence values (larger numbers) are evaluated first. Operators with
+the same precedence value are evaluated left-to-right.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Examples of this precedence are shown below:
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+```
+
+## Logical Operators
+
+Logical operators are used to change or combine logical expressions.
+There are three binary operators `and`, `or`, and `xor`. There is
+a single unary operator `not` (which can also be specified as `!`).
+
+The binary operators require two boolean operands and the unary
+operator requires a single boolean operand. In both case, the operands
+are values or expressions that are boolean types.
+
+Below is a basic explanation of the logical operators directly from
+the specification:
+
+```sentinel
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+Using parentheses to group boolean expressions, you can combine
+boolean expressions to become more complex or affect ordering:
+
+```sentinel
+(p and q) or r
+p and (q or r)
+```
+
+## Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+For any comparison, the two operands must be equivalent types. The one
+exception are integers and floats. When an integer is compared with
+a float, the integer is promoted to a floating point value.
+
+The available comparison operators are:
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+The behavior of `is` with `==` and `is not` with `!=` is identical.
+
+Example comparisons:
+
+```sentinel
+name is "Mitchell"
+idnumber > 42
+idnumber <= 12
+```
+
+Using the logical operators, these can be combined:
+
+```sentinel
+name is "Mitchell" and idnumber > 42
+```
+
+The language specification provides more detail on exactly
+[how comparison behaves](/sentinel/language/spec#comparison-operators).
+
+## Set Operators
+
+The set operators `contains` and `in` test for inclusion in a collection
+(a list or map).
+
+Set operators may 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.
+
+`contains` tests if the left-hand collection contains the right-hand value.
+`in` tests if the right-hand collection contains the left-hand value. For
+maps, "contains" looks at the keys, not the values.
+
+Examples:
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+```
+
+## Matches Operator
+
+The `matches` operator tests if a string matches a regular expression.
+The `matches` operator can be negated by prefixing the operator with
+`not`, such as `not matches`.
+
+The left-hand value is the string to test. The right-hand value is
+a string value representing a regular expression. The syntax of the regular
+expression is the same general syntax used by Python, Ruby, and other languages.
+The precise syntax accepted is the syntax accepted by RE2.
+
+Regular expressions are not anchored by default; any anchoring must be
+explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+## Any, All Expressions
+
+`any` and `all` expressions allow testing that any or all elements of a
+collection match some boolean expression. The result of an `any` and `all`
+expression is a boolean.
+
+`any` returns the boolean value `true` if any value in the collection expression
+results in the body expression evaluating to `true`. If the body expression
+evalutes to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression
+result in the body expression evaluating to `true`. If any value in the
+collection expression result in the body expression evaluating to `false`,
+the `all` expression returns `false`.
+
+For empty collections, `any` returns `false` and `all` returns `true`.
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" }
+any group.tasks as t { t.driver is "vmware" }
+```
+
+Since `any` and `all` expressions are themselves boolean expressions, they
+can be combined with other operators:
+
+```sentinel
+any ["a", "b"] as char { char is "a" } or
+other_value is "another"
+```
+
+## Emptiness Comparison
+
+The expressions `is empty` and `is not empty` provide a convenience
+method for determining the emptiness of a value. It supports the same types
+as the [built-in length](/sentinel/functions/length), collections and strings.
+
+```sentinel
+[] is empty // true
+[] is not empty // false
+["foo"] is empty // false
+["foo"] is not empty // true
+undefined is empty // undefined
+undefined is not empty // undefined
+```
+
+## Defined Comparison
+
+The expressions `is defined` and `is not defined` provide a convenience
+method for determining if a value has been defined. In other words, any value
+other than `undefined` can be considered as `defined`.
+
+```sentinel
+[] is defined // true
+4 is defined // true
+true is defined // true
+{} is defined // true
+undefined is defined // false
+[] is not defined // false
+4 is not defined // false
+true is not defined // false
+undefined is not defined // true
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/language/collection-operations.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/language/collection-operations.mdx
new file mode 100644
index 0000000000..41da8f3584
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/language/collection-operations.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Sentinel Language - Collection Operations
+sidebar_current: docs-language-collection-operations
+description: |-
+ Operations for interacting with collections (list, map).
+layout: docs
+---
+
+# Language: Collection Operations
+
+Collection operations are expressions that are performed on a list or map to
+return a variation of the initial data.
+
+At the moment, `filter` is the only collection operation available.
+
+## Filter Expression
+
+`filter` is a [quantifier
+expression](/sentinel/language/spec#quantifier-expressions-any-all-filter-map) that
+returns a subset of the provided collection. Only elements whose filter body
+returns true will be returned. If any of the elements filter body returns
+undefined, the final result will be undefined.
+
+Filter uses the same syntax as the `any` and `all` [boolean
+expressions](/sentinel/language/boolexpr#any-all-expressions):
+
+```sentinel
+filter list as value { condition } // Single-iterator, list
+filter list as idx, value { condition } // Double-iterator, list
+
+filter map as key { condition } // Single-iterator, map
+filter map as key, value { condition } // Double-iterator, map
+```
+
+Examples:
+
+```sentinel
+l = [1, 1, 2, 3, 5, 8]
+evens = filter l as v { v % 2 is 0 } // [2, 8]
+
+m = { "a": "foo", "b": "bar" }
+matched_foo = filter m as _, v { v is "foo" } // { "a": "foo" }
+```
+
+## Map Expression
+
+`map` is a [quantifier
+expression](/sentinel/language/spec#quantifier-expressions-any-all-filter-map) that
+returns a list, regardless of the input collection type. Each element within the
+input collection is evaluted according to the map expression body and appended
+to the result.
+
+```sentinel
+l = [1, 2]
+r = map l as v { v % 2 } // [false, true]
+
+m = { "a": "foo", "b": "bar" }
+r = map m as k, v { v } // ["foo", "bar"]
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/language/conditionals.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/language/conditionals.mdx
new file mode 100644
index 0000000000..1736795dbb
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/language/conditionals.mdx
@@ -0,0 +1,161 @@
+---
+page_title: Sentinel Language - Conditionals
+sidebar_current: docs-language-conditional
+description: |-
+ Conditional statements allow your policy to behave differently depending on a condition.
+layout: docs
+---
+
+# Language: Conditionals
+
+Conditional statements allow your policy to behave differently depending
+on a condition.
+
+Conditional statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## If Statements
+
+`if` statements only execute their bodies if a condition is met.
+The syntax of an `if` statement is:
+
+```sentinel
+if condition {
+ // ... this is executed if condition is true
+}
+```
+
+The `condition` must result in a boolean, such as by calling a function
+or evaluating a [boolean expression](/sentinel/language/boolexpr). If
+the `condition` is `true`, the body (within the `{}`) is executed. Otherwise,
+the body is skipped.
+
+Examples:
+
+```sentinel
+// This would execute the body
+value = 12
+if value is 18 {
+ print("condition met")
+}
+
+// Direct boolean values can be used
+value = true
+if value {
+ print("condition met")
+}
+
+// This would not execute the body since the boolean expression will
+// result in undefined.
+value = {}
+if value["key"] > 12 {
+ print("condition met")
+}
+```
+
+### Else, Else If
+
+An `else` clause can be given to an `if` statement to execute a body
+in the case the condition is _not met_. By putting another `if` statement
+directly after the `else`, multiple conditions can be tested for.
+The syntax is:
+
+```sentinel
+if condition {
+ // ...
+} else {
+ // ...
+}
+
+if condition {
+ // ...
+} else if other_condition {
+ // ...
+} else {
+ // ...
+}
+```
+
+### Scoping
+
+The body of an `if` statement does not create a new
+[scope](/sentinel/language/scope). Any variables assigned within the body
+of an if statement will modify the scope that the `if` statement itself
+is in.
+
+Example:
+
+```sentinel
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+```sentinel
+a = 18
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+## Case Statements
+
+`case` statements are a selection control mechanism that execute a clause based
+on matching expressions. It is worth noting that the expression for `case` is
+optional. When no expression is provided, it defaults the expression to `true`.
+Additionally, the order of clauses is important, as they are evaluated from top
+to bottom, executing the first match.
+The syntax of a case statement is:
+
+```sentinel
+case expression {
+ when clause_expression:
+ // executed when clause_expression and expression are equal
+ else:
+ // executed if no clause matches expression
+}
+```
+
+### When Clause
+
+Any clause that has an expression for comparison must use the `when` keyword.
+It accepts a list of expressions, seperated by a `,`.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+}
+```
+
+```sentinel
+case {
+ when x > 40:
+ return true
+}
+```
+
+### Else Clause
+
+The `else` keyword allows for capturing any expressions that have no matching
+`when` clause.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+ else:
+ return false
+}
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/language/functions.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/language/functions.mdx
new file mode 100644
index 0000000000..8214078b6b
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/language/functions.mdx
@@ -0,0 +1,229 @@
+---
+page_title: Sentinel Language - Functions
+sidebar_current: docs-language-functions
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Functions
+
+Functions allow you to create reusable code to perform computations.
+
+Below is a trivial example function that doubles a number and shows
+the main rule using the function:
+
+```sentinel playground
+double = func(x) {
+ return x * 2
+}
+
+main = rule { double(12) is 24 }
+```
+
+Functions may contain any expressions,
+[conditionals](/sentinel/language/conditionals),
+and [loops](/sentinel/language/loops). They must return a value
+at the end of their execution. If no reasonable return value exists,
+the function should return [undefined](/sentinel/language/undefined).
+
+## Function Types
+
+### Named Functions
+
+-> **NOTE:** Named functions must be created within the [package scope](/sentinel/language/scope#package-scope).
+
+Named functions are declared using the `func` keyword as its own statement.
+They provide a safe method of creating functions and have additional
+restrictions that do not apply to anonymous functions.
+
+Firstly, named functions cannot be re-assigned, and also cannot use a name
+that is already used elsewhere. For instance, the below example will error due
+to the attempt to reassign the named function identifier to a new value:
+
+```sentinel
+func sum(a, b) {
+ return a + b
+}
+
+sum = 4
+```
+
+Additionally, the following will error due to the named function attempting
+to make use of an already assigned identifier:
+
+```sentinel
+sum = 4
+
+func sum(a, b) {
+ return a + b
+}
+```
+
+Named functions are helpful for policy authors to declare critical functions
+whose value or implementation should not be changed.
+
+### Anonymous Functions
+
+An anonymous function is created by assigning a variable to a `func`. The
+variable can be reassigned at any time including to different value types.
+Anonymous functions are helpful for use cases like closures, where a function
+can return another function.
+
+```sentinel
+func makeAdder(a) {
+ return func(b) {
+ return a + b
+ }
+}
+```
+
+## Creating a Function
+
+A function can have zero or more parameters. These parameters are specified
+within parentheses `()` separated by commas. If a function has no parameters,
+the parentheses must still be present.
+
+A function must terminate with a `return` statement to return a value back to
+the caller. Only a single value can be returned. The type of a return can
+vary between return statements.
+
+Anonymous function example:
+
+```sentinel
+add1 = func(x) { return x + 1 }
+```
+
+Named function example:
+
+```sentinel
+func add1(x) {
+ return x + 1
+}
+```
+
+Both examples create a function that adds 1 to the parameter `x`.
+
+## Calling a Function
+
+Functions are called by accessing their name followed by parentheses with
+a list of comma-separated values for the parameters. If the function takes no
+parameters, an empty set of parentheses must still be used to denote a
+function call.
+
+To call the function in the example above:
+
+```sentinel
+x = 1
+y = add1(x)
+```
+
+## Scoping
+
+The body of a `func` creates a new [scope](/sentinel/language/scope).
+
+The parent scope is the scope in which the function is created. This allows
+for the creation of functions within functions that can access their outer
+scopes.
+
+Example:
+
+```sentinel
+f = func() {
+ a = 42
+ print(a) // 42
+ return undefined
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+f = func() {
+ a = 42
+ return undefined
+}
+
+print(a) // 18
+```
+
+And below is an example of creating a function in a function which uses
+outer values:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double()
+}
+
+print(f()) // 84
+```
+
+A more complex example below shows how scoping works when passing
+functions around as arguments or results:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double
+}
+
+double = f()
+print(double()) // 84
+```
+
+## Pass By Value
+
+The parameters to a function are passed by value, but not deep copied.
+This means that elements of collections can still be modified but the
+root value cannot be changed.
+
+Example:
+
+```sentinel
+f = func(x) {
+ x = "value"
+ return x
+}
+
+x = "outside"
+f(x)
+print(x) // "outside"
+```
+
+```sentinel
+f = func(x) {
+ append(x, "value")
+ return x
+}
+
+x = []
+f(x)
+print(x) // ["value"]
+```
+
+## Recursion
+
+A function is allowed to call other functions, including itself.
+This can be used to implement recursion. For example, the fibonacci sequence
+is implemented below:
+
+```sentinel
+fib = func(x) {
+ if x <= 0 {
+ return undefined
+ }
+
+ if x == 1 {
+ return 1
+ } else {
+ return x + fib(x - 1)
+ }
+}
+```
+
+Note that this example also shows using
+[undefined](/sentinel/language/undefined)
+as a return value in cases where a function has undefined behavior.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/language/imports.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/language/imports.mdx
new file mode 100644
index 0000000000..0ed1541b85
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/language/imports.mdx
@@ -0,0 +1,116 @@
+---
+page_title: Sentinel Language - Imports
+sidebar_current: docs-language-imports
+description: >-
+ Imports enable a Sentinel policy to access reusable libraries and external
+ data and functions.
+layout: docs
+---
+
+# Language: Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external
+data and functions. The exact list of available imports is dependent on the
+host system. Host systems may also make the available imports configurable
+via plugins.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that
+the import `calendar` allows access to engineer calendars. This import
+only exists for the purpose of this example and at the time of writing is
+not a real available import.
+
+```sentinel
+import "calendar"
+
+// Get the calendar for Bob for today
+bob_calendar = calendar.for("bob").today
+
+// Allow this policy to pass if Bob is not on vacation.
+main = rule { not bob_calendar.has_event("vacation") }
+```
+
+This example shows an import being used to deny a behavior if Bob is on
+vacation. Hopefully real policies do not depend on a single person being
+in the office, but the example shows the power of imports.
+
+In addition to consuming imports, you can also
+[extend Sentinel by writing custom imports](/sentinel/extending)
+This allows you to add any arbitrary behavior to your Sentinel-protected
+systems.
+
+## Importing
+
+Whether accessing a built-in library or an external plugin, the syntax
+for an import is the same:
+
+```sentinel
+import "name"
+```
+
+This adds the import with the name `name`. It can be referenced using
+the identifier `name`. For example, if the import above exposed first
+and last name data, you could access it via `name.first` or `name.last`.
+
+You can shadow imports by assigning a variable. In the example below,
+the import `name` becomes unavailable within the function because it is
+shadowed by a variable of the same name.
+
+Shadowing an import is not the same as overwriting a variable. Imports
+are not part of the [scope](/sentinel/language/scope), meaning that
+the shadow only exists for the scope it is created within. For everything
+else, the import works even after it is shadowed. The example below shows that
+as well.
+
+```sentinel
+import "name"
+
+f = func() {
+ name = "jane"
+ return name
+}
+
+print(f()) // "jane"
+print(name.first) // "bob"
+```
+
+## Aliases
+
+An import can be aliased to another name using the syntax below:
+
+```sentinel
+import "name" as other
+```
+
+This would make the import "name" available as `other`.
+
+An import may only be imported once, regardless of aliases. The following
+would be **invalid** and would result in an error:
+
+```sentinel
+import "name" as one
+import "name" as two
+```
+
+## Accessing Import Data
+
+Imports are accessed with dot-separated identifiers, such as `name.first` or
+`name.stage.first`. These are called _selector expressions_. An import may
+return nested data that further can be accessed via selector expressions.
+
+In the first example on this page with the "calendar" import, we see this:
+
+```sentinel
+import "calendar"
+
+a = calendar.for("bob") // selector expression calendar.for
+b = a.today // selector expression on the result
+c = b.vacation // accessing further nested data
+```
+
+The exact structure of an import is dependent on the import. Please reference
+the documentation for an import to learn more.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/language/index.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/language/index.mdx
new file mode 100644
index 0000000000..7f588c94cd
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/language/index.mdx
@@ -0,0 +1,114 @@
+---
+page_title: Sentinel Language
+sidebar_current: docs-language-basics
+description: |-
+ Sentinel policies are written using the Sentinel language. This language
+ is easy to learn and easy to write. You can learn the Sentinel language
+ and be productive within an hour. Learning Sentinel doesn't require any
+ formal programming experience.
+layout: docs
+---
+
+# Sentinel Language
+
+Sentinel policies are written using the Sentinel language. This language
+is easy to learn and easy to write. You can learn the Sentinel language
+and be productive within an hour. Learning Sentinel doesn't require any
+formal programming experience.
+
+This language guide will contain many details that are not necessary to
+be productive with Sentinel or are targeted to those who are looking for
+exact information about the language (such as what types of line endings are
+allowed). You can safely ignore these sentences.
+
+You may also view the
+[official language specification](/sentinel/language/spec)
+This is a specific and detailed document on the syntax and behavior of
+the language primarily intended for implementation creators and to disambiguate
+the language.
+
+## Simplest Example
+
+The example below is about the simplest practical example of Sentinel.
+It is reasonable to imagine this as a realistic policy. This shows that
+in most cases, Sentinel will be extremely simple:
+
+```sentinel
+main = rule { request.method is "GET" and request.headers contains "X-Key" }
+```
+
+## Files
+
+Sentinel policies are single files that end in the `.sentinel` file extension.
+There is currently no built-in mechanism to Sentinel for merging multiple
+files. This is purposefully done to make Sentinel policies easy to submit
+to systems that support Sentinel policies.
+
+Sentinel policy files must be UTF-8 encoded and can end in both
+Unix (LF) or Windows (CRLF) line breaks. When running the
+[auto-formatter](#),
+line endings will always use Unix line breaks.
+
+## Ordering
+
+Sentinel policies are executed top-down. For example:
+
+```sentinel
+a = 1 // a = 1 here
+b = a + 1 // b = 2 here
+a = 3 // a = 3, b = 2
+```
+
+In this example, the value of `a` and `b` is shown at each line. Since Sentinel
+executes values top-down, the final value of `a` is 3 and `b` is 2. `b` _does
+not_ become 4.
+
+## Main
+
+Sentinel expects there to be a `main` [rule](/sentinel/language/rules).
+The value of this rule is the result of the entire policy.
+
+The result of the policy depends on the evaluated contents of the `main` rule.
+For booleans, a policy passes on a `true` value, and fails on a `false` value.
+Other types generally follow a zero or zero-length pattern for determining
+success.
+
+| Type | Passing Condition | Failing Condition |
+| ------- | ----------------- | -------------------------- |
+| Boolean | `true` | `false` |
+| String | `""` | Any non-zero length string |
+| Integer | `0` | Any non-zero value |
+| Float | `0.0` | Any non-zero value |
+| List | `[]` | Any non-zero length list |
+| Map | `{}` | Any non-zero length map |
+
+A value of main that falls outside of the above types will result in a policy
+error. As a special case, if `main` evaluates to an undefined value, the error
+message will indicate as such, with a reference to where the undefined value was
+encountered.
+
+## More Complex Example
+
+The simple example above is a full working example. In our experience with
+Sentinel, many policies can be representing using this simple form. However,
+to show more features of the language, a more complex example is shown below.
+This example is also a realistic example of what Sentinel may be used for.
+
+```sentinel
+import "units"
+
+memory = func(job) {
+ result = 0
+ for job.groups as g {
+ for g.tasks as t {
+ result += t.resources.memory else 0
+ }
+ }
+
+ return result
+}
+
+main = rule {
+ memory(job) < 1 * units.gigabyte
+}
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/language/lists.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/language/lists.mdx
new file mode 100644
index 0000000000..bc5269d8eb
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/language/lists.mdx
@@ -0,0 +1,135 @@
+---
+page_title: Sentinel Language - Lists
+sidebar_current: docs-language-lists
+description: Lists are a collection of zero or more values.
+layout: docs
+---
+
+# Language: Lists
+
+Lists are a collection of zero or more values.
+
+Lists can be created using by wrapping values in `[]` and separating them
+by commas. An optional trailing comma is allowed. List elements can be
+differing types. Examples:
+
+```sentinel
+[] // An empty list
+["foo"] // Single element list
+["foo", 1, 2, true] // Multi element list with different types
+["foo", [1, 2]] // List containing another list
+```
+
+A list can be [sliced](/sentinel/language/slices) to create sublists.
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for value inclusion in a list.
+
+## Accessing Elements
+
+List elements can be accessed with the syntax `name[index]` where
+`index` is zero-indexed.
+
+A negative index accesses the list in reverse. It is the same as
+reversing a list and then using a positive index. Similar to a positive
+index, it is bounded by the length of the list as a negative value.
+
+Accessing beyond the length of the list results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+a = ["foo", 1, true, [1, 2]]
+
+a[0] // "foo"
+a[2] // true
+a[4] // undefined
+a[-2] // true
+a[-4] // "foo"
+a[-5] // undefined
+a[3][1] // 2
+```
+
+## List Append
+
+Values can be appended to a list using the built-in
+[append](/sentinel/functions/append) function.
+
+This modifies the list in-place and returns
+[undefined](/sentinel/language/undefined). For more information on
+why `append` behaves this way, please read the full documentation for the
+[append](/sentinel/functions/append) function.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append([1,2], "foo") // [1, 2, "foo"]
+append([1,2], [3]) // [1, 2, [3]]
+append(1, 3) // error()
+```
+
+## List Concatenation
+
+Two lists can be concatenated using the `+` operator or the shorthand
+`+=` assignment operator. For the `+` operator, a new list is returned.
+For `+=`, the left-hand list is modified in place.
+
+Examples:
+
+```sentinel
+[1] + [2] // [1, 2]
+[1] + [[1]] // [1, [1]]
+[1] + 1 // error
+
+a = [1]
+a += [2] // a = [1, 2]
+a += 3 // error
+```
+
+## List Length
+
+The length of a list can be retrieved using the
+[length](/sentinel/functions/length) function.
+
+Examples:
+
+```sentinel
+length([]) // 0
+length(["foo"]) // 1
+```
+
+## Removing Items From a List
+
+You can use a combination of [list concatenation](#list-concatenation) and
+[slicing](/sentinel/language/slices) to remove elements from a list.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+a = a[:2] + a[3:] // [1, 2, 4, 5]
+```
+
+The shorthand shown here is effectively the same as `a[0:2] + a[3:length(a)]`,
+which creates a new list out of the concatenation two sub-lists composed of the
+first two elements, and the rest of the list starting at index 3. This
+effectively removes the 3rd element from the list (index 2).
+
+## List Comparison
+
+Lists may be [compared](/sentinel/language/boolexpr#comparison-operators)
+for equality. Lists are equal if they are of equal length and their
+corresponding elements are comparable and equal. Lists with the same elements
+but in a different order are not equal.
+
+```sentinel
+[1, 2] is [1, 2] // true
+[1, 2] is [2, 1] // false
+["a"] is ["a", "b"] // false
+["a", ["b", "c"]] is ["a", ["b", "c"]] // true
+```
+
+List comparison speed is O(N), meaning that the speed of the comparison is
+_linearly_ proportional to the number of elements in the list. The more
+elements, the more iterations that are necessary to verify equality.
+
+-> The N-value quoted above should account for the sum of the elements of _all_
+lists in the subjects of comparison, as list comparison will recurse into these
+lists to check for equality.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/language/logging-errors.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/language/logging-errors.mdx
new file mode 100644
index 0000000000..705a72cbd4
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/language/logging-errors.mdx
@@ -0,0 +1,52 @@
+---
+page_title: Sentinel Language - Logging and Errors
+sidebar_current: docs-language-log
+description: The built-in functions `print` and `error` can be used for logging and errors.
+layout: docs
+---
+
+# Language: Logging and Errors
+
+The built-in functions `print` and `error` can be used for logging
+and errors. Logging is helpful to understand how a policy is executing
+in development. And errors are useful to halting execution immediately in
+certain scenarios.
+
+## Print
+
+The `print` function is used to output debug information. The exact location
+where this print statements go is dependent on the host application and
+the Sentinel runtime itself.
+
+The `print` function takes zero or more Sentinel values as arguments.
+Each value is formatted for human-friendly output and space-separated.
+Example:
+
+```sentinel
+value = 42
+print("the value is", value) // the value is 42
+
+map = { "foo": false }
+print(map) // { "foo": false }
+
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // false
+```
+
+## Errors
+
+Certain actions in Sentinel, such as accessing an undefined variable,
+attempting to add two incompatible types, etc. result in _runtime errors_.
+These errors halt the execution of a policy immediately. The pass/fail result
+of a policy is considered fail.
+
+Runtime errors can be manually triggered using the `error` function.
+The `error` function behaves exactly like the `print` function except that
+execution is halted immediately.
+
+This should only be used in scenarios where the policy _must fail_ due to
+some unexpected or invalid condition. The line between `error` and
+[undefined](/sentinel/language/undefined) can sometimes be unclear. Undefined
+is recommended when a policy can conceivably recover or safely ignore the
+undefined behavior. An error should be used when the policy should fail and
+notify someone regardless.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/language/loops.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/language/loops.mdx
new file mode 100644
index 0000000000..622732bd35
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/language/loops.mdx
@@ -0,0 +1,92 @@
+---
+page_title: Sentinel Language - Loops
+sidebar_current: docs-language-loops
+description: >-
+ Loop statements allow you to execute a body of code for each element in a
+ collection or for some fixed number of times.
+layout: docs
+---
+
+# Language: Loops
+
+Loop statements allow you to execute a body of code for each element in
+a collection or for some fixed number of times.
+
+Loop statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## For Statements
+
+`for` statements allow repeated execution of a block for each
+element in a collection.
+
+Example:
+
+```sentinel
+// A basic sum
+count = 0
+for [1, 2, 3] as num {
+ count += num
+}
+```
+
+The syntax is `for COLLECTION as value`. This will iterate over the
+collection, assigning each element to `value`. In the example above,
+each element is assigned to `num`. The body is executed for each element.
+In the example above, the body adds `num` to the `count` variable. This
+creates a basic sum of all values in the collection.
+
+For a map, the assigned element is the key in the map. In the example
+below, `name` would be assigned map keys.
+
+```sentinel
+list = []
+for { "a": 1, "b": 2 } as name {
+ append(list, name)
+}
+
+print(list) // ["a" "b"]
+```
+
+An alternate syntax is `for COLLECTION as key, value`. This will assign
+both the key and value to a variable. For a list, the key is the element
+index. For a map, it is the key and value is assigned the element value.
+Example:
+
+```sentinel
+count = 0
+for { "a": 1, "b": 2 } as name, num {
+ count += num
+}
+
+print(count) // 3
+```
+
+## Scoping
+
+The body of a `for` statement creates a new
+[scope](/sentinel/language/scope). If a variable is assigned within
+the body of a for statement that isn't assigned in a parent scope,
+that variable will only exist for the duration of the body execution.
+
+Example:
+
+```sentinel
+for list as value {
+ a = 42
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+for list as value {
+ a = 42
+}
+
+print(a) // 18
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/language/maps.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/language/maps.mdx
new file mode 100644
index 0000000000..298bf9a567
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/language/maps.mdx
@@ -0,0 +1,121 @@
+---
+page_title: Sentinel Language - Maps
+sidebar_current: docs-language-maps
+description: >-
+ Maps are a collection of zero or more key/value pairs. These are useful for
+ storing values that are looked up by a unique key.
+layout: docs
+---
+
+# Language: Maps
+
+Maps are a collection of zero or more key/value pairs. These are useful
+for storing values that are looked up by a unique key.
+
+Maps can be created using `{}` and specifying key/value pairs. Keys and
+values can be differing types. An optional trailing comma is allowed.
+Examples:
+
+```sentinel
+// Empty map
+{}
+
+// Map with a single value on one line
+{ "key": "value" }
+
+// Map with multiple values with differing types on multiple lines
+{
+ "key": "value",
+ 42: true,
+}
+```
+
+Maps are **unordered**. When
+[looping](/sentinel/language/loops)
+over a map, the key/value pairs can be returned in any order.
+
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for key inclusion in a map.
+
+## Accessing Elements
+
+Map elements can be accessed with the syntax `name[key]`. This looks up
+a value by a key.
+
+Accessing a key that doesn't exist results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+map = { "key": "value", 42: true, }
+
+map["key"] // "value"
+map[42] // true
+map[0] // undefined
+```
+
+## Modifying or Adding Elements
+
+Elements can be added or modified in a map by assigning to `name[key]`.
+If the key doesn't exist, the value is added. If the key already exists,
+the value is overridden.
+
+```sentinel
+map = { "key": "value" }
+
+map[42] = true // Add a new key/value
+map["key"] = 12 // Modify the value of "key"
+```
+
+## Deleting Elements
+
+An element can be deleted from a map using the
+[delete](/sentinel/functions/delete) function.
+
+Examples:
+
+```sentinel
+map = { "key": "value" }
+delete(map, "key") // map is now empty
+delete(map, "other") // no effect for non-existent key
+```
+
+## Keys and Values
+
+The keys and values of a map can be retrieved as
+[lists](/sentinel/language/lists) using the
+[keys](/sentinel/functions/keys) and
+[values](/sentinel/functions/values) functions.
+
+Because maps are unordered, the keys and values are returned in an
+unspecified order. It should not be assumed that keys and values will be
+returned in a consistent order.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+## Map Comparison
+
+Maps may be [compared](/sentinel/language/boolexpr#comparison-operators) for
+equality. Maps are equal if they are of equal length and both their
+corresponding keys and values are comparable and equal.
+
+```sentinel
+{"foo": "bar"} is {"foo": "bar"} // true
+{"foo": "bar"} is {"baz": "bar"} // false
+{"foo": "bar"} is {"foo": "baz"} // false
+{"foo": "bar"} is {"foo": "bar", "baz": "qux"} // false
+{1: "a"} is {1.0: "a"} // true (int/float comparable)
+
+// also true (maps are not ordered):
+{"m": {"a": "b"}, "l": ["a"]} is {"l": ["a"], "m": {"a": " b"}}
+```
+
+-> The current worst-case comparison speed for maps is O(N²), or _quadratic
+time_. This is the square of the cumulative elements or the parent and all child
+maps contained within the map. Consider this when making comparisons on larger,
+more complex maps. This may be optimized in the future.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/language/parameters.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/language/parameters.mdx
new file mode 100644
index 0000000000..348acf2593
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/language/parameters.mdx
@@ -0,0 +1,133 @@
+---
+page_title: Sentinel Language - Parameters
+sidebar_current: docs-language-parameters
+description: >-
+ Parameteres allow a Sentinel policy to describe variables that are expected
+ to be supplied by the calling application, in addition to any default value.
+layout: docs
+---
+
+# Language: Parameters
+
+Sentinel allows a policy author to supply parameters to help facilitate policy
+reuse and ensure sensitive values do not need to be hard-coded in a policy.
+
+Parameters are supplied by using the `param` keyword, followed by an identifier.
+A default value can also be supplied by using the `default` keyword.
+
+```sentinel
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+```
+
+Once declared, parameters can be used like any other variable, including being
+re-assigned.
+
+```sentinel
+param foo default 1 // 1 (default)
+
+foo = foo + 1 // 2
+```
+
+## Variable Descriptions
+
+You can supply a description to a parameter by adding a comment at the top of
+it. This value can be communicated to a specific implementation of Sentinel to
+provide information about what the parameter is for during configuration.
+
+```sentinel
+// An example parameter. Must be supplied or the policy will fail.
+param foo
+```
+
+## Supplying Parameter Values Using the Sentinel CLI
+
+In a production implementation, supplying parameters to a policy is an
+implementation-specific detail - see the documentation for your particular
+implementation for details.
+
+Using the [Sentinel CLI](/sentinel/commands), you can supply parameters one of
+four ways.
+
+### Supplying Parameter Values Using the Configuration File
+
+You can supply parameters using the
+[`param`](/sentinel/configuration#parameters) section of the
+[configuration file](/sentinel/configuration).
+
+```hcl
+param "foo" {
+ value = "bar"
+}
+```
+
+This method works for both `sentinel apply` and `sentinel test`.
+
+### Supplying Parameter Values Using the Environment
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can supply a value using environment variables - prefix the parameter with
+`SENTINEL_PARAM_`, using the name of the parameter to supply.
+
+```plaintext
+SENTINEL_PARAM_foo=bar sentinel apply policy.sentinel
+```
+
+### Supplying Parameter Values Using the CLI
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can also use the `-param` CLI argument to supply parameter in a `key=value`
+pair.
+
+```plaintext
+sentinel apply -param foo=bar policy.sentinel
+```
+
+### Interactive CLI Prompting
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+If a required value has not been supplied when a policy is run with `sentinel apply`, it will be prompted for, along with its description:
+
+
+
+ $ sentinel apply policy.sentinel
+
+ policy.sentinel:2:7: requires value for parameter foo
+
+ An example parameter. Must be supplied or the policy will fail.
+
+
+ Values can be strings, floats, or JSON array or object values. To
+ force
+
+ strings, use quotes.
+
+
+ Enter a value: bar
+
+
+
+ Pass
+
+
+
+
+### CLI Value Format
+
+-> **NOTE:** This section contains details for the parameter features supported by `sentinel apply`.
+
+The CLI takes either strings, or JSON numbers, arrays, or maps. If you need a
+literal string value, quote the value.
+
+```sentinel
+foo // string
+42 // number (float)
+"42" // string ("42", without quotes)
+[1, 2] // array (list)
+{"a": "b"} // object (map)
+```
+
+-> **NOTE:** Boolean values are not supported by this method.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/language/rules.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/language/rules.mdx
new file mode 100644
index 0000000000..e31924d414
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/language/rules.mdx
@@ -0,0 +1,204 @@
+---
+page_title: Sentinel Language - Rules
+sidebar_current: docs-language-rules
+description: >-
+ Rules form the basis of a policy by representing behavior that is either
+ passing or failing (true or false).
+layout: docs
+---
+
+# Language: Rules
+
+Rules form the basis of a policy by representing behavior that is either passing
+or failing. A policy can be broken down into a set of rules. Breaking down a
+policy into a set of rules can make it more understandable and aids with
+testing.
+
+An example is shown below:
+
+```sentinel playground
+weather = "sunny"
+day = "wednesday"
+is_sunny = rule { weather is "sunny" }
+is_wednesday = rule { day is "wednesday" }
+
+main = rule {
+ is_sunny and
+ is_wednesday
+}
+```
+
+A rule contains a single expression. This can be a simple [boolean
+expression](/sentinel/language/boolexpr), or an expression representing the
+discovery of a set of violations using more in-depth expressions like [`filter`
+and `map`](/sentinel/language/collection-operations). You can split expressions
+into multiple lines for readability, as you would with any other expression
+within Sentinel.
+
+A rule is also _lazy_ and _memoized_. _Lazy_ means that the rule is only
+evaluated when it is actually required, not at the point that it is
+created. This is covered in more detail in the [lazy](#lazy-and-memoized) section.
+_Memoized_ means that the value is only computed once and then saved. Once
+a rule is evaluated, the result of it is reused anytime the rule is referenced
+again.
+
+## Making rules easier to understand
+
+Rules are an abstraction that make policies much more understandable
+by making it easier for humans to see what the policy is trying to do.
+
+For example, consider the policy before which doesn't abstract into rules:
+
+```sentinel
+main = rule {
+ ((day is "saturday" or day is "sunday") and homework is "") or
+ (day in ["monday", "tuesday", "wednesday", "thursday", "friday"] and
+ not school_today and homework is "")
+}
+```
+
+Assume that `day`, `homework`, and `school_day` are available.
+
+In plain English, this policy is trying to say: you can play with your friends
+only on the weekend as long as there is no homework, or during the week if
+there is no school and no homework.
+
+Despite the relatively simple nature of this policy (a few lines, about 5
+logical expressions), it is difficult to read and understand, especially if
+you've not seen it before.
+
+The same policy using rules as an abstraction:
+
+```sentinel
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+is_valid_weekend = rule { is_weekend and homework is "" }
+is_valid_weekday = rule { not is_weekend and not school_today and homework is "" }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+By reading the names of the rules, its much clearer to see what each individual
+part of the policy is trying to achieve. The readability is improved even
+further when adding comments to the rules to explain them further, which is
+a recommended practice:
+
+```sentinel
+// A weekend is Sat or Sun
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+// A valid weekend is a weekend without homework
+is_valid_weekend = rule { is_weekend and homework is "" }
+
+// A valid weekday is a weekday without school
+is_valid_weekday = rule { not is_weekend and not school_today and homework is "" }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+## "When" Predicates
+
+A rule may have an optional `when` predicate attached to it. When this is
+present, the rule is only evaluated when the predicate results in `true`.
+Otherwise, the rule is not evaluated and the result of the rule is always
+`true`.
+
+"When" predicates should be used to define the context in which the rule
+is semantically meaningful. It is common when translating human language
+policy into Sentinel to have scenarios such as "when the key has a prefix
+of `/account/`, the remainder must be numeric." In that example, when the
+key _does not_ have the specified prefix, the policy doesn't apply.
+
+Assume you have rules `is_prefix` and `is_numeric` to check both the
+conditions in the example above. An example is shown with and without
+the "when" predicate:
+
+```sentinel
+example_no_when = rule { (is_prefix and is_numeric) or not is_prefix }
+
+example_when = rule when is_prefix { is_numeric }
+```
+
+The rules are equivalent in behavior for all values of `is_prefix` and
+`is_numeric`, but the second rule is more succint and easier to understand.
+
+## Testing
+
+To verify a policy works correct, the
+[built-in Sentinel test framework](/sentinel/writing/testing)
+uses rules as the point where you can assert behavior. You say that
+you expect certain rules to be true or false. By doing so, you ensure that
+the policy results in the value you expect using the rule flow that you
+expect.
+
+Therefore, in addition to readability, we recommend splitting policies into
+rules to aid testability.
+
+## Non-Boolean Values
+
+Sentinel supports non-boolean values in rules. This can be useful for passing
+more detail along to a calling integration when more detail is required than a
+boolean value would be able to communicate. This data shows up in the [policy
+trace](/sentinel/writing/tracing) and can be utilized in different ways
+depending on the integration you are using Sentinel with.
+
+Consider a different take on our policy above:
+
+```sentinel
+// A policy to check a set of scheduled days to determine what days you can't
+// come out and play, based on the day not being a weekend day and there being
+// homework to do.
+
+param days
+
+main = rule {
+ filter days as d {
+ d.day not in ["saturday", "sunday"] and
+ d.homework is not ""
+ }
+}
+```
+
+When given a value in the set of `days` that has a `day` that is a weekday, and
+`homework` present, the policy will fail. Additional, when tracing was enabled,
+the days that were detected as violating the policy would be given in the trace
+for the `main` rule.
+
+Generally, for non-boolean values, a policy fails on non-zero data. See the
+details on [the `main` rule](/sentinel/language#main) for more details.
+
+The result of a rule must be either a boolean, string, integer, float, list, or
+map. All other types will result in a runtime error.
+
+## Lazy and Memoized
+
+A rule is _lazy_ and _memoized_.
+
+_Lazy_ means that the rule is only evaluated when it is actually required,
+not at the point that it is created. And _memoized_ means that the value is
+only computed once and then saved. Once a rule is evaluated, the result of it
+is reused anytime the rule is referenced again.
+
+Both of these properties have important implications for Sentinel
+policies:
+
+**Performance:** Rules are a way to improve the performance of a Sentinel
+policy. If you have a boolean expression that is reused a lot, a rule will
+only be evaluated once. In the example shown above, `is_weekend` is used
+multiple times, but will only have to be evaluated once.
+
+**Behavior:** Rules accessing variables see the value of those variables
+at _the time they are evaluated_. This can lead to surprising behavior
+in some cases. For example:
+
+```sentinel playground
+a = 1
+b = rule { a == 1 }
+a = 2
+main = b
+```
+
+In this example, `main` will actually result to `false`. It is evaluated
+when it is needed, which is when the policy executes `main`. At this point,
+`a` is now 2 and `b` has not been evaluated yet. Therefore, `b` becomes `false`.
+All future references to `b` will return `false` since a rule is memoized.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/language/scope.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/language/scope.mdx
new file mode 100644
index 0000000000..f498e26dc2
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/language/scope.mdx
@@ -0,0 +1,47 @@
+---
+page_title: Sentinel Language - Scope
+sidebar_current: docs-language-scope
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Scope
+
+Scope is the context in which variables are created and accessed. If a
+variable exists in a parent scope, it is accessed and can be modified.
+Otherwise, the variable is created in your current scope.
+
+Scopes are created with _blocks_. A block is a possibly empty sequence
+of statements within matching brace brackets `{}`. You may nest blocks
+to create nested scopes.
+
+## Package Scope
+
+The package scope is the top level scope within a policy file and encapsulates
+the entire file contents. Imports, parameters and named functions must be
+declared within the package scope.
+
+## Implicit Scopes
+
+Each `any`, `all`, and `for` statement is considered to be in its own block.
+Note that `if` statements _do not_ create their own block.
+
+## Examples
+
+The example policy below shows various effects of scopes:
+
+```sentinel
+a = 1
+print(a) // 1
+
+f = func() {
+ print(a) // 1
+ a = 12
+ b = 1
+ return undefined
+}
+f()
+
+print(a) // 12
+print(b) // undefined
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/language/slices.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/language/slices.mdx
new file mode 100644
index 0000000000..82dc7f4c18
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/language/slices.mdx
@@ -0,0 +1,45 @@
+---
+page_title: Sentinel Language - Slices
+sidebar_current: docs-language-slices
+description: >-
+ Slices are an efficient way to create a substring or sublist from an existing
+ string or list, respectively.
+layout: docs
+---
+
+# Language: Slices
+
+Slices are an efficient way to create a substring or sublist from an
+existing string or list, respectively.
+
+The syntax for creating a slice is:
+
+```sentinel
+a[low : high]
+```
+
+`low` is the lowest index to slice from. The resulting slice includes
+the value at `low`. `high` is the index to slice to. The resulting slice
+will not include the value at `high`. The length of the resulting list
+or string is always `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+
+a = "hello"
+b = a[1:4] // "ell"
+```
+
+## Convenience Shorthands
+
+Some convenience shorthands are available by omitting the value for either the
+low or high index in the slice expression: omitting `low` implies a low index of
+0, and omitting `high` implies a high index of the length of the list.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+
+b = a[:2] // [1, 2] (same as a[0:2])
+b = a[2:] // [3, 4, 5] (same as a[2:length(a)])
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/language/spec.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/language/spec.mdx
new file mode 100644
index 0000000000..d8efb378cd
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/language/spec.mdx
@@ -0,0 +1,1333 @@
+---
+page_title: Sentinel Language Specification
+sidebar_current: docs-language-spec
+description: This is the specification for the Sentinel policy language.
+layout: docs
+---
+
+# Sentinel Language Specification
+
+This is the specification for the Sentinel policy language.
+
+The Sentinel language is designed with policy enforcement in mind. It is dynamically typed and garbage collected and has explicit support for _rule_ construction representing boolean logic.
+
+The language is designed to be easy to learn and use by non-programmers. It is expected to be embedded within applications.
+
+## Table of Contents
+
+
+
+
+- [Source code representation](#source-code-representation)
+- [Declarations and Scope](#declarations-and-scope)
+- [Blocks](#blocks)
+- [Lexical Elements](#lexical-elements)
+ - [Comments](#comments)
+ - [Identifiers](#identifiers)
+ - [Keywords](#keywords)
+ - [Operators and Delimiters](#operators-and-delimiters)
+ - [Integer Literals](#integer-literals)
+ - [Floating-point Literals](#floating-point-literals)
+ - [String Literals](#string-literals)
+ - [Implicit Line Joining](#implicit-line-joining)
+ - [Whitespace](#whitespace)
+ - [Semicolons](#semicolons)
+- [Variables](#variables)
+- [Undefined](#undefined)
+- [Expressions](#expressions)
+ - [Operand](#operand)
+ - [Primary Expressions](#primary-expressions)
+ - [Null](#null)
+ - [Booleans](#booleans)
+ - [Boolean Literals](#boolean-literals)
+ - [Boolean Expressions](#boolean-expressions)
+ - [List Literals](#list-literals)
+ - [Map Literals](#map-literals)
+ - [Function Literals](#function-literals)
+ - [Rule Expressions](#rule-expressions)
+ - [Index Expressions](#index-expressions)
+ - [Selectors](#selectors)
+ - [Slice Expressions](#slice-expressions)
+ - [Calls](#calls)
+ - [Operators](#operators)
+ - [Operator Precedence](#operator-precedence)
+ - [Arithmetic Operators](#arithmetic-operators)
+ - [Integer operators](#integer-operators)
+ - [Integer overflow](#integer-overflow)
+ - [Floating-point operators](#floating-point-operators)
+ - [String Concatenation](#string-concatenation)
+ - [List Concatenation](#list-concatenation)
+ - [Comparison Operators](#comparison-operators)
+ - [Logical Operators](#logical-operators)
+ - [Set Operators](#set-operators)
+ - [Matches Operator](#matches-operator)
+ - [Else Operator](#else-operator)
+ - [Quantifier Expressions (any, all, filter, map)](#quantifier-expressions-any-all-filter-map)
+- [Statements](#statements)
+ - [Expression Statements](#expression-statements)
+ - [Assignments](#assignments)
+ - [If Statements](#if-statements)
+ - [Case Statements](#case-statements)
+ - [For Statements](#for-statements)
+ - [Break Statements](#break-statements)
+ - [Continue Statements](#continue-statements)
+ - [Return Statements](#return-statements)
+- [Imports](#imports)
+- [Parameters](#parameters)
+- [Built-in Functions](#built-in-functions)
+ - [Length](#length)
+ - [Collections](#collections)
+ - [List Append](#list-append)
+ - [Map Delete](#map-delete)
+ - [Keys and Values](#keys-and-values)
+ - [Range](#range)
+ - [Type Conversion](#type-conversion)
+ - [Printing](#printing)
+ - [Errors](#errors)
+- [Program Execution](#program-execution)
+
+
+
+## Source code representation
+
+Source code is Unicode text encoded in UTF-8. A "character" referenced in this document refers to a Unicode code point. Each code point is distinct: upper and lower case letters are different characters.
+
+The underscore character \_ (U+005F) is considered a "letter".
+
+```ebnf
+letter = unicode_letter | "_" .
+decimal_digit = "0" … "9" .
+octal_digit = "0" … "7" .
+hex_digit = "0" … "9" | "A" … "F" | "a" … "f" .
+```
+
+## Declarations and Scope
+
+A declaration binds an identifier to a value. An identifier is declared at the point it is first assigned. An assignment is considered a first assignment if the identifier hasn't already been previously declared in the current scope or any parent scopes.
+
+The scope of an identifier is the extent of source text in which the identifier denotes the specified value. Sentinel is lexically scoped using blocks.
+
+An identifier declared in a block may be redeclared in an inner block. While the identifier of the inner declaration is in scope, it denotes the value assigned in the inner declaration.
+
+## Blocks
+
+A block is a possibly empty sequence of statements within matching brace brackets.
+
+```ebnf
+Block = "{" StatementList "}" .
+StatementList = { Statement ";" } .
+```
+
+Blocks nest and affect scoping.
+
+In addition to explicit blocks in the source code, there are implicit blocks:
+
+1. The universe block encompasses all source text.
+2. Each program has a program block containing all source text for that program.
+3. Each "any", "all", and "for" statements is considered to be in its own implicit block. "if" does not create an implicit block.
+
+## Lexical Elements
+
+### Comments
+
+Comments are sections of source text used for documentation.
+
+```plaintext
+# Single line comment
+// Single line comment
+/* multi
+line
+ comment */
+```
+
+Three forms of comments are supported: two single-line forms and one multi-line form. A single-line comment begins with the token `//` or `#`. Everything between the starting token and the end of the line is ignored.
+
+A multi-line comment begins with the token `/*` and ends with the token `*/`. Everything between `/*` and `*/` is ignored.
+
+A comment may not start inside a string literal or inside another comment.
+
+A multi-line comment containing no newlines acts like a space.
+
+### Identifiers
+
+Identifiers name program entities such as rules, variables, and functions. An identifier is a sequence of one or more letters and digits. The first character in an identifier must be a letter.
+
+```ebnf
+identifier = letter { letter | unicode_digit } .
+```
+
+```sentinel
+a
+_a
+_A
+αβ
+```
+
+#### Pre-Declared Identifiers
+
+The following identifiers are implicitly declared in the [universe block](#blocks):
+
+```plaintext
+Constants:
+ false null true undefined
+
+Functions:
+ append bool delete error float int keys length print range string values
+```
+
+### Keywords
+
+The following keywords are reserved and may not be used as identifiers:
+
+```plaintext
+all
+any
+as
+break
+case
+continue
+default
+else
+empty
+filter
+for
+func
+if
+import
+map
+param
+return
+rule
+when
+```
+
+### Operators and Delimiters
+
+The following character sequences represent operators, delimiters, and other special tokens:
+
+```plaintext
++
+-
+*
+/
+%
++=
+-=
+*=
+/=
+%=
+==
+<
+>
+=
+!
+!=
+<=
+>=
+( )
+[ ]
+{ }
+,
+.
+:
+;
+and
+contains
+else
+in
+is
+matches
+not
+or
+xor
+```
+
+As a special case, `else` is both a keyword and operator, depending on the context. See [Else Operator](#else-operator) for more details.
+
+### Integer Literals
+
+An integer literal is a sequence of digits representing an integer constant. An optional prefix sets a non-decimal base: `0` for octal, `0x` or `0X` for hexadecimal. In hexadecimal literals, letters `a-f` and `A-F` represents values 10 through 15.
+
+Integers are signed 64-bit values (-9223372036854775808 to 9223372036854775807).
+
+```ebnf
+int_lit = decimal_lit | octal_lit | hex_lit .
+decimal_lit = ( "1" … "9" ) { decimal_digit } .
+octal_lit = "0" { octal_digit } .
+hex_lit = "0" ( "x" | "X" ) hex_digit { hex_digit } .
+```
+
+```
+42
+0600
+0xBadFace
+170141183460469231731687303715884105727
+```
+
+### Floating-point Literals
+
+A floating-point literal is a decimal representation of a floating-point constant. It has an integer part, a decimal point, a fractional part, and an exponent part. The integer and fractional part comprise decimal digits; the exponent part is an e or E followed by an optionally signed decimal exponent. One of the integer part or the fractional part may be elided; one of the decimal point or the exponent may be elided.
+
+Floating-point numbers are IEEE-754 64-bit floating numbers.
+
+```ebnf
+float_lit = decimals "." [ decimals ] [ exponent ] |
+ decimals exponent |
+ "." decimals [ exponent ] .
+decimals = decimal_digit { decimal_digit } .
+exponent = ( "e" | "E" ) [ "+" | "-" ] decimals .
+```
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+### String Literals
+
+String literals are character sequences between double quotes, as in "bar". Within the quotes, any character may appear except newline and unescaped double quote. The text between the quotes forms the value of the literal.
+
+A multi-character sequence beginning with a backslash encode values in various formats.
+
+Backslash escapes allow arbitrary values to be encoded as ASCII text. There are four ways to represent the integer value as a numeric constant: `\x` followed by exactly two hexadecimal digits; `\u` followed by exactly four hexadecimal digits; `\U` followed by exactly eight hexadecimal digits, and a plain backslash `\` followed by exactly three octal digits. In each case the value of the literal is the value represented by the digits in the corresponding base.
+
+After a backslash, certain single-character escapes represent special values:
+
+```plaintext
+\a U+0007 alert or bell
+\b U+0008 backspace
+\f U+000C form feed
+\n U+000A line feed or newline
+\r U+000D carriage return
+\t U+0009 horizontal tab
+\v U+000b vertical tab
+\\ U+005c backslash
+\" U+0022 double quote
+```
+
+The three-digit octal (\nnn) and two-digit hexadecimal (\xnn) escapes represent individual bytes of the resulting string; all other escapes represent the (possibly multi-byte) UTF-8 encoding of individual characters. Thus inside a string literal \377 and \xFF represent a single byte of value 0xFF=255, while ÿ, \u00FF, \U000000FF and \xc3\xbf represent the two bytes 0xc3 0xbf of the UTF-8 encoding of character U+00FF.
+
+A string value is a (possibly empty) sequence of bytes. Strings are immutable: once created, it is impossible to change the contents of a string.
+
+The length of a string s (its size in bytes) can be discovered using the built-in function `length`. An individual character (of type string) can be accessed by integer indices 0 through `length(s)-1`.
+
+```ebnf
+string_lit = `"` { unicode_value | byte_value } `"` .
+unicode_value = unicode_char | little_u_value | big_u_value | escaped_char .
+byte_value = octal_byte_value | hex_byte_value .
+octal_byte_value = `\` octal_digit octal_digit octal_digit .
+hex_byte_value = `\` "x" hex_digit hex_digit .
+little_u_value = `\` "u" hex_digit hex_digit hex_digit hex_digit .
+big_u_value = `\` "U" hex_digit hex_digit hex_digit hex_digit
+ hex_digit hex_digit hex_digit hex_digit .
+escaped_char = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | `"` ) .
+```
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+### Implicit Line Joining
+
+Expressions can be split over more than one line. For example:
+
+```sentinel
+a or
+b or # This is a comment
+c
+```
+
+Implicitly continued lines can have trailing comments. Blank continued lines are allowed.
+
+### Whitespace
+
+Whitespace is needed to separate tokens, but no distinction is made between the number and combination of whitespace characters. Whitespace characters can be space (U+0020), horizontal tabs (U+0009), carriage returns (U+000D), and newlines (U+000A).
+
+```sentinel
+a or b
+
+a or b
+
+a or
+ b
+
+rule { a or b }
+
+rule {
+ a or b }
+
+rule {
+ a or
+ b
+}
+```
+
+### Semicolons
+
+The formal grammar uses semicolons ";" as terminators in a number of productions.
+It is idiomatic Sentinel source to omit semicolons in most cases.
+
+A semicolon is automatically inserted into the token stream immediately after
+a line's final token if that token is:
+
+- An identifier
+- An integer, float, or string literal
+- The keyword `break`, `continue`, or `return`
+- The delimiter `)`, `]`, or `}`
+
+## Variables
+
+A variable is a storage location for holding a value.
+
+A variable has a dynamic type, which is the concrete type of the value assigned to the variable at run time. The dynamic type may vary during execution and change as a result of further assignments.
+
+A variable's value is retrieved by referring to the variable in an expression; it is the most recent value assigned to the variable. If a variable has not yet been declared (initially assigned), it is an error.
+
+```sentinel
+x = 7 // x is type int
+x = "foo" // x is now type string
+
+x = y // error if y is not previously assigned
+```
+
+## Undefined
+
+The value denoted by the keyword `undefined` represents undefined behavior or values. It can be created directly using the keyword `undefined`. It is also returned as a result of expressions in specified cases.
+
+`undefined` is a valid operand for any operations. Only `undefined or true` will result in true. All other operations result in `undefined`. An exception is if `undefined` is not reached as a result of short-circuit operations.
+
+```sentinel
+undefined OR true = true
+undefined OR false = undefined
+undefined OR undefined = undefined
+undefined AND true = undefined
+undefined AND false = undefined
+undefined AND undefined = undefined
+undefined XOR true = undefined
+undefined XOR false = undefined
+undefined XOR undefined = undefined
+
+// Short-circuit examples
+false OR true OR undefined = true
+false OR undefined OR true = true
+true AND false AND undefined = false
+true AND undefined AND false = undefined
+
+// Non-logical operators
+undefined + 5 = undefined
+-undefined = undefined
+!undefined = undefined
+```
+
+If the result of the main rule is `undefined`, it is treated as `false` but is indicative of erroneous logic.
+
+## Expressions
+
+### Operand
+
+Operands denote the elementary values in an expression. An operand may be a literal, an identifier denoting a variable, rule, or function, or a parenthesized expression.
+
+```ebnf
+Operand = Literal | OperandName | MethodExpr | "(" Expression ")" .
+Literal = BasicLit | MapLit | ListLit | FunctionLit | RuleLit .
+BasicLit = int_lit | float_lit | string_lit .
+OperandName = identifier .
+```
+
+### Primary Expressions
+
+Primary expressions are the operands for unary and binary expressions.
+
+```ebnf
+PrimaryExpr =
+ Operand |
+ PrimaryExpr Selector |
+ PrimaryExpr Index |
+ PrimaryExpr Slice |
+ PrimaryExpr Arguments .
+
+Selector = "." identifier .
+Index = "[" Expression "]" .
+Slice = "[" [ Expression ] ":" [ Expression ] "]" .
+Arguments = "(" [ Expression { "," Expression } ] ")" .
+```
+
+```sentinel
+x
+2
+(s + ".txt")
+f(3.1415, true)
+m["foo"]
+s[i : j + 1]
+obj.color
+f.p[i].x()
+x is empty
+x is not empty
+```
+
+### Null
+
+The reserved word `null` denote the singleton value `null`. Null represents the explicit absence of a value. Behavior of `null` within expressions is specified explicitly for each expression.
+
+### Booleans
+
+### Boolean Literals
+
+The reserved words `true` and `false` denote objects that represent the boolean values `true` and `false`, respectively. These are boolean literals.
+
+```ebnf
+BoolLit = "true" | "false" .
+```
+
+#### Boolean Expressions
+
+Boolean expressions are expressions that must result in a boolean value. Any other value type becomes the `undefined` value.
+
+```ebnf
+BoolExpr = Expression .
+```
+
+### List Literals
+
+A list literal denotes a list, which is an integer indexed collection of values.
+
+```ebnf
+ListLit = "[" [ ElementList [ "," ] ] "]" .
+ElementList = Element { "," Element } .
+Element = Expression | LiteralValue .
+```
+
+A list may contain zero or more values. The number of values in a list is its length.
+
+A list has a set of indices. An empty list has an empty set of indices. A non-empty list has the index set `{0...n - 1}` where `n` is the length of the list. Attempting to access a list using an index that is not a member of its set of indices results in the `undefined` value.
+
+### Map Literals
+
+A map literal denotes a map, which is an unordered group of elements indexed by a set of unique keys.
+
+```ebnf
+MapLit = "{" [ KeyedElementList [ "," ] ] "}" .
+KeyedElementList = KeyedElement { "," KeyedElement } .
+KeyedElement = Element ":" Element .
+```
+
+Keys can only be a boolean, numeric, or string type.
+
+The value of a non-existent key is the `undefined` value.
+
+### Function Literals
+
+A function literal represents a function.
+
+```ebnf
+FunctionLit = "func" Function .
+Function = Parameters FunctionBody .
+FunctionBody = Block .
+Parameters = "(" [ IdentifierList [ "," ] ] ")" .
+IdentifierList = identifier { "," identifier } .
+```
+
+```sentinel
+func(a, b) { return b }
+```
+
+Function literals are only allowed in the file scope. They may refer to variables defined in surrounding blocks.
+
+A function must terminate with a return statement. If a function has no meaningful return value, it should return `undefined`.
+
+### Rule Expressions
+
+A rule is an expression that is evaluated lazily and the result is memoized.
+
+If the optional "when" predicate is present, the rule is evaluated only when
+the "when" boolean expression results in true. If the predicate is false,
+the rule is not evaluated and returns true. The predicate is evaluated when
+the rule would be evaluated; it is also lazy and memoized in the same way.
+
+```ebnf
+RuleExpr = "rule" [ "when" BoolExpr ] "{" Expr "}" .
+```
+
+```sentinel
+rule { x is y }
+
+rule {
+ x == "value" or
+ y == "other"
+}
+
+rule when x is y { y > 42 }
+
+rule { map ["a", "b", "c"] as _, id {
+ { "id": id }
+}}
+```
+
+### Index Expressions
+
+A primary expression of the form `a[x]` denotes the element of a list or map indexed by x. The value x is called the index or key.
+
+For `a` of type map:
+
+- If `a` contains a key `x`, `a[x]` is the map value with key `x`.
+- If `a` does not contain key `x`, `a[x]` is the `undefined` value.
+
+For `a` of type list:
+
+- `x` must be an integer
+- `x` must be in the range `[-1 * length(a), length(a)-1]`
+- If `x` is contained in the set of indices of `a`, `a[x]` is the list element at index `x`. If `x` is negative, `a[x]` is equivalent to `a[length(a)+x]`.
+- If `x` is not contained in the set of indices of `a`, `a[x]` is the `undefined` value.
+
+For `a` of value `null`:
+
+- `a[x]` is the `undefined` value.
+
+Otherwise `a[x]` is an error.
+
+### Selectors
+
+For a primary expression x, the selector expression `x.f` denotes the field `f` of the value `x`. The identifier `f` is called the selector. The type of the selector expression is the type of the selector.
+
+As a special case, selectors can be [reserved words](#keywords) and keyword [operators](#operators-and-delimiters), but cannot be any other non-identifier element.
+
+Selectors are used to access data from [imports](#imports). The first primary expression `x` in `x.f` denotes the import name. The field `f` is the selector to access data from the import.
+
+Selectors may also be used to access map data with static keys. They are syntactic sugar over index expressions. `math.pi` is equivalent to `math["pi"]` and exhibit the same limitations and behavior as an index expression. Selectors cannot, however, be used to assign values to map data.
+
+Selectors on undefined result in undefined.
+
+```sentinel
+math.pi
+time.pst.hour
+```
+
+### Slice Expressions
+
+Slice expressions construct a substring or list from a string or list.
+
+The primary expression
+
+```sentinel
+a[low : high]
+```
+
+constructs a substring or list. The indices `low` and `high` select which elements of operand `a` appear in the result. The result has indices starting at 0 and length equal to `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+```
+
+For convenience, any of the indices may be omitted. A missing `low` index defaults to zero; a missing `high` index defaults to the length of the sliced operand:
+
+```sentinel
+a[2:] // same as a[2 : length(a)]
+a[:3] // same as a[0 : 3]
+a[:] // same as a[0 : length(a)]
+```
+
+The indices are in range if `0 <= low <= high <= length(a)`, otherwise they are out of range. If the indices are out of range at run time, the result is the `undefined` value.
+
+If `a` is the value `null`, the result is the `undefined` value.
+
+If `a` is any other value type, it is an error.
+
+### Calls
+
+```ebnf
+CallExpr = identifier Arguments .
+```
+
+Given an expression `f` where `f` is a function value:
+
+```sentinel
+f(a1, a2, … an)
+```
+
+calls `f` with arguments `a1, a2, ... an`. The type of the expression is the result of `f`. The arguments are evaluated left to right. Arguments are passed by value.
+
+### Operators
+
+```ebnf
+Expression = UnaryExpr | Expression binary_op Expression .
+UnaryExpr = PrimaryExpr | unary_op UnaryExpr .
+
+binary_op = logical_op | set_op | rel_op | add_op | mul_op | else_op .
+logical_op = "and" | "or" | "xor" .
+set_op = ["not"] ( "contains" | "in" ).
+rel_op = "==" | "!=" | "<" | "<=" | ">" | ">=" |
+ "is" | "is not" | "matches" | "not matches" .
+add_op = "+" | "-" .
+mul_op = "*" | "/" | "%" .
+else_op = "else" .
+unary_op = "+" | "-" | "!" | "not" | empty_op | defined_op .
+empty_op = "is empty" | "is not empty" .
+defined_op = "is defined" | "is not defined" .
+```
+
+#### Operator Precedence
+
+Unary operators have the highest precedence.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Binary operators of the same precedence associate from left to right. For instance, x / y _ z is the same as (x / y) _ z.
+
+### Arithmetic Operators
+
+Arithmetic operators apply to numeric values and yield a result of the same type when both operands are the same.
+
+Arithmetic operations between integer and floating-point types result in a floating-point value with the integer operand treated as a floating-point value for purposes of calculation.
+
+Arithmetic operations between numeric values (integer, floating-point) and non-numeric values (strings, lists) are not permitted.
+
+All five supported arithmetic operators (+, -, \*, /, %) apply to both integer and floating-point types; + also applies to strings.
+
+```plaintext
++ sum integers, floats, strings, lists
+- difference integers, floats
+* product integers, floats
+/ quotient integers, floats
+% remainder integers, floats
+```
+
+#### Integer operators
+
+For two integer values x and y, the integer quotient `q = x / y` and remainder `r = x % y` satisfy the following relationships:
+
+```sentinel
+x = q*y + r and |r| < |y|
+```
+
+with x / y truncated towards zero.
+
+```plaintext
+ x y x / y x % y
+ 5 3 1 2
+-5 3 -1 -2
+ 5 -3 -1 2
+-5 -3 1 -2
+```
+
+As an exception to this rule, if the dividend x is the most negative value for the int type of x (-9223372036854775808), the quotient `q = x / -1` is equal to x (and r = 0).
+
+If the divisor is a constant, it must not be zero. If the divisor is zero at run time, an error occurs.
+
+#### Integer overflow
+
+For signed integers, the operations `+`, `-`, and `*` may legally overflow and the resulting value exists and is deterministically defined by the signed integer representation, the operation, and its operands. No exception is raised as a result of overflow.
+
+#### Floating-point operators
+
+For floating-point numbers, +x is the same as x, while -x is the negation of x. The result of a floating-point division by zero is not specified beyond the IEEE-754 standard.
+
+#### String Concatenation
+
+Strings can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = "hi"
+y = "hello"
+x = x + ", " + y // "hi, hello"
+x += " and good bye" // "hi, hello and good bye"
+```
+
+String addition creates a new string by concatenating the operands.
+
+#### List Concatenation
+
+Lists can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = [1, 2]
+x = x + [2, 3] // [1, 2, 2, 3]
+x += [4] // [1, 2, 2, 3, 4]
+```
+
+List addition creates a new list by concatenating the operands.
+
+### Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+In any comparison, the two operands must be equivalent types. The only exception are integers and floats, which are considered numeric and may be compared. Any comparison between other non-matching types results in the `undefined` value.
+
+The equality operators `==`, `!=`, `is`, and `is not` apply to operands that are comparable. The ordering operators `<`, `<=`, `>`, and `>=` apply to operands that are ordered. The behavior of `is` with `==` and `is not` with `!=` is identical. These terms and the result of the comparisons are defined as follows:
+
+- Boolean values are comparable. Two boolean values are equal if they are either both true or both false.
+- Integer values are comparable and ordered, in the usual way.
+- Floating point values are comparable and ordered, as defined by the IEEE-754 standard.
+- An integer compared with floating point value treats the integer as the converted floating point value.
+- String values are comparable and ordered, lexically byte-wise.
+- Lists are comparable. Lists are equal if they are of equal length and their
+ corresponding elements are comparable and equal.
+- Maps are comparable. Maps are equal if they are of equal length and both their
+ corresponding keys and values are comparable and equal.
+
+If either operand is the `undefined` value, the result of the expression is the `undefined` value.
+
+### Emptiness Comparisons
+
+Checking the emptiness of an object can be achieved by using one of the emptiness expressions, `is empty` or `is not empty`. Although similar to [Comparison Operators](#comparison-operators) in that they yield a boolean value and read as though a comparison is taking place, both `is empty` and `is not empty` are evaluated as a multi-word expression.
+
+An emptiness comparison can only be performed against collections, strings or `undefined`, with the same rules as the [built-in length](/sentinel/functions/length) function.
+
+```sentinel
+"" is empty // true
+"foo" is empty // false
+[] is empty // true
+[1] is empty // false
+{} is empty // true
+{"a": "b"} is empty // false
+
+"" is not empty // false
+"foo" is not empty // true
+[] is not empty // false
+[1] is not empty // true
+{} is not empty // false
+{"a": "b"} is not empty // true
+
+undefined is empty // undefined
+undefined is not empty // undefined
+```
+
+### Logical Operators
+
+Logical operators apply to boolean values and yield a boolean result.
+
+Logical operators are evaluated left-to-right and perform short-circuit logic. The right-hand side is not guaranteed to be evaluated if a short-circuit can be performed.
+
+```plaintext
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+### Set Operators
+
+The set operators `contains` and `in` test for set inclusion for lists
+and maps, and substring inclusion for strings.
+
+Set operators may be negated by prefixing the operator with `not`: `not contains` and `not in`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+`contains` tests if the left-hand collection contains the right-hand value. For lists, this tests equality of any value. For maps, this tests equality of any key. For strings, this tests for substring existence.
+
+`in` tests if the right-hand collection contains the left-hand value. The behavior is equivalent to `contains`.
+
+The collection must be a list, map, or string. If it is the `undefined` value, the result is the `undefined` value. For any other value, the result is an error.
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+
+"test" contains "est" // true
+"test" contains "best" // false
+"test" in "testing" // true
+"best" in "testing" // false
+```
+
+### Matches Operator
+
+The matches operator tests if a string matches a regular expression.
+
+The matches operators may be negated by prefixing the operator with `not`: `not matches`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+The left-hand value is the string to test. The right-hand value is a string value representing a regular expression.
+
+The syntax of the regular expression is the same general syntax used by Python, Ruby, and other languages. More precisely, it is the syntax accepted by RE2. The regular expression is not anchored by default; any anchoring must be explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+If either operand is `undefined`, the result is the `undefined` value. For any other non-string value, the result is an error.
+
+### Else Operator
+
+Else operators can be used to provide a default value for an expression that may evaluate to the `undefined` value. Else operators return their left-hand value unless it is `undefined`, otherwise they return their right-hand value.
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else null
+```
+
+### Quantifier Expressions (any, all, filter, map)
+
+Quantifiers are expressions that apply a predicate to a collection (list or map). The purpose of the quantifier is to produce a result based on its particular type.
+
+`any` and `all` expressions are existential and universal quantifiers, respectively. `any` expressions are equivalent to a chain of `or` and `all` expressions are equivalent to a chain of `and`. Both expressions implement short-circuiting equivalent to logical operators.
+
+The `map` expression is an apply-to-all quantifier and returns a new list for all values in the input collection. The output list will be the same length as the input collection.
+
+The `filter` expression is a subset quantifier and returns the subset of all values in the input collection for which the supplied predicate applies. This will be zero or more elements, up to the length of the input.
+
+The body of a quantifier expression is a boolean expression.
+
+`any` returns the boolean value `true` if any value in the collection expression results in the body expression evaluating to `true`. If the body expression evaluates to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression result in the body expression evaluating to `true`. If any value in the collection expression result in the body expression evaluating to `false`, the all expression returns `false`.
+
+For empty collections, `any` returns false and `all` returns `true`.
+
+`map` always returns a list, regardless of if the input collection is a list itself; maps (as in the data type) also return lists when processed through `map`. Each element is the result of the supplied expression body.
+
+`filter` returns a list or map, the same type as its input collection. Only the elements for which the expression body evaluated to `true` will be returned. If an iteration of the expression body results in `undefined`, the entire result set is `undefined`.
+
+When the collection is a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When the collection is a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+QuantExpr = QuantOp Expression "as" [ identifier "," ] identifier "{" BoolExpr "}" .
+QuantOp = "all" | "any" | "filter" | "map" .
+```
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" } // true if every iterations is true
+any group.tasks as t { t.driver is "vmware" } // true if any iteration is true
+map group.tasks as t { { "driver": t.driver } } // [{"driver": "vmware"}, ...]
+filter group.tasks as t { t.driver is "vmware" } // subset of tasks that is true
+```
+
+## Statements
+
+### Expression Statements
+
+Function call expressions may exist in the statement context.
+
+```ebnf
+ExpressionStmt = Expression .
+```
+
+### Assignments
+
+Assignments set the value of an expression to the value of another expression.
+
+```ebnf
+Assignment = AssignExpr assign_op Expression .
+AssignExpr = identifier | IndexExpr .
+assign_op = [ add_op | mul_op ] "=" .
+```
+
+The assignment `x op= y` is equivalent to `x = x op (y)` for supported values of `op`.
+
+```sentinel
+a = 1
+b[12] = 42
+c["key"] = "value"
+
+a *= 12
+b[12] += 8
+```
+
+Assignments to lists and maps can be carried out using [index expressions](#index-expressions), with the operands of the index expression being evaluated alongside the right hand side expression in a right-to-left (RHS, LHS) order.
+
+In addition to the rules detailed in the section describing their use, the following rules apply when assigning to maps or lists using index expressions:
+
+- The [variable](#variables) must exist and be a list or map. Attempts to use an index assignment on an unknown variable, or a variable that not a list or map, results in a runtime error.
+- Assigning to a list or map index that already exists overwrites that index's data with evaluated right hand side's value.
+- Assigning to an unknown key in a map creates a key for that map with the evaluated right hand side's value assigned to that key.
+- Attempting to assign a value to a list index that is out of range results in a runtime error.
+
+### If Statements
+
+If statements specify the conditional execution of two branches according to the value of a boolean expression. If the expression evaluates to true, the "if" branch is executed, otherwise, if present, the "else" branch is executed.
+
+```ebnf
+IfStmt = "if" BoolExpr Block [ "else" ( IfStmt | Block ) ] .
+```
+
+```sentinel
+if x < y {
+ return x
+} else if x > z {
+ return z
+} else {
+ return y
+}
+```
+
+### Case Statements
+
+Case statements specify a selection control mechanism to conditionally execute a branch based on expression equality.
+
+Each clause that wishes to provide an expression must use the "when" keyword. Multiple expressions can be provided, separated with a comma (",").
+
+There can be one, optional, "else" clause within a `case` statement. The "else" clause is executed if all other clauses failed to run.
+
+The clauses are evaluated in the order they are listed, with the first successful evaluation being executed.
+
+A `case` statement can optionally provide an expression. If no expression is provided, it is the equivalent of writing `case true {`.
+
+```ebnf
+CaseStmt = "case" [ Expression ] "{" { CaseWhenClause } "}" .
+CaseWhenClause = CaseWhenCase ":" StatementList .
+CaseWhenCase = "when" Expression { "," Expression } | "else" .
+```
+
+```sentinel
+case x {
+when y, z:
+ return true
+else:
+ return false
+}
+
+case {
+when x > 42:
+ return true
+else:
+ return false
+}
+```
+
+### For Statements
+
+A `for` statement specifies repeated execution of a block.
+
+The expression must evaluate to a list or a map.
+
+When iterating over a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When iterating over a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+ForStmt = "for" Expressions "as" [ identifier "," ] identifier Block .
+```
+
+```sentinel
+for [1, 2, 3] as v { count += v } // basic sum
+
+for [1, 2, 3] as idx, v {
+ if idx > 1 { count += v }
+}
+
+data = { "a": 12, "b": 32 }
+for data as k { count += data[k] } // sum map values
+for data as k, v { count += v }
+```
+
+### Break Statements
+
+A `break` statement terminates execution of the innermost `for` statement
+within the same function.
+
+```ebnf
+BreakStmt = "break" .
+```
+
+```sentinel
+// Will only print "1"
+for [1, 2, 3] as v {
+ print(v)
+ break
+}
+```
+
+### Continue Statements
+
+A `continue` statement begins the next iteration of the innermost `for` loop.
+The `for` loop must be within the same function.
+
+```ebnf
+ContinueStmt = "continue" .
+```
+
+```sentinel
+// Will print 1, 3
+for [1, 2, 3] as v {
+ if v == 2 {
+ continue
+ }
+
+ print(v)
+ break
+}
+```
+
+### Return Statements
+
+A return statement in a function F terminates the execution of F and provides the result value.
+
+```ebnf
+ReturnStmt = "return" Expression .
+```
+
+A function must terminate with a return statement.
+
+The return statement can be invoked at any time during a function to terminate execution.
+
+## Imports
+
+An import adds an assignment to the global scope to external definitions.
+
+The import declarations must only appear at the top of the source file, before any other statements. Comments may appear before imports.
+
+```ebnf
+ImportDecl = "import" Name ["as" identifier] .
+Name = string_lit .
+```
+
+An import name and identifier must be distinct. Two names may not repeated even if they're declared with different identifiers.
+
+If an identifier is not specified, the identifier is the name of the import itself.
+
+An import is not a valid value. The identifier representing the import may not be used as an expression, such as in assignment statements or call expressions.
+
+Values in imports are not assignable directly via their selectors. Function calls may be used to alter the state of the external data represented by the import. Whether or not this is supported is specific to the import implementation. The exception to this assignment restriction is the memoized data returned by a call to an import (see below).
+
+Data returned by imports can be memoized, meaning that data is returned by the import in the form of a map which is then used as much as possible without having to return to the import for more data. For example, `t = time.now` returns a map so that `t.hour` will be able to be looked up without having to go back to the import for that data.
+
+Data returned by imports may be callable, meaning that methods may be called on the memoized data returned by an import. For example, given `t = time.now`, `t.after(some_previous_time)` is valid, with the receiver data being set to the local memoized data stored in `t`. It is a valid case to accept modifications to the receiver data (example: assignment to the memoized data via index expressions), as long as all data in the receiver continues to be string-keyed. It is an invalid case to accept receiver data with non-string-typed keys. Methods may also alter the contents of receiver data so that it is different after the call is finished.
+
+As with methods, imports may also have callable keys where the data may not be memoized. Example: in the event of `v = foo.bar`, if `v.baz` is not included in the memoized data, a call will be made to the import to fetch it. The same rules and restrictions to receiver data apply to callable keys as do to method calls.
+
+The support for callable methods and non-memoized keys on data returned by imports is specific to the import implementation.
+
+The handling of import loading is specific to the runtime implementation.
+
+Implicit imports may be added by the runtime, for example for standard library definitions. An explicit import of the same name should override a matching implicit import. For example, if "time" is an implicit import and the program specifies `import "time" as cal`, then only `cal` is defined.
+
+```sentinel
+import "time"
+import "math" as m
+```
+
+## Parameters
+
+```ebnf
+ParamDecl = "param" identifier [ "default" Expression ]
+```
+
+A parameter is a way for a policy to describe variables that are expected to be supplied by the calling application, in addition to any default value.
+
+Parameter declarations must appear at the top of the source file, following imports.
+
+Like imports, comments may appear before parameters; this is mainly pertinent when the policy is not importing anything, which would make parameters the first declarations that appear in a policy.
+
+A parameter declaration describes a valid variable that is added to the scope of a policy at runtime. This variable has the same properties and rules as other variables assigned during the course of policy evaluation as defined by the specification. This includes having a dynamic type and being able to be re-assigned during execution.
+
+Parameters may only be strings, integers, floating point numbers, booleans, lists, or maps. More complex types such as rules or functions are not allowed.
+
+A parameter can specify a default value by adding one in the declaration via use of the "default" keyword, and supplying a literal for the default value. The literal for a default is a very limited expression, comprising of:
+
+- For strings, a simple string literal;
+- For integers and floating-point numbers, either a literal denoting the value, or a unary expression with the literal, and the operators - or +;
+- For booleans, the pre-declared identifiers true or false;
+- For lists and maps, their respective literals composed of values and keys (for maps) of the above values only.
+
+Any other type of expression or identifier is not allowed.
+
+A parameter that does not have a default value defined is required by the policy. Evaluating a policy with a required parameter that has not been supplied at execution results in a runtime error.
+
+Parameters must not conflict with imports or any other identifiers currently defined in the global scope, including any reserved or pre-declared identifiers. Attempting to assign a parameter to an existing value results in a runtime error.
+
+```sentinel
+import "time"
+
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+param time default "11:00" // error, conflicts with "time" import
+param undefined // error, reserved identifier
+```
+
+## Built-in Functions
+
+Built-in functions are predeclared. They are called like any other function.
+
+### Length
+
+The built-in function `length` returns the length of a collection of string.
+
+The length of a string is the number of bytes in the string. It is not necessarilly the number of characters.
+
+The length of a collection is the number of elements in that collection.
+
+The length of `undefined` is `undefined`.
+
+### Collections
+
+#### List Append
+
+The built-in function `append` appends a value to the end of a list in-place.
+
+Appending to a non-list value results in an immediate `fail`.
+
+The return value of `append` is always the `undefined` value.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+#### Map Delete
+
+The built-in function `delete` deletes elements from a map by key. The map is modified in-place.
+
+Deleting a key that does not exist does nothing.
+
+Calling delete for a non-map value results in an immediate `fail`.
+
+The return value of `delete` is always the `undefined` value.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
+
+#### Keys and Values
+
+The built-in function `keys` and `values` return the keys and values of a map, respectively. The return value is a list. Both functions return values in an unspecified order.
+
+The `keys` or `values` of `undefined` is `undefined`.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+### Range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it defaults to `1`.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4]
+range(1, 5, 2) // [1,3]
+range(0, -3, -1) // [0,-1,-2]
+```
+
+### Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a value of that type according to the rules below.
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
+
+### Printing
+
+The built-in function `print` can be used to output formatted debug information. The runtime may omit print function calls depending on its execution mode. The runtime may choose where the output of a print function goes.
+
+`print` is a variadic function, accepting one or more values to be printed; values are separated by a single whitespace character (`" "`).
+
+The return value of `print` is always `true` to allow it to be used in boolean expressions.
+
+```sentinel
+print("hello") // "hello"
+print("hello", "world") // "hello world"
+print("The", "number", "is", 42) // "The number is 42"
+print([1, 2, 3]) // "[1, 2, 3]"
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // "false"
+```
+
+### Errors
+
+The built-in function `error` is equivalent to `print` but immediately causes execution to halt and the main rule to evaluate to `false`. The program execution is considered to have failed.
+
+## Program Execution
+
+Program execution begins by evaluating the source top to bottom. If evaluation is successful, the `main` rule is evaluated and its result is returned. Execution can be interrupted at any time by an error, which can be called explicitly or implicitly through illegal or undefined behavior.
+
+---
+
+> The content of this page is licensed under the [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
+>
+> Based on [The Go Programming Language Specification](https://golang.org/ref/spec) licensed under [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/language/undefined.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/language/undefined.mdx
new file mode 100644
index 0000000000..dc51531783
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/language/undefined.mdx
@@ -0,0 +1,97 @@
+---
+page_title: Sentinel Language - Undefined
+sidebar_current: docs-language-undefined
+description: >-
+ The value `undefined` is a special value representing undefined behavior or an
+ unknown value. Any operation on an `undefined` value results in `undefined`.
+layout: docs
+---
+
+# Language: Undefined
+
+The value `undefined` is a special value representing undefined behavior
+or an unknown value.
+
+Undefined is not an error because there are situations where the undefined
+value can be safely ignored and the language also provides construts for
+recovering from an undefined value.
+
+The undefined value can be created manually with `undefined`. In most cases,
+undefined is created by accessing a non-existent list element or value.
+The undefined value is created in a number of other circumstances. The documentation
+will note when an undefined value may be created.
+
+Almost any operation with `undefined` results in `undefined`. For example,
+performing math on undefined, attempting to call a function that is undefined,
+etc.
+
+## Main and Undefined
+
+If the value `undefined` is returned from the top-level `main` rule, then
+the policy is considered `false`. However, the runtime provides mechanisms
+for the host application to determine the policy failure was caused by
+an undefined value and report that to the user.
+
+The `main` rule returning the undefined value should be considered a logical
+error in most cases and should probably be corrected by the policy writer.
+
+## Debugging Undefined
+
+When an `undefined` value is first created (either explicitly or implicitly),
+the runtime will record the position where the undefined was created. This
+location can be used to find logic that could be erroneous.
+
+## Boolean Logic and Undefined
+
+Boolean logic is one way to safely recover from `undefined`. If boolean
+logic can safely determine a result despite an undefined value, that result
+will be returned.
+
+For example: `undefined or true` will result in `true`, because no matter
+what actual value `undefined` could've been if the policy behaved properly,
+the boolean expression would still be true.
+
+Another scenario where `undefined` can be safely ignored is short-circuit
+logic with `and`. `false and undefined` will result in `false`.
+
+The full table of logical operations on `undefined` is below:
+
+```sentinel
+undefined or true = true
+undefined or false = undefined
+undefined or undefined = undefined
+undefined and true = undefined
+undefined and false = undefined
+undefined and undefined = undefined
+undefined xor true = undefined
+undefined xor false = undefined
+undefined xor undefined = undefined
+
+// Short-circuit examples
+false or true or undefined = true
+false or undefined or true = true
+true and false and undefined = false
+true and undefined and false = undefined
+```
+
+## Else Expressions
+
+The `else` expression allows explicit recovery from undefined and is useful
+for setting default values.
+
+`else` is an operator where the left and right hand side are values. If the
+left-hand value is `undefined`, the right-hand value is returned. Otherwise,
+the left-hand value is returned.
+
+Examples:
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else "default"
+
+// In more complex scenarios
+
+foo() else 42 == 12
+a = config.value else "default"
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/language/values.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/language/values.mdx
new file mode 100644
index 0000000000..f932480340
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/language/values.mdx
@@ -0,0 +1,210 @@
+---
+page_title: Sentinel Language - Values
+sidebar_current: docs-language-values
+description: Values are the data that Sentinel policies operate on.
+layout: docs
+---
+
+# Language: Values
+
+Values are the _data_ that Sentinel policies operate on. You can create this
+data yourself, for example by just typing the number `42`, or you may access
+this data from an external source to make policy decisions.
+
+Values have a _type_, which determines what kind of operations can be performed
+on the data. Example types are booleans (true and false), numbers,
+and strings (text).
+
+This page documents all the available value types. You can also
+[convert between types](#type-conversion).
+
+## Boolean
+
+A boolean is a value that is either true or false.
+
+A boolean value is created literally with `true` or `false`.
+
+As a policy language, booleans are central to the behavior of Sentinel.
+Booleans are used as conditions in [if statements](/sentinel/language/conditionals),
+are the result of [rules](/sentinel/language/rules), and more. The ultimate
+result of a Sentinel policy is true or false.
+
+## Integer
+
+An integer is a whole number.
+
+An integer is a 64-bit value. This means it can represent numbers from
+-9,223,372,036,854,775,808 to 9,223,372,036,854,775,808.
+
+Integers are created by typing them out literally with no
+separators (such as a comma). An optional prefix can set a non-decimal base:
+`0` for octal, and `0x` for hexadecimal. In hexadecimal literals, letters
+`a-f` and `A-F` represent values 10 to 15.
+
+Example integers are shown below:
+
+```sentinel
+42
+0600
+0xBadFace
+170141183460469
+```
+
+Integers are used for math and numerical comparison.
+
+## Float
+
+A float is a number with a decimal point. It has an integer part, a decimal
+point, a fractional part, and optionally an exponent part. Example
+floats are shown below:
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+Floats are IEEE-754 64-bit floating point numbers.
+
+## String
+
+Strings are text values.
+
+Strings are created by wrapping text in double quotes, such as `"hello"`.
+Within the quotes, any character may appear except newline and an unescaped
+double quote. The text between the quotes is the value.
+
+Because Sentinel policies must be UTF-8 encoded text, strings themselves are
+UTF-8 encoded text. This means you can put any value UTF-8 character into
+a string, such as `"日本語"`.
+
+You can have a string with a literal double-quote by _escaping_ it with
+a backslash. For example: `"they said \"hello\""` turns into the value
+`they said "hello"`.
+
+Backslash escapes can be used for much more than only escaping a double quote.
+Newlines are `\n`, a literal backslash is `\\`, and many more. The full list
+of available backslash escapes can be found
+[in the language specification](/sentinel/language/spec#string-literals).
+
+Example strings:
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+Strings do not support indexing, however it is possible to achieve a similar
+result through the combination of the [strings](/sentinel/imports/strings)
+standard import and list indexing.
+
+```sentinel playground
+import "strings"
+
+asciistr = "Hello, world!"
+utf8str = "日本語"
+
+utf8split = rule {
+ strings.split(utf8str, "")[2] == "語"
+}
+
+asciisplit = rule {
+ strings.split(asciistr, "")[5] == ","
+}
+
+main = rule { utf8split and asciisplit }
+```
+
+Strings support [slicing](/sentinel/language/slices) to efficiently create
+substrings.
+
+## List
+
+See [Lists](/sentinel/language/lists).
+
+## Map
+
+See [Maps](/sentinel/language/maps).
+
+## Rule
+
+See [Rules](/sentinel/language/rules).
+
+## Function
+
+See [Functions](/sentinel/language/functions).
+
+## Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a
+value of that type. Some examples are shown below followed by a list of exact
+rules for type conversion.
+
+```sentinel
+int(42) // 42
+int("42") // 42
+int(42.8) // 42
+int(true) // 1
+
+float(1.2) // 1.2
+float(1) // 1.0
+float("4.2") // 4.2
+float(true) // 1.0
+
+string("foo") // "foo"
+string(88) // "88"
+string(0xF) // "15"
+string(true) // "true"
+
+bool("true") // true
+bool(1) // true
+bool(-1) // true
+bool(0.1) // true
+bool("false") // false
+bool(0) // false
+```
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/language/variables.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/language/variables.mdx
new file mode 100644
index 0000000000..7516890212
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/language/variables.mdx
@@ -0,0 +1,99 @@
+---
+page_title: Sentinel Language - Variables
+sidebar_current: docs-language-vars
+description: Variables store values that can be used later.
+layout: docs
+---
+
+# Language: Variables
+
+Variables store values that can be used later.
+
+Most notably, a variable assignment of `main` is required for all
+Sentinel policies. This is the main [rule](/sentinel/language/rules) that
+is executed to determine the result of a policy. The `main` rule may
+use other variables that are assigned in the policy.
+
+Example:
+
+```sentinel
+a = 1
+b = a + 1
+```
+
+In this example, two variables are assigned: `a` and `b`. `a` is given
+the value of "1" and `b` uses the `a` to calculate its own value.
+
+## Syntax
+
+The syntax for assigning a variable is:
+
+```sentinel
+IDENTIFIER = VALUE
+```
+
+On the left of the equal sign is an _identifier_. This is a name for your
+variable and will be how you reference it later. An identifier is any
+combination of letters and digits, but must start with a letter. An
+underscore `_` is also a valid letter.
+
+On the right is any valid Sentinel value or expression. A value is a
+literal value such as a number or string. An expression is some computed
+value such as doing math, calling a function, etc.
+
+## Assignment and Reassignment
+
+A variable is _assigned_ when it is given a value. You can also _reassign_
+variables at any time by setting it to a new value. The new value takes
+effect for any subsequent use of that variable. A variable can be reassigned
+to a different type.
+
+For example:
+
+```sentinel
+a = 1 // a = 1
+b = a // b = 1
+a = "value" // a = "value", b = 1
+c = a // c = "value", b = 1
+```
+
+In the above example you can see that the variables are set and reassigned.
+Notice that the value of a variable is the _current_ value, and that reassigning
+a variable only affects _future_ uses of that variable. You can see this with
+`c` and `b` being different values.
+
+## Unassigned Variables
+
+Using a variable that is _unassigned_ is an error.
+
+In the example below, the first line would result in the policy erroring.
+Sentinel is executed top-down and the value of `c` is not available yet
+on the first line.
+
+```sentinel
+a = c // Error!
+c = 1
+```
+
+## Type Conversion
+
+While a topic more related to [values][lang-values], variables can be assigned
+(or-reassigned) a different value type via type conversion.
+
+[lang-values]: /sentinel/language/values
+
+```sentinel
+s = "1.1"
+a = int(s) // a = 1
+a = float(s) // a = 1.1
+
+a = 1
+s = string(a) // s = "1"
+```
+
+For more details, see the type conversion sections in the
+[values][lang-values-type-conversion] page, or the [Sentinel Language
+Specification][lang-spec-type-conversion].
+
+[lang-values-type-conversion]: /sentinel/language/values#type-conversion
+[lang-spec-type-conversion]: /sentinel/language/spec#type-conversion
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/nomad.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/nomad.mdx
new file mode 100644
index 0000000000..a105b0337d
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/nomad.mdx
@@ -0,0 +1,51 @@
+---
+page_title: Nomad and Sentinel
+sidebar_current: docs-app-nomad
+description: >-
+ Nomad Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies can currently execute on job
+ submission (creation, update).
+layout: docs
+---
+
+# Nomad
+
+Nomad is a simple, flexible scheduler and workload orchestrator.
+[Nomad Enterprise](https://www.hashicorp.com/products/nomad/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies can currently execute on job submission (creation, update).
+
+Sentinel policies have full access to the job structure. This allows the
+Sentinel policy to control behavior based on any attribute within a job,
+such as the driver, resource requests, network configuration, volume
+configuration, and more. The information that Sentinel policies have access
+to will expand over time.
+
+Nomad fully supports all [enforcement levels](/sentinel/concepts/enforcement-levels).
+For soft mandatory policies, the `sentinel-override` capability must be
+available on the user's ACL policy to allow override. Overrides are always
+logged.
+
+The Nomad integration with Sentinel is documented in depth in the
+[Nomad Enterprise documentation](https://www.nomadproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Only allow Docker-based jobs.**
+
+```sentinel
+# Test policy only allows Docker based tasks
+main = rule { all_drivers_docker }
+
+# all_drivers_docker checks that all the drivers in use are Docker
+
+all_drivers_docker = rule {
+ all job.task_groups as tg {
+ all tg.tasks as task {
+ task.driver is "docker"
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/terraform.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/terraform.mdx
new file mode 100644
index 0000000000..9e4c430ddb
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/terraform.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Terraform and Sentinel
+sidebar_current: docs-app-terraform
+description: >-
+ Use Sentinel with HCP Terraform and Terraform Enterprise to enforce policy
+ on Terraform configurations, states, and plans.
+layout: docs
+---
+
+# Terraform
+
+[HCP Terraform and Terraform Enterprise](https://www.hashicorp.com/products/terraform/) use Sentinel to enforce
+policy on [Terraform](https://www.terraform.io) configurations, states, and plans.
+
+The Sentinel integration with Terraform runs within
+HCP Terraform and Terraform Enterprise
+after a `terraform plan` and before a `terraform apply`. The policies
+have access to the created plan, the state at the time of the plan,
+and the configuration at the time of the plan.
+
+The Terraform integration with Sentinel is documented in depth in the [HCP Terraform and Terraform Enterprise documentation](/terraform/cloud-docs/policy-enforcement/sentinel).
+Please read that page for full documentation. This page will only show basic examples.
+
+### Examples
+
+**Example: All AWS instances must have a tag**
+
+```sentinel
+import "tfplan"
+
+main = rule {
+ all tfplan.resources.aws*instance as *, instances {
+ all instances as \_, r {
+ (length(r.applied.tags) else 0) > 0
+ }
+ }
+}
+```
+
+**Example: Only allow GCP instance sizes smaller than `n1-standard-16`**
+
+```sentinel
+import "tfplan"
+
+allowed_machine_types = [
+ "n1-standard-1",
+ "n1-standard-2",
+ "n1-standard-4",
+ "n1-standard-8",
+]
+
+main = rule {
+ all tfplan.resources.google_compute_instance as _, instances {
+ all instances as _, r {
+ r.applied.machine_type in allowed_machine_types
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/vault.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/vault.mdx
new file mode 100644
index 0000000000..6db2f8af22
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/vault.mdx
@@ -0,0 +1,64 @@
+---
+page_title: Vault and Sentinel
+sidebar_current: docs-app-vault
+description: >-
+ Vault Enterprise uses Sentinel to augment the built-in policy system to
+ provide Role Governing Policies (RGPs) and Endpoint Governing Policies (EGPs)
+ to enable complex, flexible policies across identities and endpoints.
+layout: docs
+---
+
+# Vault
+
+[Vault Enterprise](https://www.hashicorp.com/products/vault/) uses Sentinel
+to augment the built-in policy system to provide Role Governing Policies (RGPs)
+and Endpoint Governing Policies (EGPs) to enable complex, flexible policies
+across identities and endpoints.
+
+**Role Governing Policies (RGPs)** are Sentinel policies that are tied to
+particular tokens, Identity entities, or Identity groups. They have access to
+a rich set of controls across various aspects of Vault. These are evaluated
+whenever a token they're attached to is used.
+
+**Endpoint Governing Policies (EGPs)** are Sentinel policies that are tied to
+particular paths instead of tokens. They have access to as much request
+information as possible, but they can take effect even on unauthenticated
+paths, such as login paths.
+
+The Vault integration with Sentinel is documented in depth in the
+[Vault Enterprise documentation](https://www.vaultproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Endpoint policy that requires MFA authentication from a corporate network.**
+
+```sentinel
+import "sockaddr"
+
+# We expect logins to come only from our private IP range
+cidrcheck = rule {
+ sockaddr.is_contained(request.connection.remote_addr, "10.20.0.0/16")
+}
+
+# Require Ping MFA validation to succeed
+ping_valid = rule {
+ mfa.methods.ping.valid
+}
+
+main = rule when request.path is "auth/ldap/login" {
+ ping_valid and cidrcheck
+}
+```
+
+**Example: Endpoint policy that disallows tokens created before a certain time.**
+
+```sentinel
+import "time"
+import "strings"
+
+main = rule when not strings.has_prefix(request.path, "auth/ldap/login") {
+ time.load(token.creation_time).unix > time.load("2017-09-17T13:25:29Z").unix
+}
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/writing/basic.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/writing/basic.mdx
new file mode 100644
index 0000000000..b62533466e
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/writing/basic.mdx
@@ -0,0 +1,113 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-basic
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Policy Basics
+
+Sentinel policies are easy to write while still supporting advanced constructs
+for creating complex policies. This page will explain the basics of writing
+Sentinel policies to get started. You don't need any prior Sentinel knowledge,
+but we do recommend reading the
+[getting started guide](/sentinel/intro/getting-started/first-policy) and
+[language guide](/sentinel/language) after this.
+
+Sentinel policies are text files written using the [Sentinel language](/sentinel/language).
+The policies are evaluated top-to-bottom. The value of `main` after execution
+determines whether a policy passes or fails.
+
+## The Simplest Policy
+
+Sentinel only requires that a policy have a `main` variable that evaluates to
+a boolean value.
+
+A valid example is shown below:
+
+```sentinel playground
+main = 10 > 5
+```
+
+This type of minimal policy is not purely academic. In practice, simple
+policies can often be reduced to a single line logical statement resulting
+in `true` or `false`. However, the expression is usually wrapped in a
+[rule](/sentinel/language/rules) for testing reasons.
+
+You can verify Sentinel will execute this minimal policy using the CLI:
+
+```
+$ sentinel apply minimal.sentinel
+Pass
+```
+
+## Logical Expressions
+
+Policy is at its core a set of logic: you can or can not perform some action
+under a certain set of circumstances. Those circumstances are logical
+expressions. Therefore, Sentinel policies primarily translate into logical
+expressions.
+
+Detailed documentation on boolean expressions is
+[available in the language guide](/sentinel/language/boolexpr).
+
+A simple numerical comparison was seen in the first example on this page.
+Sentinel also provides inclusion operators such as `contains`, `any`, `all`, and
+more. Sentinel allows some operators to have aliases to promote readability
+while remaining programmer-familiar, such as `==` which can equivalently be `is`.
+
+The example below verifies that all numbers in a list are even:
+
+```sentinel playground
+numbers = [6, 22, 8, 4, 12]
+main = all numbers as n { n % 2 is 0 }
+```
+
+## Variables
+
+A policy will very often use variables. Applications such as
+[Nomad](https://www.nomadproject.io) inject variables into the global
+scope of a policy for making policy decisions. For example, Nomad injects
+the job that is being run into the policy scope. Knowing how to use
+variables is critical to effectively using Sentinel.
+
+Detailed documentation on how to define and access variables is
+[available in the language guide](/sentinel/language/variables).
+
+Variables can be defined and used explicitly. For example:
+
+```sentinel playground
+value = 10
+main = value > 5
+```
+
+But they may also be introduced implictly by the host system. Nomad
+injects `job` into policies to describe the job that is being run. The
+policy below is a valid policy that requires a job have two task groups.
+Notice that `job` is not defined anywhere. It is implicitly inserted by
+the host application (in this case Nomad). Refer to the application you're
+writing policy for to determine if it implicitly inserts values.
+
+```json sentinel
+{
+ "policy": "main = length(job.task_groups) is 2",
+ "globals": {
+ "job": {
+ "task_groups": ["foo", "bar"]
+ }
+ }
+}
+```
+
+## And more!
+
+The Sentinel language supports many more features such as functions,
+loops, and more. You can learn about all of this in the
+[complete language guide](/sentinel/language).
+
+The other pages in the [writing policy](/sentinel/writing)
+will cover other information you need to know about writing Sentinel
+policies that isn't simply a language reference.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/writing/debugging.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/writing/debugging.mdx
new file mode 100644
index 0000000000..7dc284d222
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/writing/debugging.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-debugging
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Debugging
+
+As policies become more complex, it becomes harder to understand exactly why
+a policy may be behaving differently than you expect.
+
+Prior to actively debugging, there are multiple best practices we recommend
+following. We recommend breaking your policy down into a set of
+[rules](/sentinel/writing/rules). This will make it easier to understand
+[traces](/sentinel/writing/tracing) and make it easier to
+[test](/sentinel/writing/testing) your policies.
+This should help to debug policies up to a significant complexity.
+
+## Print Logging
+
+The built-in [print function](/sentinel/language/logging-errors#print)
+can be used to log data. The print output is only shown during failures
+or when [tracing is explicitly enabled](/sentinel/writing/tracing).
+
+The `print` function outputs each argument, which can be of any type, converted
+to a human-friendly string format. Arguments are separated with a single space.
+
+Example:
+
+```sentinel playground
+value = 42
+print("the number is", value) // the number is 42
+
+object = { "foo": false }
+print(object) // { "foo": false }
+
+main = rule { true }
+```
+
+The `print` function returns the value `true` so that it can also be
+used within rule expressions. Note that the `print` function should be
+used at the beginning of statements otherwise they may never be
+reached. This is due to internal performance techniques within Sentinel
+like [short-circuiting](https://en.wikipedia.org/wiki/Short-circuit_evaluation).
+
+Example:
+
+```sentinel playground
+// rule_a and rule_b will evaluate to false
+rule_a = rule { print("rule_a is printed") and false } // "rule_a is printed"
+rule_b = rule { false and print("rule_b is printed") } // Nothing is printed
+
+// rule_c and rule_d will evaluate to true
+rule_c = rule { print("rule_c is printed") or true } // "rule_c is printed"
+rule_d = rule { true or print("rule_d is printed") } // Nothing is printed
+
+main = rule { rule_a or rule_b or rule_c and rule_d }
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/writing/imports.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/writing/imports.mdx
new file mode 100644
index 0000000000..74ac751ee4
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/writing/imports.mdx
@@ -0,0 +1,117 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom import](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only local
+context for making policy decisions.
+
+Sentinel also comes with a set of [standard imports](/sentinel/imports).
+Standard imports are available to every Sentinel policy to help policy writers
+with common tasks such as working with the time, network addresses, and more.
+
+-> **This page is about writing policies that _use_ imports.** If you're
+interested in _creating_ a new import, please see the section on [extending
+Sentinel](/sentinel/extending) for information on how to write modules and
+import plugins.
+
+## Using Imports
+
+To use an import, you use the `import` keyword at the top of your policy. This
+specifies the name of the import you want to use. The application you're writing
+the policy for must already be
+[configured](/sentinel/configuration#imports) to provide that
+import.
+
+Details on imports can be found in the
+[import section in the language reference](/sentinel/language/imports).
+
+In the example below, we use the [time](/sentinel/imports/time) import:
+
+```sentinel playground
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+There are two options to develop a policy locally when using imports:
+configure Sentinel to launch the import on `apply`, or mock the import
+using `mock`. The former requires access to the import while the
+latter is faster (doesn't have to launch a process for plugins) and
+doesn't require the import.
+
+## Mocking Imports
+
+The first option to developing policies locally is to mock the import values.
+When mocking an import, you don't need the import to be available. This can be
+useful since some imports may not be available as a plugin and may only be
+available to the application the policy runs in.
+
+Mocks are specified via the [configuration
+file](/sentinel/configuration). Mocks can also be used for
+[testing](/sentinel/writing/testing).
+
+You can supply mock configuration one of two ways, depending on your use case:
+
+- **[Using static data](/sentinel/configuration#mocking-static-data):**
+ Use this method when you can accurately represent your mock data in JSON and
+ do not need to mock complex Sentinel features such as functions.
+- **[Using Sentinel code](/sentinel/configuration#mocking-with-sentinel-code):**
+ Use this method when using a static JSON object is insufficient, such as when
+ you need to mock functions or other complex Sentinel features.
+
+Our example does not require complex data to be mocked, so a static object
+is sufficient:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ hour = 12
+ weekday_name = "Tuesday"
+ }
+ }
+}
+```
+
+This can be used via the CLI:
+
+```
+$ sentinel apply -config=config.hcl policy.sentinel
+Pass
+```
+
+## Launching Import Plugins
+
+If you have access to the plugin binary, you can launch the import. The
+benefit of this is that it is really using the import to test your
+policy. If the import changes, your policies may start failing. If you
+only use mock data and the import changes, your policies will still
+appear to work.
+
+Imports are configured in the configuration file:
+
+```hcl
+import "plugin" "custom_time" {
+ source = "/path/to/sentinel-time-import"
+}
+```
+
+This would require the `sentinel-time-import` binary. For this example
+this doesn't currently exist. We plan on writing one to provide for this
+section of the documentation.
+
+## Custom Imports
+
+You can also [create your own imports](/sentinel/extending).
+
+If your policy decisions could benefit from accessing external information,
+then you can use custom imports as a way to do this.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/writing/index.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/writing/index.mdx
new file mode 100644
index 0000000000..064069d389
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/writing/index.mdx
@@ -0,0 +1,35 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing
+description: >-
+ Sentinel provides a language and workflow for building policy across any
+ system that embeds Sentinel. By learning Sentinel once, you are able to
+ effectively control access to many systems using Sentinel's powerful features.
+ Basic concepts that are important to understand for Vault usage.
+layout: docs
+---
+
+# Writing Sentinel Policy
+
+This section covers how to write Sentinel policies.
+
+It is recommended that you complete the
+[getting started guide](/sentinel/intro/getting-started/first-policy) prior to
+reading this section of the documentation. The getting started guide will
+explain all the basics of writing and testing policies. This section of the
+documentation will serve as more of a reference guide to all available
+features of Sentinel.
+
+Sentinel provides a language and workflow for building policy across any system
+that embeds Sentinel. By learning Sentinel once, you are able to effectively
+control access to many systems using Sentinel's powerful features.
+
+Sentinel also provides a [local CLI](/sentinel/commands) for developing and testing
+Sentinel policies. This CLI can be integrated into a daily developer's workflow
+along with CI to treat [policy as code](/sentinel/concepts/policy-as-code).
+
+Sentinel uses its own [language](/sentinel/language) for writing
+policies. You can view a [language reference](/sentinel/language)
+as well as the [specification](/sentinel/language/spec) for details. You
+don't have to read those documents immediately, since the language should
+be easy enough to pick up throughout this section.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/writing/rules.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/writing/rules.mdx
new file mode 100644
index 0000000000..ab4fc69d08
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/writing/rules.mdx
@@ -0,0 +1,65 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-rules
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Rules
+
+Rules form the basis of a policy by representing behavior that is either passing
+or failing. Rules are a first class language construct in Sentinel. A policy can
+and should be broken down into rules to aid with readability, testability, and
+performance.
+
+Rules provide:
+
+- **Readability:** A policy that is broken down into rules can be read
+ more easily. The logic becomes clearer to see for policy writers when
+ they come back to it.
+
+- **Reportability:** When [tracing](/sentinel/writing/tracing) is enabled,
+ rules form the foundation of the data returned. All evaluated rules are added
+ to the trace with the data the rule evaluated to, and their position within
+ policy or module source files.
+
+- **Testability:** [Policy testing](/sentinel/writing/testing) is
+ built on asserting the values of rules. By breaking logic down into
+ rules, you're able to more effectively test your policies.
+
+- **Performance:** Rules are only evaluated on demand, and are also only
+ evaluated once. This means that a rule referenced multiple times only has a
+ one-time performance cost. For complex logic, this could result in improved
+ performance.
+
+An example usage of rules is shown below:
+
+```json sentinel
+{
+ "policy": "is_sunny = rule { weather is \"sunny\" }\nis_wednesday = rule { day is \"wednesday\" }\nmain = rule { is_sunny and is_wednesday }",
+ "globals": {
+ "weather": "sunny",
+ "day": "wednesday"
+ }
+}
+```
+
+Rules can also contain non-boolean data. This can be useful for passing more
+detail to the caller than an opaque boolean value would be able to communicate.
+
+```sentinel playground
+days = [{"weather": "sunny"}]
+main = rule {
+ filter days as d {
+ d.weather is not "sunny"
+ }
+}
+```
+
+Details about rules and their behavior can be found in
+[the language reference](/sentinel/language/rules). Rules have important
+behavior so we recommend reading the language reference on rules.
+Rules will be used throughout the remainder of the documentation.
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/writing/testing.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/writing/testing.mdx
new file mode 100644
index 0000000000..5d8a522c28
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/writing/testing.mdx
@@ -0,0 +1,495 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-testing
+description: >-
+ Sentinel has a built-in test framework to validate a policy behaves as
+ expected.
+layout: docs
+---
+
+# Testing
+
+Sentinel has a built-in test framework to validate a policy behaves as expected.
+
+With an ever-increasing amount of automation surrounding technology,
+the guardrails provided by policies are a critical piece towards ensuring
+expected behavior. As a reliance on correct policy increases, it is important
+to test and verify policies.
+
+Testing is a necessary step to fully realize
+[policy as code](/sentinel/concepts/policy-as-code). Just as good software
+is well tested, a good set of policies should be equally well tested.
+
+## Test Folder Structure
+
+Policies are tested by asserting that [rules](/sentinel/writing/rules)
+are the expected values given a pre-configured input. Tests are run
+by executing the [test command](/sentinel/commands/test).
+
+Sentinel is opinionated about the folder structure required for tests.
+This opinionated structure allows testing to be as simple as running
+`sentinel test` with no arguments. Additionally, it becomes simple to test
+in a CI or add new policies.
+
+The structure Sentinel expects is `test//*.[hcl|json]` where ``
+is the name of your policy file without the file extension. Within that folder
+is a list of HCL or JSON files. Each file represents a single test case.
+Therefore, each policy can have multiple tests associated with it.
+
+-> Note that the primary configuration file format for Sentinel is HCL. While
+you can write configuration files in a HCL-equivalent JSON, we only discuss the
+use of HCL on this page.
+
+## Test Case Format
+
+Each HCL file within the test folder for a policy is a single test case.
+
+The file is the same configuration format as the [CLI configuration
+file](/sentinel/configuration). The format lets you define mock data, imports to
+use, and more. This mock data is the key piece in being able to test policies:
+you craft a specific scenario and assert your policy behaves as you expect.
+
+Test cases also use the `test` block within the configuration file to assert the
+value of [rules](/sentinel/writing/rules). If the `test` key is omitted, the
+policy is expected to pass. If the test key is specified, only the rules
+specified in the map will be asserted. This means if you omit `main`, then the
+final policy result is not asserted.
+
+Example with assertions:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+
+test {
+ rules = {
+ main = false
+ is_open_hours = false
+ is_weekday = true
+ }
+}
+```
+
+The configuration above specifies some parameter data, and asserts the result of
+some rules. This is the same configuration used in the example section below.
+
+## Example
+
+Lets use the following file as an example. Save this file to a directory
+and name it `policy.sentinel`. It can be named anything with the `sentinel`
+extension, but by naming it `policy.sentinel` your output should match
+the example output on this page.
+
+```sentinel
+// The day of the week.
+param day
+
+// The hour of the day.
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+### A Passing Test
+
+Next, let's define a single test case. Relative to where you saved
+the policy, create a file at the path `test/policy/good.hcl`.
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 14
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/good.hcl
+```
+
+This passed because the policy passed. We didn't assert any specific
+rules. By not specifying any assertions, `test` expects the policy itself
+to fully pass.
+
+### A Failing Test
+
+Define another test case to fail. We want to verify our policy fails when expected, too.
+
+Save the following as `test/policy/7-am.hcl`:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+FAIL - policy.sentinel
+ FAIL - test/policy/7-am.hcl
+ expected "main" to be true, got: false
+
+ trace:
+ policy.sentinel:9:1 - Rule "main"
+ bool: false
+
+ policy.sentinel:8:1 - Rule "is_open_hours"
+ bool: false
+
+ PASS - test/policy/good.hcl
+```
+
+As you can see, the test fails because "main" is false. This is good
+because the policy _should_ have failed since we specified an invalid
+hour. But, we expect main to be false and don't want our test to fail!
+Update `7-am.hcl` to add test assertions:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+
+test {
+ rules = {
+ main = false
+ is_open_hours = false
+ }
+}
+```
+
+And when we run the tests:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/7-am.hcl
+ PASS - test/policy/good.hcl
+```
+
+The test passes. We asserted that we expect the `main` rule to be false,
+the `is_open_hours` rule to be false, and the `is_weekday` rule to be
+true. By asserting some rules are true, we can verify that our policy
+is failing for reasons we expect.
+
+## Mocking
+
+The above example demonstrates how to test by supplying different
+[parameters](/sentinel/language/parameters). Parameters in a policy can be
+specifically useful when you want to control user-defined input values to a
+policy.
+
+However, generally, when testing, you will need mimic the conditions you will
+see in production. Production implementations of Sentinel will supply data using
+one of two methods:
+
+- **Global data:** Data is injected directly into the policy's scope and is
+ accessible using normal identifiers, similar to variables.
+- **Imports:** Data is stored behind an [import](/sentinel/language/imports)
+ and loaded on demand as needed by the policy author.
+
+Proper testing of a policy requires that these values be able to be _mocked_ -
+or, in other words, simulated in a way that allows the accurate testing of the
+scenarios that a policy could reasonably pass or fail under.
+
+Mocking both globals and imports can be done by setting various parts of the
+configuration file.
+
+### Mocking Globals
+
+Demonstrating the mocking of globals can be seen by making a few modifications to
+our example policy, removing the `param` declarations:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Then, change the `param` section in the configuration file to
+[`global`](/sentinel/configuration#global).
+
+```json
+global "day" {
+ value = "monday"
+}
+
+global "hour" {
+ value = 14
+}
+```
+
+This test should still pass, as if nothing had happened, although what we've
+done is shifted our parameters to globals, simulating an environment where `day`
+and `hour` are already defined for us.
+
+### Mocking Imports
+
+To mock imports, we need to use the
+[`mock`](/sentinel/configuration#mock-imports) section of the
+configuration file.
+
+Let's say the above example is behind an import named `time`.
+
+-> **NOTE:** [`time`](/sentinel/imports/time) is a valid standard import.
+This example may not be accurate to the
+import's syntax.
+
+The code now looks like this:
+
+```sentinel
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To mock this import, we can [mock it as static
+data](/sentinel/configuration#mocking-static-data). The configuration
+file now looks like, without assertions:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ weekday_name = "Monday"
+ hour = 14
+ }
+ }
+}
+```
+
+The policy will now pass, with the `time` import mocked.
+
+Data can also be [mocked as Sentinel
+code](/sentinel/configuration#mocking-with-sentinel-code). In this case,
+the above configuration file would look like:
+
+```hcl
+mock "time" {
+ module {
+ source = "mock-time.sentinel"
+ }
+}
+```
+
+And a file named `mock-time.sentinel` would now hold your mock values:
+
+```sentinel
+day = "monday"
+hour = 14
+```
+
+Mocking as Sentinel code allows more complex details to be mocked as well, such
+as functions. Say we wanted to mock the `time.load()` function. To mock this,
+just add it to the `mock-time.sentinel` file:
+
+```sentinel
+load = func(_) {
+ return {
+ "weekday_name": "Monday",
+ "hour": 14,
+ }
+}
+```
+
+Your code can now be written as:
+
+```sentinel
+import "time"
+
+t = time.load("a_mock_timestamp")
+
+is_weekday = rule { t.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { t.hour > 8 and t.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To see more details, see the [Mock
+Imports](/sentinel/configuration#mock-imports) section in the
+configuration file.
+
+## Asserting Non-Boolean Rules
+
+Non-boolean rules can also be asserted by `sentinel test`.
+
+To assert non-boolean values, simply enter the expected value into the rule
+contents:
+
+```hcl
+param "maintenance_days" {
+ value = [
+ {
+ day = "wednesday"
+ hour = 9
+ },
+ {
+ day = "friday"
+ hour = 1
+ },
+ {
+ day = "sunday"
+ hour = 1
+ },
+ ]
+}
+
+test {
+ rules = {
+ main = [
+ {
+ day = "wednesday"
+ hour = 9
+ },
+ ]
+ }
+}
+```
+
+This would assert a policy checking for violations of a maintenance policy,
+saying that maintenance hours should happen before 6AM on any given day:
+
+```sentinel
+param maintenance_days
+
+main = rule {
+ filter maintenance_days as d {
+ d.hour >= 6
+ }
+}
+```
+
+## Test JSON Output
+
+Running `sentinel test` with the `-json` flag will give you the test results in
+a JSON output format, suitable for parsing by reporting software.
+
+-> **Note:** The JSON output format is currently under development and the
+format may change at a later time. Additionally, support for other well-known
+formats, such as JUnit, may become available in the future.
+
+The current format is an object with the top-level keys being `policies` and
+`duration`, with each test grouped up by policy being run. `duration` represents
+time taken in milliseconds for all policies to run.
+
+The policy result fields are:
+
+- `path`: The path of the policy and the index of the test result in the
+ `policies` field in the root object.
+- `status`: A string representation of the policy's test status as a whole. Can
+ be one of `PASS`, `FAIL`, `ERROR`, or `?`. The final status, `?`, represents a
+ policy that has no tests to process, and acts like a passing test.
+- `errors`: An array of any error messages encountered during processing the
+ policy for testing. Usually reserved for policy file or parser-related errors.
+ For case-specific errors, see the error field for the particular case.
+- `cases`: A map of case results, indexed by case path.
+- `duration`: Time taken in milliseconds for the policy to run.
+
+The case result fields are:
+
+- `path`: The path of the test case and the result's index in the `cases` field
+ in the policy object.
+- `status`: A string representation of the case's test status. Can be one of
+ `PASS`, `FAIL` or `ERROR`.
+- `errors`: An array of any error messages encountered during running this test
+ case.
+- `trace`: The trace for this policy in JSON format. See the
+ [tracing](/sentinel/writing/tracing) page for more details.
+- `rule_detail`: When the `status` of this test case is `FAIL`, contains an
+ object of assertion failure detail, indexed by rule. Only failures are counted
+ here; any rules not found here can be assumed to have passed or not asserted.
+- `config_warnings`: An array of strings denoting any configuration warnings
+ found while processing the configuration for this test case.
+- `config_legacy`: Denotes whether or not the configuration is a legacy JSON
+ configuration and needs to be modernized. This field may be removed in future
+ releases.
+
+A passing example is shown below:
+
+```json
+{
+ "policies": {
+ "policy.sentinel": {
+ "path": "policy.sentinel",
+ "status": "PASS",
+ "errors": null,
+ "duration": 5,
+ "cases": {
+ "test/policy/pass.hcl": {
+ "path": "test/policy/pass.hcl",
+ "status": "PASS",
+ "errors": null,
+ "trace": {
+ "description": "A very basic policy to determine if a run is within working hours.",
+ "error": null,
+ "print": "",
+ "result": true,
+ "rules": {
+ "is_open_hours": {
+ "desc": "Passes if run during business hours.",
+ "ident": "is_open_hours",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 290,
+ "line": 13,
+ "column": 1
+ },
+ "value": true
+ },
+ "is_weekday": {
+ "desc": "Passes if the day does not fall on the weekend.",
+ "ident": "is_weekday",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 193,
+ "line": 10,
+ "column": 1
+ },
+ "value": true
+ },
+ "main": {
+ "desc": "",
+ "ident": "main",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 338,
+ "line": 14,
+ "column": 1
+ },
+ "value": true
+ }
+ }
+ },
+ "rule_detail": {},
+ "config_warnings": null,
+ "config_legacy": false
+ }
+ }
+ }
+ },
+ "duration": 10
+}
+```
diff --git a/content/sentinel/v0.26.x/content/sentinel/docs/writing/tracing.mdx b/content/sentinel/v0.26.x/content/sentinel/docs/writing/tracing.mdx
new file mode 100644
index 0000000000..e98767da00
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/docs/writing/tracing.mdx
@@ -0,0 +1,403 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-tracing
+description: >-
+ Sentinel provides execution traces to better understand the execution of a
+ policy.
+layout: docs
+---
+
+# Traces
+
+Sentinel provides execution traces to better understand the execution of a policy.
+
+When using the Sentinel [CLI](/sentinel/commands), traces are shown
+on policy failure during `apply` or `test`, or when the `-trace` flag is
+explicitly specified. For Sentinel-enabled applications, traces are optional
+and may require special configuration to enable.
+
+The availability of the trace may vary from application to application. While
+some applications may only log traces on failure, others, such as [Terraform
+Cloud](https://www.terraform.io/cloud), log the trace on every execution. For
+more details, consult your implementation's documentation.
+
+-> **Note:** We're actively improving Sentinel tracing in each release
+to provide better data and improve readability. The exact format of a
+trace may not match the Sentinel version embedded in the application you're
+using, but the data should be similar. The canonical format for a trace should
+get more stable as we approach a 1.0 release.
+
+## Analyzing a Trace
+
+To show traces, let's use the example policy below with the CLI:
+
+```sentinel
+param day
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+
+main = rule { is_open_hours and is_weekday }
+```
+
+Let's cause the policy to fail so the trace is more interesting.
+If you're on a terminal that supports it, the trace output will be colored
+so you can more clearly see passes, failures, and rules.
+
+
+
+ $ sentinel apply main.sentinel{'\n'}
+
+ main.sentinel:1:7: requires value for parameter day
+
+ {'\n'}
+ {'\n'}
+ {'\n'}
+ {' '}Values can be strings, floats, or JSON array or object values. To force
+ {'\n'}
+ {' '}strings, use quotes.{'\n'}
+ {'\n'}
+ {' '}Enter a value: sunday{'\n'}
+ {'\n'}
+
+ main.sentinel:2:7: requires value for parameter hour
+
+ {'\n'}
+ {'\n'}
+ {'\n'}
+ {' '}Values can be strings, floats, or JSON array or object values. To force
+ {'\n'}
+ {' '}strings, use quotes.{'\n'}
+ {'\n'}
+ {' '}Enter a value: 14{'\n'}
+ {'\n'}
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+
+ Fail - main.sentinel
+
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:7:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}
+ false
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:5:1 - Rule "is_open_hours"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}true{'\n'}
+ {'\n'}
+
+ main.sentinel:4:1 - Rule "is_weekday"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}false{'\n'}
+
+
+
+We can see all of our rules neatly and clearly displayed along with the result
+of the policy. If you are getting the trace due to a failed policy and not
+because you explicitly used `-trace`, an informational message is displayed.
+
+As we can see from our basic example, the `main` rule has failed and its result
+is highlighted in red. All peripheral rules that were also evaluated as part of
+the policy are also displayed below the main rule. Source positions are also
+displayed so that you can find the references to the rules in your code.
+
+Via the trace, we can clearly see that while `is_open_hours` returned a true
+result, `is_weekday` did not, and per the logic in our code, the policy is
+rejected.
+
+## Non-Boolean Rules and the Trace
+
+The trace is particularly important when working with rules that return
+non-boolean data, such as rules where you want to see violations instead of a
+simple opaque boolean value. Let's take our example from the [rules](./rules)
+section, and write a small static policy for it:
+
+```sentinel playground
+days = [
+ {"weekday": "Sunday", "weather": "clouds"},
+ {"weekday": "Monday", "weather": "rain"},
+ {"weekday": "Tuesday", "weather": "sunny"},
+ {"weekday": "Wednesday", "weather": "sunny"},
+ {"weekday": "Thursday", "weather": "clouds"},
+ {"weekday": "Friday", "weather": "rain"},
+ {"weekday": "Saturday", "weather": "sunny"},
+]
+
+main = rule {
+ filter days as d {
+ d.weather is not "sunny"
+ }
+}
+```
+
+Here is the output of our policy:
+
+
+
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+ {'\n'}
+
+ Fail - weather.sentinel
+
+ {'\n'}
+ {'\n'}
+
+ weather.sentinel:11:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+
+ {' [\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Sunday"'}
+ {'\n'}
+ {' "weather": "clouds"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Monday"'}
+ {'\n'}
+ {' "weather": "rain"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Thursday"'}
+ {'\n'}
+ {' "weather": "clouds"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Friday"'}
+ {'\n'}
+ {' "weather": "rain"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' ]'}
+
+
+
+
+We can now see all of the weekdays with non-sunny weather.
+
+Note that to prevent formatting issues and excessive output, the human-readable
+trace is limited in the volume of output it will display for collections.
+
+## JSON Output
+
+The trace can also be displayed in JSON by adding `-json` to `sentinel apply`
+and `sentinel test`, and will display the trace in its entirety, unlike the
+standard CLI output.
+
+Here is the result of running `sentinel apply -json` against our weather policy:
+
+```json
+{
+ "can_override": false,
+ "error": null,
+ "duration": 12,
+ "policies": [
+ {
+ "allowed_failure": false,
+ "error": null,
+ "duration": 12,
+ "policy": "weather.sentinel",
+ "result": false,
+ "trace": {
+ "description": "",
+ "error": null,
+ "print": "",
+ "result": false,
+ "rules": {
+ "main": {
+ "desc": "",
+ "ident": "main",
+ "position": {
+ "filename": "weather.sentinel",
+ "offset": 328,
+ "line": 11,
+ "column": 1
+ },
+ "value": [
+ {
+ "weather": "clouds",
+ "weekday": "Sunday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Monday"
+ },
+ {
+ "weather": "clouds",
+ "weekday": "Thursday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Friday"
+ }
+ ]
+ }
+ }
+ }
+ }
+ ],
+ "result": false
+}
+```
+
+Note that the trace is always included with this output, regardless of whether
+or not the policy passes or fails; using `-trace` is unnecessary.
+
+Additionally, with `sentinel apply`, you can display the value of a single rule
+in JSON, instead of the entire trace. This is done with the `-json-rule` flag.
+
+-> **Note:** `sentinel apply -json-rule` needs to be run either against a single
+on-disk policy, or against a single policy within a policy set. It is ignored
+in full policy set executions and functions exactly like `-json` in these
+scenarios.
+
+Here is the result of executing `sentinel apply -json-rule main weather.sentinel`:
+
+```json
+[
+ {
+ "weather": "clouds",
+ "weekday": "Sunday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Monday"
+ },
+ {
+ "weather": "clouds",
+ "weekday": "Thursday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Friday"
+ }
+]
+```
+
+## Other Trace Details
+
+There are some other details that are good to know when working with the trace:
+
+- Only rules that get executed are added to the trace. Considering our first
+ example where the `main` rule is the expression `is_open_hours and is_weekday`,
+ if our expression was using an `or` operator instead of `and`, in addition to
+ the policy passing, `is_open_hours` would be present in the trace, but
+ `is_weekday` would not be, as the policy would have never evaluated that rule.
+- Descriptions for both rules and policies will show up in the trace as well if
+ present. Here is the output to our first policy with the appropriate
+ annotations:
+
+
+
+ ...{'\n\n'}
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+
+ Fail - main.sentinel
+
+ {'\n'}
+ {'\n'}
+ Description:{'\n'}
+ {' A very basic policy to determine if a run is within working\n'}
+ {' hours.\n'}
+ {'\n'}
+
+ main.sentinel:7:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}
+ false
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:5:1 - Rule "is_open_hours"
+
+ {'\n'}
+ {' '}
+ Description:
+ {'\n'}
+ {' Passes if run during business hours.\n'}
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}true{'\n'}
+ {'\n'}
+
+ main.sentinel:4:1 - Rule "is_weekday"
+
+ {'\n'}
+ {' '}
+ Description:
+ {'\n'}
+ {' Passes if the day does not fall on the weekend.\n'}
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}false{'\n'}
+
+
diff --git a/content/sentinel/v0.26.x/content/sentinel/intro/getting-started/first-policy.mdx b/content/sentinel/v0.26.x/content/sentinel/intro/getting-started/first-policy.mdx
new file mode 100644
index 0000000000..38d7b22c9a
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/intro/getting-started/first-policy.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Your First Sentinel Policy
+sidebar_current: intro-getting-started-first
+description: Let's begin by writing a working Sentinel policy.
+layout: intro
+---
+
+# Your First Sentinel Policy
+
+Sentinel is a system to enforce complex policies on an integrated application.
+
+Writing Sentinel policy requires minimal programming experience. The Sentinel
+language is designed to be approachable and learned quickly and easily. Whether
+you're a professional programmer or someone who uses SQL and Excel, you can
+learn to write Sentinel policies.
+
+Let's begin by writing a simple, working Sentinel policy:
+
+```sentinel playground
+hour = 4
+main = rule { hour >= 0 and hour < 12 }
+```
+
+This is a valid Sentinel policy. It will pass since we hardcoded the
+`hour` to be 4. In a real system, `hour` may be something that is provided
+to us and actually set to the current hour. We'll learn more about that later.
+
+For now, try running this policy locally. Save the above example to a file
+named `policy.sentinel` and execute it. Then, modify the policy to make it
+fail. Play around more if you'd like.
+
+```
+$ sentinel apply policy.sentinel
+Pass
+```
+
+## Main
+
+Every Sentinel policy must have a `main` rule. This is the rule that
+is evaluated to determine the result of a policy.
+
+A rule describes an expression that generally means one of two things:
+
+- Does a policy _pass a condition_ that would authorize an operation? In our
+ above example, describe a policy that checks the supplied hour (4) is within an
+ authorized time window (between 0 - midnight, and 12 noon).
+- Conversely, can a policy find any _violations_ that would block authorization
+ of the operation? Building on the above, consider a policy that takes a
+ schedule, and finds all time blocks that fall outside of the example time window
+ supplied in the above policy.
+
+It is easy to imagine that such a rule might be used in a system such
+as [Nomad](https://www.nomadproject.io) to restrict the times when a
+deploy can occur. The power of arbitrary logical statements within Sentinel
+allows Sentinel policies to restrict almost any behavior.
+
+Next, we'll
+[introduce and explain rules](/sentinel/intro/getting-started/rules)
+so we can use them in our policies.
diff --git a/content/sentinel/v0.26.x/content/sentinel/intro/getting-started/imports.mdx b/content/sentinel/v0.26.x/content/sentinel/intro/getting-started/imports.mdx
new file mode 100644
index 0000000000..dbbb146ecf
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/intro/getting-started/imports.mdx
@@ -0,0 +1,58 @@
+---
+page_title: Imports
+sidebar_current: intro-getting-started-imports
+description: Imports enable Sentinel to access external data and functions.
+layout: intro
+---
+
+# Imports
+
+Imports enable Sentinel to access external data and functions.
+
+Sentinel ships with a set of [standard imports](/sentinel/imports).
+Standard imports are available by default to every Sentinel policy. Note that
+the application embedding Sentinel may allow or deny the available
+set of imports.
+
+To use an import, use the `import` statement. Then, access data and
+functions on the import using the import name followed by a period and
+the field you want to access. The example below checks whether the request
+path starts with a prefix. Assume that `request_path` is available.
+
+```sentinel
+import "strings"
+
+main = rule { strings.has_prefix(request_path, "/admin") }
+```
+
+## External Data
+
+The true power in imports is their ability to reference external data.
+Imports can be added to a Sentinel-enabled application through
+[plugins](/sentinel/extending). This enables any Sentinel-enabled
+application to make policy decision based on any source of data.
+
+This ability allows the policy system to enforce almost any necessary
+organizational policy, since the ability of a policy isn't restricted
+purely to the embedding application's data model.
+
+For example, policies in [Nomad](https://www.nomadproject.io) can
+access data in [Consul](https://www.consul.io) to determine attributes
+of a policy. In the example below, we use a hypothetical Consul import:
+
+```sentinel
+import "consul"
+
+main = rule {
+ job.tasks[0].resources.memory <= int(consul.get("policy/nomad/max-memory"))
+}
+```
+
+In this example, a Nomad job's memory usage is limited to the value of
+a Consul key/value item. By simply changing a Consul KV entry, policy
+can be changed. Imports can do anything, you can [write your own import plugins](/sentinel/extending)
+to extend Sentinel.
+
+Next, we'll learn how to
+[test policies](/sentinel/intro/getting-started/testing)
+to ensure our policy logic is correct.
diff --git a/content/sentinel/v0.26.x/content/sentinel/intro/getting-started/index.mdx b/content/sentinel/v0.26.x/content/sentinel/intro/getting-started/index.mdx
new file mode 100644
index 0000000000..5c9b6b43c5
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/intro/getting-started/index.mdx
@@ -0,0 +1,16 @@
+---
+page_title: Install Sentinel CLI - Getting Started
+sidebar_current: intro-getting-started-overview
+description: The first step to using Sentinel is to get the CLI installed.
+layout: intro
+---
+
+# Getting Started With Sentinel
+
+This section covers getting started with Sentinel. Here, we will take you
+through the first steps that you will need to do to get started with installing
+the Sentinel CLI, writing your first policy, and getting familiar with rules,
+boolean logic, imports, and testing.
+
+You can select a topic on the menu to get started. The first step is to [install
+the Sentinel CLI](/sentinel/intro/getting-started/install).
diff --git a/content/sentinel/v0.26.x/content/sentinel/intro/getting-started/install.mdx b/content/sentinel/v0.26.x/content/sentinel/intro/getting-started/install.mdx
new file mode 100644
index 0000000000..a23a5b7239
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/intro/getting-started/install.mdx
@@ -0,0 +1,56 @@
+---
+page_title: Install Sentinel CLI - Getting Started
+sidebar_current: intro-getting-started-install
+description: The first step to using Sentinel is to get the CLI installed.
+layout: intro
+---
+
+# Install Sentinel CLI
+
+Sentinel is a policy framework that is embedded in the enterprise versions of
+HashiCorp tools. The policies you write are deployed to these applications and
+enforced there.
+
+The Sentinel CLI is a command-line interface for local development and testing.
+For the getting started guide, we'll use the CLI to learn how to write policies
+for Sentinel-enabled applications. The Sentinel CLI is distributed as a [binary
+package](/sentinel/install) for all supported platforms and architectures.
+
+## Installation Instructions
+
+To install the Sentinel CLI, find the [appropriate package](/sentinel/install) for your
+system and download it. The CLI is packaged as a zip archive.
+
+After downloading Sentinel, unzip the package. The CLI runs as a single binary
+named `sentinel`. Any other files in the package can be safely removed and
+Sentinel will still function.
+
+The final step is to make sure that the `sentinel` binary is available on the `PATH`.
+See [this page](https://stackoverflow.com/questions/14637979/how-to-permanently-set-path-on-linux)
+for instructions on setting the PATH on Linux and Mac.
+[This page](https://stackoverflow.com/questions/1618280/where-can-i-set-path-to-make-exe-on-windows)
+contains instructions for setting the PATH on Windows.
+
+## Verifying the Installation
+
+After installing the Sentinel CLI, verify the installation worked by opening a
+new terminal session and checking that the `sentinel` binary is available. By
+executing `sentinel`, you should see help output similar to the following:
+
+```
+$ sentinel
+Usage: sentinel [--version] [--help] []
+
+Available commands are:
+ apply Execute a policy and output the result
+ fmt Format Sentinel policy to a canonical format
+ test Test policies
+ version Prints the Sentinel version
+```
+
+If you get an error that the binary could not be found, then your `PATH`
+environment variable was not setup properly. Please go back and ensure that your
+`PATH` variable contains the directory where Sentinel was installed.
+
+Otherwise, the CLI is installed and ready to go. Let's celebrate by [writing our
+first policy!](/sentinel/intro/getting-started/first-policy)
diff --git a/content/sentinel/v0.26.x/content/sentinel/intro/getting-started/logic.mdx b/content/sentinel/v0.26.x/content/sentinel/intro/getting-started/logic.mdx
new file mode 100644
index 0000000000..d3716a64ea
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/intro/getting-started/logic.mdx
@@ -0,0 +1,54 @@
+---
+page_title: Logic
+sidebar_current: intro-getting-started-logic
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Logic
+
+The core of a policy is a set of logical expressions to determine whether
+a behavior is allowed or not.
+Sentinel makes it easy to write readable logical expressions to express
+the intended policy.
+
+The logical expression syntax will be familiar to anyone who has programmed
+before. Sentinel also supports a couple more unique constructs to assist
+with common policy requirements. Detailed documentation on how to write
+logical expressions and the supported operators is available in
+[the boolean expression reference](/sentinel/language/boolexpr).
+
+Example logic:
+
+```sentinel
+a is b // Equality, you can also use ==
+a is not b // Inequality, you can also use !=
+
+a > b // Relational operators, comparison
+a < b
+a >= b
+a <= b
+
+a contains b // Inclusion check, substrings, etc.
+a not contains b // Negated version of above
+```
+
+The full list of available operators can be found in
+[the boolean expression reference](/sentinel/language/boolexpr).
+
+Logic can be combined with `and` or `or`. When combined with `and`, both
+sides must result in `true`. When combined with `or`, only one side needs
+to be true to result in `true`.
+
+With these building blocks, basic rules can be built up:
+
+```sentinel
+main = rule {
+ hour >= 8 and
+ hour < 17
+}
+```
+
+Next, we'll introduce
+[imports](/sentinel/intro/getting-started/imports)
+so that we can write rules that interact with external data.
diff --git a/content/sentinel/v0.26.x/content/sentinel/intro/getting-started/next-steps.mdx b/content/sentinel/v0.26.x/content/sentinel/intro/getting-started/next-steps.mdx
new file mode 100644
index 0000000000..23b470be59
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/intro/getting-started/next-steps.mdx
@@ -0,0 +1,21 @@
+---
+page_title: Next Steps
+sidebar_current: intro-getting-started-nextsteps
+description: That concludes the getting started guide for Sentinel.
+layout: intro
+---
+
+# Next Steps
+
+That concludes the getting started guide for Sentinel. Hopefully you're excited
+about the possibilities of Sentinel and ready to put this knowledge to use to
+improve the safety of your environments.
+
+We've covered the basics of the core features of Sentinel in this guide.
+We recommend the following as next steps:
+
+- [Documentation](/sentinel) - The documentation is an in-depth
+ reference guide of all the features of Sentinel.
+
+- [Language Reference](/sentinel/language) - The language reference
+ is a complete reference to the features of the Sentine policy language.
diff --git a/content/sentinel/v0.26.x/content/sentinel/intro/getting-started/rules.mdx b/content/sentinel/v0.26.x/content/sentinel/intro/getting-started/rules.mdx
new file mode 100644
index 0000000000..f12a61fe75
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/intro/getting-started/rules.mdx
@@ -0,0 +1,86 @@
+---
+page_title: Rules
+sidebar_current: intro-getting-started-rules
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Rules
+
+Rules in Sentinel are a first-class concept. Within a policy, rules serve a few
+purposes:
+
+- They make complex logic more understandable by allowing said logic to be
+ broken down.
+- They allow assertion of this logic through
+ [testing](/sentinel/intro/getting-started/testing) of the rule's contents.
+- They facilitate reporting of data as rules get published as part of the [policy
+ trace](/sentinel/writing/tracing).
+
+A rule functions in ways similar to both a variable and a function: they hold a
+value, but are lazily evaluated; a rule's value is not assigned until the first
+time it's referenced in a policy. Additionally, while the value of evaluated
+rules will be available within a policy's trace after it's evaluated, values of
+variables - and the return value of functions - are not. Finally, a rule value
+is memoized - further references to the rule will not change its result.
+
+Rules can hold more than just boolean data. For more advanced rule patterns, see
+[the language reference](/sentinel/language/rules).
+
+## Writing Rules
+
+Let's look at the Sentinel policy we wrote in the previous section:
+
+```sentinel playground
+hour = 4
+main = rule { hour >= 0 and hour < 12 }
+```
+
+The logic in this policy is straightforward and easy to understand. However,
+it is common for policy logic to become much more complicated. Rules are the
+key abstraction for making complex logic understandable. We can turn the
+policy above into a set of rules:
+
+```sentinel playground
+hour = 4
+
+past_midnight = rule { hour >= 0 }
+before_noon = rule { hour < 12 }
+
+main = rule {
+ past_midnight and
+ before_noon
+}
+```
+
+This policy is easier to read. Our original policy was already quite
+simple, but it is easy to imagine a more complex policy becoming much
+more readable by extracting logical expressions into a set of rules.
+
+Rules don't just exist to make a policy more readable. They're also a
+testing and debugging point. For testing, you can assert that certain rules
+are certain values given a specific input to verify that your policy works
+as you expect. Testing and debugging are covered in later sections.
+
+## Conditional Rules
+
+In many cases, a rule is only valid when something else is also true.
+
+Consider the human language statement "before you eat, you must wash your hands."
+The rule "you must wash your hands" is only valid "before you eat". In any
+other scenario, the rule is meaningless.
+This is also true in a technical context. Imagine the rule "if the driver
+is Docker, you must not expose any ports." For this example, assume
+rules `is_docker` and `has_no_exposed_ports` exist. You can write this rule
+like this:
+
+```sentinel
+main = rule when is_docker { has_no_exposed_ports }
+```
+
+When `is_docker` is true, the rule is evaluated as normal. However,
+when `is_docker` is false, the rule always returns `true`, because the
+logic of the rule is meaningless.
+
+Let's learn how to create more complex rules with
+[logical expressions](/sentinel/intro/getting-started/logic).
diff --git a/content/sentinel/v0.26.x/content/sentinel/intro/getting-started/testing.mdx b/content/sentinel/v0.26.x/content/sentinel/intro/getting-started/testing.mdx
new file mode 100644
index 0000000000..22a720667b
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/intro/getting-started/testing.mdx
@@ -0,0 +1,72 @@
+---
+page_title: Testing
+sidebar_current: intro-getting-started-testing
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Testing
+
+It is important to ensure the policies you write are correct. Sentinel
+includes a built-in test framework that can be run locally and in CI
+environments to test that policies behave as expected.
+
+A common pitfall with many simple ACL systems is that they provide no easy
+way to verify their correctness. You basically have to set the ACL and try
+the behaviors against a real system to verify it is working as expected.
+This requires a lot of setup and is unique to each system.
+
+[Sentinel's built-in test framework](/sentinel/writing/testing) has zero
+dependencies. Contained within the [Sentinel CLI](/sentinel/commands), it can
+mock the data that real systems are exposing to the policy. It is designed to be
+CI-friendly and enables continuous testing of your policies. This is necessary
+for [policy as code](/sentinel/concepts/policy-as-code).
+
+Detailed documentation on testing policies is available in [the Sentinel Testing
+reference](/sentinel/writing/testing).
+
+## Writing Tests
+
+For this example, save the following policy as `officehours.sentinel`:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Next, let's write a passing test case. This will test that the policy actually passes
+when we expect it to pass. Sentinel is opiniated about the [test folder structure](/sentinel/docs/writing/testing#test-folder-structure). Save the following in `test/officehours/good.hcl`:
+
+```hcl
+global "day" {
+ value = "monday"
+}
+
+global "hour" {
+ value = 14
+}
+```
+
+And run `sentinel test`:
+
+```
+$ sentinel test
+PASS - officehours.sentinel
+ PASS - test/officehours/good.hcl
+```
+
+The `sentinel test` command will automatically find all Sentinel policies
+and their associated test cases and run them all. The test framework will
+run all HCL files as individual test cases, allowing you to test a variety
+of scenarios for your policies.
+
+Try adding another test case to force the policy to fail. This test case can
+be saved at `test/officehours/fail.hcl`. For test cases with intentional
+failures, you'll need to use the [test assertions described in the testing
+reference](/sentinel/writing/testing#a-failing-test).
+
+Now that you have a good grasp of what Sentinel is and how to use it, please feel
+free to look through the
+[next steps](/sentinel/intro/getting-started/next-steps)
+you can take to further your Sentinel knowledge.
diff --git a/content/sentinel/v0.26.x/content/sentinel/intro/index.mdx b/content/sentinel/v0.26.x/content/sentinel/intro/index.mdx
new file mode 100644
index 0000000000..785a442fb8
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/intro/index.mdx
@@ -0,0 +1,32 @@
+---
+layout: intro
+page_title: Documentation
+sidebar_current: docs-home
+description: >-
+ Sentinel is a language and framework for policy built to be embedded in
+ existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+[introduction](/sentinel/intro) to understand what Sentinel is, how it
+compares to other software, and more.
+
+
diff --git a/content/sentinel/v0.26.x/content/sentinel/intro/what.mdx b/content/sentinel/v0.26.x/content/sentinel/intro/what.mdx
new file mode 100644
index 0000000000..36853d366c
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/intro/what.mdx
@@ -0,0 +1,64 @@
+---
+page_title: Introduction
+sidebar_current: intro-what
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: intro
+---
+
+# Introduction to Sentinel
+
+Welcome to the intro guide to Sentinel! This guide is the best
+place to start with Sentinel.
+
+If you are already familiar with the basics of Sentinel, the
+[documentation](/sentinel) provides a better reference
+guide for all available features as well as internals.
+
+## What is Sentinel?
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+Sentinel provides a language for writing policy and a workflow for developing
+and testing policies independent of the software they'll be written to. We
+also provide a framework for developers to build plugins that allow a
+Sentinel-enabled system to access external information to make policy
+decisions.
+
+Most systems today have some degree of access control. You are able to define
+identities and what they have access to. These ACL systems solve an immediate
+and necessary problem of locking down a system in very broad strokes. Sentinel
+is a reusable system for more advanced software policy decisions. Sentinel
+enables:
+
+- **Fine-Grained Policy**: Most ACL systems only enable coarse-grained
+ behaviors: "read", "write", etc. Sentinel enables fine-grained behavior such
+ as disallowing a certain API call when specific parameters are present.
+
+- **Logic-Based Policy**: You can write policy using full
+ conditional logic. For example, you may only allow a certain application behavior
+ on Monday to Thursday unless there is a manager override.
+
+- **Accessing External Information**: Sentinel can source external information
+ to be used in policy decisions. For example, a policy that restricts the size
+ of a payload may read data from [Consul](https://www.consul.io) to determine
+ the payload size limit.
+
+- **Enforcement levels**: Sentinel allows policies to be defined along
+ with an "enforcement level" that dictates the pass/fail behavior of a policy.
+ Advisory policies warn if they fail, soft mandatory policies can have their
+ failures overridden, and hard mandatory policies must pass under all
+ circumstances. Having this as a built-in concept enables you to model
+ policy more accurately and completely for your organization.
+
+## Next Steps
+
+See the page on [Why Sentinel?](/sentinel/intro/why) to learn more
+about the origins of the product. Then, continue onwards with
+the [getting started guide](/sentinel/intro/getting-started/install) to write
+your first policies and see how Sentinel works in practice.
diff --git a/content/sentinel/v0.26.x/content/sentinel/intro/why.mdx b/content/sentinel/v0.26.x/content/sentinel/intro/why.mdx
new file mode 100644
index 0000000000..98b58aab92
--- /dev/null
+++ b/content/sentinel/v0.26.x/content/sentinel/intro/why.mdx
@@ -0,0 +1,49 @@
+---
+page_title: Why Sentinel?
+sidebar_current: intro-why
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: intro
+---
+
+# Why Sentinel?
+
+The growth of infrastructure and applications has been enabled in part
+by an increasing trend towards automation everywhere.
+Configuration as code (such as Chef or Puppet with [Packer](https://www.packer.io))
+has enabled automated machine configuration. Infrastructure
+as code (such as [Terraform](https://www.terraform.io)) has enabled
+automatic infrastructure creation. And schedulers (such as
+[Nomad](https://www.nomadproject.io) and Kubernetes) have enabled
+automatic application deployment.
+Sentinel enables guardrails to be put in place
+on automation while allowing the codification and automatic enforcement
+of business requirements in critical areas of your infrastructure.
+
+Meanwhile, businesses have business requirements and sometimes legal
+requirements which must be expressed in policies. Traditionally, these
+policies are enforced by humans. But in a highly automated world, the
+automation is only as fast as its slowest component. In many cases, this
+is the human verification step.
+
+As an example: before infrastructure as code and autoscaling, if an order
+came through for 5,000 new machines, a human would likely respond to the
+ticket verifying that the user really intended to order 5,000 new machines.
+Today, automation can almost always freely order 5,000 new compute instances
+without any hesitation, which can result in unintended expense or system
+instability.
+
+Sentinel introduces [policy as code](/sentinel/concepts/policy-as-code)
+and a powerful framework built-in to HashiCorp tooling to allow automation
+guardrails, business requirements, legal compliance, and more to be
+actively enforced by the running systems in realtime.
+
+With Sentinel, you can require an override to create certain numbers of
+infrastructure resources. You can disallow unsafe deployment configurations
+with Nomad. You can enforce certain key/value formats in Consul. You
+can restrict secret access by time in Vault. And more.
+
+Sentinel is available today in HashiCorp enterprise products. You can
+try Sentinel immediately with the [getting started guide](/sentinel/intro/getting-started/install) or learn more about the integrations in the
+[documentation](/sentinel).
diff --git a/content/sentinel/v0.26.x/data/docs-nav-data.json b/content/sentinel/v0.26.x/data/docs-nav-data.json
new file mode 100644
index 0000000000..3c24a533cc
--- /dev/null
+++ b/content/sentinel/v0.26.x/data/docs-nav-data.json
@@ -0,0 +1,387 @@
+[
+ {
+ "title": "Release Notes",
+ "path": "changelog"
+ },
+ {
+ "title": "Basic Concepts",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "concepts"
+ },
+ {
+ "title": "Policy as Code",
+ "path": "concepts/policy-as-code"
+ },
+ {
+ "title": "Policy Language",
+ "path": "concepts/language"
+ },
+ {
+ "title": "Imports",
+ "path": "concepts/imports"
+ },
+ {
+ "title": "Enforcement Levels",
+ "path": "concepts/enforcement-levels"
+ }
+ ]
+ },
+ {
+ "title": "Configuration File Syntax",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "configuration"
+ },
+ {
+ "title": "Override Files",
+ "path": "configuration/overrides"
+ },
+ {
+ "title": "Remote Sources",
+ "path": "configuration/remote-sources"
+ }
+ ]
+ },
+ {
+ "title": "Commands (CLI)",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "commands"
+ },
+ {
+ "title": "apply",
+ "path": "commands/apply"
+ },
+ {
+ "title": "fmt",
+ "path": "commands/fmt"
+ },
+ {
+ "title": "test",
+ "path": "commands/test"
+ }
+ ]
+ },
+ {
+ "title": "Writing Policy",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "writing"
+ },
+ {
+ "title": "Basics",
+ "path": "writing/basic"
+ },
+ {
+ "title": "Rules",
+ "path": "writing/rules"
+ },
+ {
+ "title": "Traces",
+ "path": "writing/tracing"
+ },
+ {
+ "title": "Testing",
+ "path": "writing/testing"
+ },
+ {
+ "title": "Imports",
+ "path": "writing/imports"
+ },
+ {
+ "title": "Debugging",
+ "path": "writing/debugging"
+ }
+ ]
+ },
+ {
+ "title": "Extending Sentinel",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "extending"
+ },
+ {
+ "title": "Modules",
+ "path": "extending/modules"
+ },
+ {
+ "title": "Plugins",
+ "path": "extending/plugins"
+ },
+ {
+ "title": "Static Imports",
+ "path": "extending/static-imports"
+ },
+ {
+ "title": "Internals",
+ "path": "extending/internals"
+ }
+ ]
+ },
+ {
+ "title": "Features",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "features"
+ },
+ {
+ "title": "terraform",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "features/terraform"
+ },
+ {
+ "title": "tfplan/v1",
+ "path": "features/terraform/tfplan-v1"
+ },
+ {
+ "title": "tfplan/v2",
+ "path": "features/terraform/tfplan-v2"
+ },
+ {
+ "title": "tfconfig/v1",
+ "path": "features/terraform/tfconfig-v1"
+ },
+ {
+ "title": "tfconfig/v2",
+ "path": "features/terraform/tfconfig-v2"
+ },
+ {
+ "title": "tfstate/v1",
+ "path": "features/terraform/tfstate-v1"
+ },
+ {
+ "title": "tfstate/v2",
+ "path": "features/terraform/tfstate-v2"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Language",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "language"
+ },
+ {
+ "title": "Variables",
+ "path": "language/variables"
+ },
+ {
+ "title": "Values",
+ "path": "language/values"
+ },
+ {
+ "title": "Lists",
+ "path": "language/lists"
+ },
+ {
+ "title": "Maps",
+ "path": "language/maps"
+ },
+ {
+ "title": "Rules",
+ "path": "language/rules"
+ },
+ {
+ "title": "Imports",
+ "path": "language/imports"
+ },
+ {
+ "title": "Parameters",
+ "path": "language/parameters"
+ },
+ {
+ "title": "Boolean Expressions",
+ "path": "language/boolexpr"
+ },
+ {
+ "title": "Arithmetic",
+ "path": "language/arithmetic"
+ },
+ {
+ "title": "Slices",
+ "path": "language/slices"
+ },
+ {
+ "title": "Conditionals",
+ "path": "language/conditionals"
+ },
+ {
+ "title": "Loops",
+ "path": "language/loops"
+ },
+ {
+ "title": "Collection Operations",
+ "path": "language/collection-operations"
+ },
+ {
+ "title": "Functions",
+ "path": "language/functions"
+ },
+ {
+ "title": "Scope",
+ "path": "language/scope"
+ },
+ {
+ "title": "Undefined",
+ "path": "language/undefined"
+ },
+ {
+ "title": "Logging and Errors",
+ "path": "language/logging-errors"
+ },
+ {
+ "title": "Specification",
+ "path": "language/spec"
+ }
+ ]
+ },
+ {
+ "title": "Builtin Functions",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "functions"
+ },
+ {
+ "title": "append",
+ "path": "functions/append"
+ },
+ {
+ "title": "compare",
+ "path": "functions/compare"
+ },
+ {
+ "title": "delete",
+ "path": "functions/delete"
+ },
+ {
+ "title": "error",
+ "path": "functions/error"
+ },
+ {
+ "title": "keys",
+ "path": "functions/keys"
+ },
+ {
+ "title": "length",
+ "path": "functions/length"
+ },
+ {
+ "title": "print",
+ "path": "functions/print"
+ },
+ {
+ "title": "range",
+ "path": "functions/range"
+ },
+ {
+ "title": "values",
+ "path": "functions/values"
+ }
+ ]
+ },
+ {
+ "title": "Standard Imports",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "imports"
+ },
+ {
+ "title": "collection",
+ "routes": [
+ {
+ "title": "Reference",
+ "path": "imports/collection"
+ },
+ {
+ "title": "collection/maps",
+ "path": "imports/collection/maps"
+ },
+ {
+ "title": "collection/lists",
+ "path": "imports/collection/lists"
+ }
+ ]
+ },
+ {
+ "title": "base64",
+ "path": "imports/base64"
+ },
+ {
+ "title": "decimal",
+ "path": "imports/decimal"
+ },
+ {
+ "title": "http",
+ "path": "imports/http"
+ },
+ {
+ "title": "json",
+ "path": "imports/json"
+ },
+ {
+ "title": "runtime",
+ "path": "imports/runtime"
+ },
+ {
+ "title": "sockaddr",
+ "path": "imports/sockaddr"
+ },
+ {
+ "title": "strings",
+ "path": "imports/strings"
+ },
+ {
+ "title": "time",
+ "path": "imports/time"
+ },
+ {
+ "title": "types",
+ "path": "imports/types"
+ },
+ {
+ "title": "units",
+ "path": "imports/units"
+ },
+ {
+ "title": "version",
+ "path": "imports/version"
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Consul",
+ "path": "consul"
+ },
+ {
+ "title": "Nomad",
+ "path": "nomad"
+ },
+ {
+ "title": "Terraform",
+ "path": "terraform"
+ },
+ {
+ "title": "Vault",
+ "path": "vault"
+ }
+]
diff --git a/content/sentinel/v0.26.x/data/intro-nav-data.json b/content/sentinel/v0.26.x/data/intro-nav-data.json
new file mode 100644
index 0000000000..c95181af70
--- /dev/null
+++ b/content/sentinel/v0.26.x/data/intro-nav-data.json
@@ -0,0 +1,47 @@
+[
+ {
+ "title": "What is Sentinel?",
+ "path": "what"
+ },
+ {
+ "title": "Why Sentinel?",
+ "path": "why"
+ },
+ {
+ "title": "Getting Started",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "getting-started"
+ },
+ {
+ "title": "Installing the CLI",
+ "path": "getting-started/install"
+ },
+ {
+ "title": "Your First Policy",
+ "path": "getting-started/first-policy"
+ },
+ {
+ "title": "Rules",
+ "path": "getting-started/rules"
+ },
+ {
+ "title": "Logic",
+ "path": "getting-started/logic"
+ },
+ {
+ "title": "Imports",
+ "path": "getting-started/imports"
+ },
+ {
+ "title": "Testing",
+ "path": "getting-started/testing"
+ },
+ {
+ "title": "Next Steps",
+ "path": "getting-started/next-steps"
+ }
+ ]
+ }
+]
diff --git a/content/sentinel/v0.26.x/img/sentinel-import-topology.svg b/content/sentinel/v0.26.x/img/sentinel-import-topology.svg
new file mode 100644
index 0000000000..36f7c79bc9
--- /dev/null
+++ b/content/sentinel/v0.26.x/img/sentinel-import-topology.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/content/sentinel/v0.26.x/redirects.jsonc b/content/sentinel/v0.26.x/redirects.jsonc
new file mode 100644
index 0000000000..1c7c77f138
--- /dev/null
+++ b/content/sentinel/v0.26.x/redirects.jsonc
@@ -0,0 +1,7 @@
+[
+ {
+ "source": "/sentinel/commands/config",
+ "destination": "/sentinel/configuration",
+ "permanent": true,
+ },
+]
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/changelog.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/changelog.mdx
new file mode 100644
index 0000000000..6140b30480
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/changelog.mdx
@@ -0,0 +1,904 @@
+---
+page_title: Sentinel Runtime Release Notes
+sidebar_current: docs-changelog
+description: >-
+ These are the public release notes for the Sentinel runtime. See this document
+ for the latest updates.
+layout: docs
+---
+
+# Sentinel Runtime Release Notes
+
+These are the release notes for the Sentinel runtime.
+
+Each version of the runtime is released with a corresponding version of
+[Sentinel CLI](/sentinel/commands). To download the CLI, see the [install
+page](/sentinel/install).
+
+Sentinel integrations and embedded runtimes may not always have the latest
+version installed, depending on the product's individual release cycle. For more
+information, contact the support team for your specific integration.
+
+
+## 0.27.0 (August 7, 2024)
+
+ENHANCEMENTS:
+- `imports/collection/maps`: Added an `unset` helper to the `collection/maps`
+import, providing the ability to remove keys using a provided path.
+- `imports/collection/maps`: Added an `omit` helper to the `collection/maps`
+import, which returns a map with all provided paths removed from the source
+map.
+- `imports/strings`: Added the `replace`, `trim`, `trim_left`, `trim_right` and
+`trim_space` helpers to the `strings` import.
+
+## 0.26.3 (July 19, 2024)
+
+ENHANCEMENTS:
+
+- `imports/collection/maps`: Added a `set` helper to the `collection/maps`
+import, providing the ability to set values deeply within a map.
+- `imports/collection/maps`: Added a `pick` helper to the `collection/maps`
+import, providing the ability to select a series of values from a map.
+
+## 0.26.2 (June 19, 2024)
+
+ENHANCEMENTS:
+
+- `imports/collection`: Added a `filter` helper to the `collection` import,
+providing a functional approach to filtering collections.
+
+## 0.26.1 (May 22, 2024)
+
+NOTES:
+
+- This release changes lower-level components and there are no user-facing changes.
+
+## 0.26.0 (May 15, 2024)
+
+FEATURES:
+
+- `imports/collection`, `imports/collection/lists` and `import/collection/maps`:
+Adding new helper imports for dealing with collections (lists, maps), helping
+avoid complexities within policy code.
+- `sentinel/eval`: Fixed an issue with per-policy parameters where parameters
+may leak from one policy into the following policy.
+
+## 0.25.1 (Apr 18, 2024)
+
+BREAKING CHANGES:
+
+- `imports/http`: The default `Accept` header has been changed to `*/*`, removing a redundant
+extension that some servers may not accept by default.
+
+## 0.25.0 (Apr 8, 2024)
+
+ENHANCEMENTS:
+
+- `cmd/apply`: JSON results will return an additional `duration` field for individual policies as well as the evaluation as a whole.
+- `cmd/test`: `sentinel test` will return an additional `duration` field in the JSON output.
+
+BUG FIXES:
+
+- `cmd`: Removed redundant debug logging for custom plugins.
+
+## 0.24.4 (Mar 21, 2024)
+
+ENHANCEMENTS:
+
+- `runtime/format`: Null values will now print correctly for rule value outputs.
+- `config`: Some internal changes to configuration parsing workflow.
+
+## 0.24.3 (Feb 9, 2024)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.24.2 (Jan 31, 2024)
+
+BUG FIXES:
+
+- `imports/static`: Fixed an issue where `nil` values provided to the built-in
+static import were being treated as `undefined` in policy.
+
+## 0.24.1 (Jan 19, 2024)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where the Sentinel cache was unstable due to concurrent
+tests.
+
+## 0.24.0 (Dec 7, 2023)
+
+ENHANCEMENTS:
+
+- `cmd/apply`, `cmd/test`: Custom import plugins can now be fetched from remote
+sources.
+- `cmd/apply`, `cmd/test`: Static imports can now be fetched from remote
+sources.
+- `features/terraform`: Added support for `resource_drift` in the `tfplan/v2` import.
+
+## 0.23.1 (Oct 19, 2023)
+
+BUG FIXES:
+
+- `cmd/apply`: Warnings and errors are included in the JSON output if the json flag is enabled.
+
+## 0.23.0 (Sept 5, 2023)
+
+FEATURES:
+
+- `features/apply_all`: Adds the `apply-all` feature, allowing for all policies to evaluate regardless of result, instead of exiting on first failure.
+
+BUG FIXES:
+
+- `features/terraform`: The `tfplan/v1` import correctly handles nested attribute schemas.
+
+## 0.22.1 (June 22, 2023)
+
+BUG FIXES:
+
+- `sentinel/eval`: Under certain conditions, per-policy parameters would cause
+ Sentinel to panic. This has been resolved.
+
+## 0.22.0 (May 31, 2023)
+
+- `config`: Configuration now supports a `sentinel` block to manage the
+ Sentinel runtime.
+
+## 0.21.1 (May 8, 2023)
+
+BUG FIXES:
+
+- `config`: An issue with certain identifiers being treated as incorrectly invalid
+ has been resolved.
+
+## 0.21.0 (March 8, 2023)
+
+BREAKING CHANGES:
+
+- `lang/ast`: `is empty` and `is not empty` are now treated as `ast.UnaryExpr`
+ expressions, with `ast.IsEmptyExpr` being removed.
+- `lang/ast`: You can now assert if a value is defined or not using the `is defined`
+ and `is not defined` syntax.
+
+FEATURES:
+
+- `config`: Parameter values can now be provided for individual policies within
+ a policy block.
+
+## 0.20.0 (February 16, 2023)
+
+ENHANCEMENTS:
+
+- `cmd/testcmd`: Allows sentinel tests to run concurrently by default. Sequential style testing can
+ be enabled by running with the `-maxConcurrency=1` option.
+- `cmd/testcmd`: Allows sentinel test command to timeout after a certain duration. This can be provided
+ by the user or will default to 5 minutes.
+- `cmd/apply`: The policy enforcement level is now included in the JSON output.
+- `lang/ast`: Functions can now be declared as named statements, providing
+ a safer function declaration.
+
+BREAKING CHANGES:
+
+- `cmd/apply`: Policies provided directly to the apply command will now default their enforcement
+ level to `advisory`, aligning with the `policy` configuration block.
+- `sentinel`: JSON results will no longer return `allowed_failure` or `can_override` fields.
+- `sentinel/result`: A new package has been added which provides additional methods to return
+ supplemental data about the evaluation result.
+
+## 0.19.5 (February 9, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `SelectorExpr` will correctly rewrite `X`.
+
+## 0.19.4 (February 8, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `Compile` function will correctly set the `Original`
+ field when rewriting a `CallExpr` as an `ImportExpr`.
+
+## 0.19.3 (February 3, 2023)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.19.2 (January 17, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `Compile` function will no longer modify `ast.CallExpr`
+ arguments in place.
+
+## 0.19.1 (December 14, 2022)
+
+BUG FIXES:
+
+- `lang/ast`: The `Rewrite` function will no longer modify the provided Node
+ in place.
+
+ENHANCEMENTS:
+
+- `lang/semantic`: The Break semantic check uses the AST walker.
+
+## 0.19.0 (December 6, 2022)
+
+BREAKING CHANGES:
+
+- `config`: Attempts to override a standard import with a custom import will
+ now be met with an error.
+- `imports`: All plugin imports now return `sdk.Plugin` instead of `sdk.Import`,
+ as per the SDK update to v0.4.0.
+
+ENHANCEMENTS:
+
+- `runtime/importer`: An `Import` and `ImportInstance` interface have been
+ added to improve the import capabilities.
+- `runtime/importer`: The `Importer` interface now returns a `Import` interface
+ instead of a `sdk.Import`.
+- `lang/semantic`: The Import Assignment semantic check uses the AST walker.
+- `lang/ast`: Added an AST Walker so the AST rewriter does not need to be used for basic AST tasks.
+
+FEATURES:
+
+- `config`: Configuration syntax now allows for configuration of imports within
+ the standard library.
+- `config`: A new configuration syntax for defining modules and plugins is now
+ available.
+- Static data can now be added via `import` configuration.
+- `cmd/apply`: The `apply` command now supports multiple primary configuration
+ files by default, loading all configuration files within the working
+ directory.
+- `cmd/apply`: The `apply` command now accepts applying configuration overrides
+ to primary configuration.
+
+## 0.18.13 (October 31, 2022)
+
+BUG FIXES:
+
+- `cmd/apply`: `sentinel apply` no longer aborts on advisory policy failure.
+
+## 0.18.12 (September 15, 2022)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where nested `CallExpr` were not rewritten.
+
+## 0.18.11 (June 8, 2022)
+
+NOTES:
+
+- `config`: The `enforcement_level` key for policies is now optional, defaulting to `"advisory"` when not supplied.
+
+## 0.18.10 (May 20, 2022)
+
+NOTES:
+
+- `runtime/initwd`: Improvements to the installer, including timeouts and filesize limits, as well as disabling support
+ for symlinks within `git` or `hg` repositories.
+
+## 0.18.9 (March 23, 2022)
+
+NOTES:
+
+- This release fixes an issue with the Docker container build pipeline. There are no changes to the
+Sentinel runtime or CLI.
+
+## 0.18.8 (March 22, 2022)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue in the installer for Windows paths.
+
+## 0.18.7 (February 24, 2022)
+
+NOTES:
+
+- **Darwin arm64**. This release will be our first to include Darwin arm64 architecture.
+
+BUG FIXES:
+
+- `lang/ast`: Fixed issues where `IndexSpec` and `RuleLit` nodes where returning incorrect end positions.
+- `lang/ast`: Fixed an issue where `ParamSpec` nodes where returning incorrect end positions.
+
+## 0.18.6 (February 2, 2022)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.18.5 (January 14, 2022)
+
+IMPROVEMENTS:
+
+- `runtime/initwd`: Changed the sum algorithm used during remote installation from `md5` to `sha256` to reduce chance of collision.
+
+## 0.18.4 (July 20, 2021)
+
+FEATURES:
+
+- `imports/static`: Improved handling of struct tags when performing encoding, allowing `-` to mark as ignored.
+
+## 0.18.3 (June 1, 2021)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue for remote source installation where duplicate sub-directories resulted in incorrect installation.
+- `imports/static`: Fixed an issue where global configuration that contained an empty map could not be accessed without raising selector issues.
+
+## 0.18.2 (May 18, 2021)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where import expressions inside lists and maps were not being evaluated correctly.
+
+## 0.18.1 (May 11, 2021)
+
+BUG FIXES:
+
+- `imports/json`: Fixed an issue where empty maps where failing when passed at any level to `marshal`.
+
+## 0.18.0 (March 25, 2021)
+
+FEATURES:
+
+- `imports/http`: Added support for POST actions within the `http` import
+ through addition of the `post` function. Additionally, support for adding
+ a body to the `request` object has been added via `with_body`.
+
+## 0.17.4 (February 2, 2021)
+
+BUG FIXES:
+
+- `runtime/format`: Fixed a nesting issue with the rule printer where nesting
+ state leaks were leading to all collection values eventually being truncated,
+ regardless of their nesting level.
+
+## 0.17.3 (January 15, 2021)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue where local file installation failed on Windows
+ when using `sentinel test`.
+
+## 0.17.2 (January 13, 2021)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where duplicate log messages were being printed with
+ `sentinel test`.
+
+## 0.17.1 (January 7, 2021)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where hard-coded path separator caused `sentinel
+ test` issues in Windows.
+
+## 0.17.0 (January 5, 2021)
+
+LANGUAGE CHANGES:
+
+- **Map Expression** `map`. A new quantifier expression, `map` has been added.
+ `map` takes a collection and applies an operation to each element in the
+ collection, returning a list of results with the resulting values.
+- **Emptiness checks** `is empty` and `is not empty`. Two new expressions,
+ `is empty` and `is not empty` have been added. As they are aliases to the
+ `length` built-in, only `string`, `map`, `list` or `undefined` is
+ supported.
+- **Comparable maps** Maps (the data type) are now comparable. Maps are equal if
+ they are of equal length and both their corresponding keys and values are
+ comparable and equal.
+- **Rich Return Types** Rules can now process and return non-boolean data.
+ Scalar, list, and map types are supported. When non-boolean values are used,
+ the presence of a non-zero or non-zero-length `main` rule determines policy
+ failure. More details can be found on
+ https://docs.hashicorp.com/sentinel/language#main.
+
+FEATURES:
+
+- **CLI**: `sentinel apply` and `sentinel test` now have options for JSON
+ output. For more details, see the CLI documentation.
+- `imports/base64`: Added a new import, `base64`, to assist with encoding and
+ decoding base64 strings.
+
+## 0.16.1 (October 21, 2020)
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where `contains` to ensure any instance of `undefined` would correctly result in `undefined`.
+- `runtime/eval`: Fixed an issue in `any` and `all` expressions to ensure correct boolean logic when dealing with a collection containing `undefined`.
+- `imports`: Fixed an issue where import processes were not killed, which caused non-graceful closures for the clients.
+- `lang/scanner`: Fixed an issue where inline `#` style comments were not being consumed.
+
+## 0.16.0 (October 14, 2020)
+
+BREAKING CHANGES:
+
+- `cmd/doc`: Removal of the deprecated `sentinel doc` command.
+- `sentinel`: Replace `whitelist` and `blacklist` with `allowlist` and `denylist`, as per inclusive language changes.
+
+FEATURES:
+
+- `imports/version`: Added a new import, `version`, which supplies helpers for dealing with semantic versioning.
+- `cmd/apply`: Added an `HCL` based configuration file, which will eventually deprecate the legacy `JSON` configuration.
+- `cmd/apply`: Added support for download remote policies and modules.
+- `cmd/test`: Added support for downloading remote test modules.
+- `cmd/apply`: Added support for evaluating a policy based on it's key within the configuration.
+- `cmd/apply`: Added support for running all policies in a configuration by default.
+
+## 0.15.6 (July 7, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.15.5 (May 20, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel integrations. There are no user-facing changes.
+
+## 0.15.4 (May 13, 2020)
+
+BUG FIXES:
+
+- `imports`: Fixed an issue with the standard imports that was affecting the handling of null data within lists.
+
+## 0.15.3 (April 16, 2020)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where `IndexExpr` were not rewritten, as well as ensuring `SelectorExpr` uses any nested rewrites.
+- `lang/printer`: Fixed issue printing deeply nested structures and/or loops.
+
+IMPROVEMENTS:
+
+- `imports/decimal`: Added alias methods to improve consistency of method names within the `decimal` import. The alias methods are `is_nan` for `isnan`, as well as both `is_inf` and `is_infinite` for `isinfinite`.
+- `command/apply`: `sentinel apply` will now output the policy description when a trace is forced via `-trace`.
+- `sentinel`: Improved `String` output formatting of Policy docstring for `EvalPolicyResult`.
+
+## 0.15.2 (April 2, 2020)
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue with the `printer` that was causing a panic
+ when a `#` line comment was used with no text following it.
+- `imports/types`: Fixed an issue with the `types` import where calls to
+ `type_of` for undefined types were incorrectly returning as map types. These
+ will now be correctly be identified as undefined as expected.
+
+## 0.15.1 (March 6, 2020)
+
+BUG FIXES:
+
+- `sentinel`: Fixed how modules are managed internally in the runtime to correct
+ race conditions in concurrent scenarios and to allow for per-evaluation module
+ restrictions. This functionality is only of interest to embedded applications
+ with long-lived runtimes and is currently not implemented in any HashiCorp
+ integration.
+
+## 0.15.0 (March 5, 2020)
+
+NOTES:
+
+- **Windows Digital Signature**. Our windows releases for this version and up will be signed
+ and verified according to Microsoft's requirements.
+
+FEATURES:
+
+- `cmd/config`: Modules can now be loaded off of local disk using the Sentinel
+ CLI. For information on how to do so, see the Sentinel documentation.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: Changed modules so that they now have singleton scope when
+ loaded. Importing a module under two different aliases, or within a nested
+ module, will now share scopes - modification of state in one will alter the
+ other.
+- `lang/object`: Introduced an interface to allow the duplication of various
+ types, like collections.
+- `runtime/eval`: Changed how collection data is returned from modules. This
+ data is now duplicated to prevent accidental or deliberate circumvention of
+ the prohibition on assignment to import data, and brings behavior to parity
+ with binary imports.
+
+## 0.14.4 (February 6, 2020)
+
+IMPROVEMENTS:
+
+- `imports/time`: Changed the `add` timespace function in the `time` import to
+ allow it to take float types as durations. These values will be truncated to
+ the appropriate integer-value duration.
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where out-of-place right-hand braces were being
+ parsed as empty statements, instead of raising syntax errors.
+
+## 0.14.3 (January 22, 2020)
+
+IMPROVEMENTS:
+
+- `cmd/test`: Fail when an assertion is not found in a trace.
+- `cmd/test`: Added a message to notify when no rules were evaluated in a test.
+
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue where import aliases (the `as baz` in `import
+ "foo/bar" as baz`) were being removed when formatting with `sentinel fmt`.
+
+- `imports/http`: Fixed an issue with the http import where the client library's
+ debug logs were being printed to standard error.
+
+## 0.14.2 (January 15, 2020)
+
+NOTES:
+
+With this release, we are discontinuing support for MacOS 32-bit. 64-bit builds
+are now the only builds available for MacOS.
+
+IMPROVEMENTS:
+
+- `lang/printer`: A newline is now added to files formatted via `sentinel fmt`.
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue in printing where multi-line expressions would
+ indent infinitely. They will now only indent once at the most. This is mostly
+ seen when using `sentinel fmt`.
+- `runtime/encoding`: Fixed an issue where if a plugin returned a deeply
+ embedded undefined value, the value was instead decoded into a map.
+
+## 0.14.1 (January 6, 2020)
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where reserved words could not be used as
+ selectors when the selector expression was the last lexeme on a line.
+
+## 0.14.0 (December 18, 2019)
+
+LANGUAGE CHANGES:
+
+- **Filter Expression** `filter`. A new quantifier expression, `filter` has been added.
+ `filter` returns a subset of the provided collection based on a boolean condition that
+ is asserted for each element.
+
+NOTES:
+
+- **Apple Notarization**. Our darwin releases for this version and up will be signed
+ and notarized according to Apple's requirements.
+
+## 0.13.1 (November 25, 2019)
+
+BUG FIXES:
+
+- `runtime/eval`: Parameters are no longer allowed in mock files. Adding one to
+ a mock will result in a runtime error.
+- `runtime/eval`: Fixed an issue where import calls from within mocks were
+ failing under certain circumstances.
+- `imports/decimal`: `int` should now correctly provide the truncated integer
+ representation of a decimal number, not the rounded one.
+
+## 0.13.0 (November 15, 2019)
+
+LANGUAGE CHANGES:
+
+- **Policy Parameters**. The new [policy
+ parameters](https://docs.hashicorp.com/sentinel/language/parameters) feature
+ allows authors to use a `param` declaration at the top of a policy to supply
+ values that are expected to come from outside of a policy. See the linked
+ documentation for more details.
+
+FEATURES:
+
+- `imports/http`: The [`http`
+ import](https://docs.hashicorp.com/sentinel/imports/http) is a new addition
+ to the standard library enabling the use of HTTP-accessible data from outside
+ the runtime in policy rules.
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where loading other imports before a mocked
+ import would cause those imports to no longer be visible from within the
+ policy.
+
+## 0.12.0 (October 7, 2019)
+
+LANGUAGE CHANGES:
+
+- **New `case` statement**. This statement is a selection control mechanism to
+ conditionally execute a branch based on expression equality, allowing
+ simplification of complex conditional chains that may otherwise need to be
+ written with `else if`. See the [Case
+ Statements](https://docs.hashicorp.com/sentinel/language/spec#case-statements)
+ section of the Sentinel language specification for more details.
+
+IMPROVEMENTS:
+
+- `lang/semantic`: Added a semantic check to ensure usage of append is not using
+ a return value.
+
+BUG FIXES:
+
+- `runtime/eval`: Corrected an issue where calling a method on an import object
+ value that was the result of a method call on another import object value
+ would have erroneously tried to call an import of the name of the "parent"
+ import object value. Example: in `a = subject.new(); b = a.call(); c =
+ b.call()`, `b.call()` would attempt to call a method named `call` on the root
+ namespace for an import named `a`. This has now been corrected so that
+ `b.call()` will now correctly call the `call` method for the respective object
+ namespace residing in the import named `subject`.
+- `imports`: Some standard imports may have been returning null for some unknown
+ keys in objects when they should have been returning undefined. This was due
+ to an SDK issue which was corrected in SDK version 0.3.2, which has now been
+ corrected.
+- `cmd/test`: `sentinel test` will now correctly fail a policy if it encounters
+ an error.
+- `cmd/test`: `sentinel test` will now correctly display errors and other output
+ that were missing due to a formatting issue.
+- `runtime/eval`: The `append` builtin now correctly returns undefined for all
+ calls, as called for by the Specification. Note that in most cases, the
+ semantic check outlined above will trigger an error if the return value is
+ used.
+
+## 0.11.0 (September 5, 2019)
+
+LANGUAGE CHANGES:
+
+- **New builtin function:** `range()`. This function existed in the spec in
+ earlier versions but was removed as it lacked an implementation. This has now
+ been implemented and re-added to the spec. See the
+ [Range](https://docs.hashicorp.com/sentinel/language/spec#range) section of
+ the Sentinel Specification for more details.
+- Lists are now comparable. Lists are equal if their corresponding elements are
+ comparable and equal.
+- Method calls on values returned by imports are now supported. See the
+ [Imports](https://docs.hashicorp.com/sentinel/language/spec#imports) section
+ of the Sentinel Specification for more details.
+
+FEATURES:
+
+- `imports/decimal`: This is a new import designed to do exact precision
+ mathematical calculations.
+- `runtime/eval`: Compound call expressions that refer to imports (example:
+ foo.bar().baz() when `foo` is a loaded import) will now function as expected.
+ Previously, this was only supported up to the first call (example:
+ `foo.bar()`).
+- `imports/time`: Timespaces returned by calls such as `time.now` are now
+ callable. Example: `t = time.now; t.after(some_previous_time)` will now
+ function.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: The implementation of comparison of non-comparable types has
+ now changed. Rather than triggering a runtime error, non-comparable types will
+ now return false when attempting to compare.
+
+## 0.10.4 (August 15, 2019)
+
+BUG FIXES:
+
+- `runtime/encoding`: Fixed an issue with conversion of null values that could
+ lead to crashes in imports.
+
+## 0.10.3 (July 15, 2019)
+
+BUG FIXES:
+
+- `command/config`: Parsing a configuration file where malformed data is
+ encountered after apparently properly-formed data will now report an error. An
+ example would be a situation where misplaced braces would cause a JSON object
+ to only parse part of the configuration file.
+
+## 0.10.2 (June 25, 2019)
+
+BUG FIXES:
+
+- `lang/parser`: Corrected an issue where parsing certain compound binary
+ expressions where the negation predicate (`not`) was in use would cause the
+ negation to have no effect. Example: `foo else "bar" not in "baz"` would have
+ been parsed and evaluated as `foo else "bar" in "baz"`, effectively producing
+ the opposite result.
+
+## 0.10.1 (May 9, 2019)
+
+BUG FIXES:
+
+- `command/test`: `sentinel test` now displays policies without
+tests as an unknown result with [no test files], instead of the somewhat
+erroneous behavior of displaying it as a PASS. A full test run with a mixture
+of passing tests and no tests still results in an overall successful result.
+
+## 0.10.0 (April 18, 2019)
+
+LANGUAGE CHANGES:
+
+- Mixed-number arithmetic operations are now allowed. Addition (`+`),
+ subtraction (`-`), multiplication (`*`), division (`/`), and remainder
+ operations (`%`) are no longer restricted to number values of the same
+ type, and can mix integer and floating point. The result of these operations
+ is always a floating-point number.
+- Remainder (modulo, `%`) operations are now allowed on floating-point numbers.
+
+
+BUG FIXES:
+
+- `lang/parser`: Fixed a bug that affected the use of `not` with `contains`,
+ `matches`, or `in` in a compound expression.
+- `runtime/eval`: Fixed error messages on evaluation errors with `contains` and
+ `in` to indicate that strings are an allowed type along with lists and maps.
+
+
+## 0.9.2 (March 15, 2019)
+
+This is a dependency update related to the changes mentioned in 0.9.1. No other
+changes have been made.
+
+## 0.9.1 (March 14, 2019)
+
+This is a patch release that is required to integrate with the latest versions
+of the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). No other
+changes have been made.
+
+## 0.9.0 (January 28, 2019)
+
+FEATURES:
+
+- `imports/strings`: Added a `join` function to the `strings` import. This can
+ be used to join a list into a string with a specific separator.
+ Multi-dimensional lists and all Sentinel primitives are supported.
+
+## 0.8.1 (January 17, 2019)
+
+BUG FIXES:
+
+- `lang/eval`: Fixed a bug that prevents the effective use of `return`
+ statements in `for` loops.
+
+## 0.8.0 (January 14, 2019)
+
+FEATURES:
+
+- Mocks can now be represented by Sentinel code. This allows for the mocking of
+ functions and other complex data structures that cannot be represented in
+ JSON. For more information on using this feature via mocks, click
+ [here](https://docs.hashicorp.com/sentinel/commands/config#mocking-with-sentinel-code).
+
+
+IMPROVEMENTS:
+
+- Import validation has now been moved to the semantic checking phase. This
+ should result in better reporting of validation errors. In addition, import
+ validation will now enforce the use of an `as` identifier when an import path
+ is not a valid identifier on its own (example: `import "foo/bar"`).
+
+## 0.7.0 (December 12, 2018)
+
+BUG FIXES:
+
+- There have been changes to the runtime in how scope is handled over multiple
+ policy executions. Scope is now correctly unique per single policy execution,
+ and values set or builtins that are overridden in one policy will no longer
+ affect those values within another.
+
+## 0.6.0 (November 30, 2018)
+
+FEATURES:
+
+- `imports/runtime`: This new import allows for one to check various aspects of
+ the Sentinel runtime as it may be embedded in the simulator or a specific
+ implementation. For now, it allows the version to be checked.
+
+## 0.5.1 (November 28, 2018)
+
+IMPROVEMENTS:
+
+- `imports/time`: Added the `zone` and `zone_string` attributes to assist with
+ validation of a timespace's zone.
+- `command/fmt`: Added a new -check flag. This option does not commit changes,
+ but instead checks to see what files need formatting and outputs them on
+ stdout.
+
+BUG FIXES:
+
+- `command/test`: Ensure that passing test results are correctly output one per
+ line. Tests are also now run in a deterministic fashion based on
+ lexicographical (alphabetical) order.
+- `imports/time`: `month_name` and `weekday_name` will now show up correctly in
+ a returned timespace result.
+
+
+## 0.5.0 (November 5, 2018)
+
+IMPROVEMENTS:
+
+- `spec`: Selectors can now contain any reserved word (example: `rule`) or
+ keyword operator (example: `any`, `all`, `is`, `not`). This only works for the
+ _selector_ part of the expression (after the first period) - the first primary
+ expression (before the first period) still needs to be an identifier that does
+ not conflict with reserved words.
+
+BUG FIXES:
+
+- The simulator should now display import function call names correctly in
+ import errors.
+
+## 0.4.0 (October 1, 2018)
+
+FEATURES:
+
+- `builtin`: Added the `bool` built-in type conversion function. Booleans will
+ also now accepted as conversion into other values as well, with the full list
+ of behaviors available in the spec.
+
+## 0.3.2 (September 27, 2018)
+
+FEATURES:
+
+- `command/apply`: `sentinel apply` now prints out messages output by the
+ `print()` function when a trace is output on policy failure, or when a trace
+ is forced with `-trace`.
+- `imports/time`: Added the `month_name` and `weekday_name` keys to the
+ timespace, which return full-English names for the month and day of the week.
+
+
+BUG FIXES:
+
+- `command/fmt`: `sentinel fmt -` Will no longer print out the filter status
+ message on the output stream when `-write=false` Is not explicitly stated.
+ This brings the behavior of the command in line with the help text.
+- `runtime`: Index operations on the right-hand-side that have negative indexes
+ that go out of range (example: `length(list) * -1 - 1`) now correctly return
+ `undefined`. left-hand-side index assignments with a out-of-range negative
+ index still return runtime errors.
+
+## 0.3.1 (August 3, 2018)
+
+BUG FIXES:
+
+- `runtime`: Basic index assignment has been implemented as per the spec.
+- `runtime`: Index expressions for lists with negative indexes will no longer panic if the
+ list index is less than `length(list) * -1`.
+
+## 0.3.0 (July 20, 2018)
+
+FEATURES:
+
+- **New standard import: `types`.** This can be used to dynamicaly detect the
+ type of some value.
+
+## 0.2.0 (April 11, 2018)
+
+FEATURES:
+
+- **New standard import: `json`.** Marshal and unmarshal JSON documents and
+ access their contents as native Sentinel values.
+- **`break` and `continue`.** These are now both specified and implemented.
+ `break` allows loop exiting and `continue` allows immediate execution of the
+ next iteration.
+
+IMPROVEMENTS:
+
+- runtime: `print()` map values are now ordered alphabetically by keys.
+
+BUG FIXES:
+
+- command/test: If no `test` block exists, test behaves like it is asserting
+ `main: true`.
+- runtime: default maximum stack depth to 500
+- runtime: `print()` map values now appear like more typical maps.
+- runtime: division by zero is an error, not a crash
+- runtime: plugins that send map values with `null` values now decode properly
+ into native Sentinel values.
+
+## 0.1.0 (September 19, 2017)
+
+Initial release.
+
+
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/commands/apply.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/commands/apply.mdx
new file mode 100644
index 0000000000..c5e81d6d65
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/commands/apply.mdx
@@ -0,0 +1,63 @@
+---
+page_title: 'Command: apply'
+sidebar_current: docs-commands-apply
+description: >-
+ The `sentinel apply` command is used to execute a policy locally for
+ development purposes.
+layout: docs
+---
+
+# Command: `apply`
+
+The `sentinel apply` command is used to execute a policy locally for development
+purposes.
+
+## Usage
+
+Usage: `sentinel apply [options] POLICY`
+
+This command executes the policy file at the path specified by POLICY. In
+addition to a path to a policy, POLICY can reference a label of a
+[policy](/sentinel/configuration#policies) entry in the configuration.
+
+Use the exit code of this command to determine the exact status of the policy
+evaluation. `0` is pass, `1` is fail, `2` is undefined (fail, but because the
+result was undefined), and `3` is a runtime error. Errors unrelated to the
+policy status itself are returned with an exit status of `9`.
+
+To control the behavior of the `apply` command, create a [configuration
+file](/sentinel/configuration). With this, you can define available
+[import plugins](/sentinel/concepts/imports), mock data, and global values.
+This can help you simulate a policy embedded within an application.
+
+As of the 0.16 release, POLICY is optional. When it is not supplied,
+`sentinel apply` will run **all** policies in the supplied configuration.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-config=path` - Path to a configuration file specifying available imports,
+ mock data, globals, etc. The default is `sentinel.[hcl|json]`.
+
+- `-json` Enable JSON output. Using this mode, all other output will be
+ suppressed and the full detail of the apply will be output in a
+ machine-readable format. Enabling this implies `-trace`. See the section on
+ [tracing JSON output](/sentinel/writing/tracing#json-output) for more details.
+
+- `-json-rule=RULE` Enable JSON output, outputting only the value of the
+ specified rule. When running against a policy set, the policy must be supplied
+ to enable per-rule filtering. Implies `-json`.
+
+- `-global key=value` - Set global values. This is the same as setting `global`
+ in the configuration file, and will override any of these respective values
+ set in the configuration. The value is either a string, or a JSON number,
+ array, or object. To force strings, use quotes.
+
+- `-param key=value` - Set parameters, the same as setting `param` in the
+ configuration file. Values are handled in the same way they are with the
+ `-global` flag.
+
+- `-trace` - Always show the execution trace. This shows intermediate
+ boolean expression values. This always shows for failed policies.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/commands/fmt.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/commands/fmt.mdx
new file mode 100644
index 0000000000..8297316d8b
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/commands/fmt.mdx
@@ -0,0 +1,33 @@
+---
+page_title: 'Command: fmt'
+sidebar_current: docs-commands-fmt
+description: The `sentinel fmt` command formats a policy source to a canonical format.
+layout: docs
+---
+
+# Command: `fmt`
+
+The `sentinel fmt` command formats a policy source to a canonical format.
+
+## Usage
+
+Usage: `sentinel fmt [options] FILE ...`
+
+This command formats all the specified policy files to a canonical format.
+
+By default, policy files are overwritten in place. This behavior can be
+changed with the `-write` flag. If a specified FILE is `-` then stdin is
+read and the output is always written to stdout.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-write=true` - Write formatted policy to the named source file. If false,
+ output will go to stdout. If multiple files are specified, the output will
+ be concatenated directly.
+
+- `-check=false` - Don't format, only check if formatting is necessary. Files
+ that require formatting are printed, and a non-zero exit code is returned if
+ changes are required.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/commands/index.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/commands/index.mdx
new file mode 100644
index 0000000000..7177093cec
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/commands/index.mdx
@@ -0,0 +1,23 @@
+---
+page_title: Commands (CLI)
+sidebar_current: docs-commands
+description: >-
+ Sentinel provides a `sentinel` command-line interface (CLI) for developing and
+ testing policies.
+layout: docs
+---
+
+# Sentinel CLI Commands
+
+The Sentinel command-line interface (CLI) allows for the developing and testing
+of policies outside of a particular Sentinel implementation. Having a standard
+workflow to develop policies is critical for our mission of [policy as
+code](/sentinel/concepts/policy-as-code).
+
+The CLI takes a subcommand to execute. The complete list of subcommands is in
+the navigation to the left.
+
+The Sentinel CLI is a well-behaved command line application. In erroneous cases,
+a non-zero exit status will be returned. It also responds to -h and --help as
+you'd expect. To view a list of the available commands at any time, just run
+`sentinel` with no arguments.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/commands/test.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/commands/test.mdx
new file mode 100644
index 0000000000..ed50aacc22
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/commands/test.mdx
@@ -0,0 +1,53 @@
+---
+page_title: 'Command: test'
+sidebar_current: docs-commands-test
+description: The `sentinel test` command runs policies against the Sentinel test framework.
+layout: docs
+---
+
+# Command: `test`
+
+The `sentinel test` command runs policies against the Sentinel test framework.
+
+To learn more about testing, see the [testing
+policies](/sentinel/writing/testing) page.
+
+## Usage
+
+Usage: `sentinel test [options] [PATH] ...`
+
+This command runs the defined test cases against a set of policies.
+
+The test cases are expected to live at the path `test//*.[hcl|json]` relative
+to the policy being tested. `` should be the name of the policy
+excluding the file extension. The files should be in the [configuration
+file structure](/sentinel/configuration). The `test` key is used for
+assertions. Test cases ignore the root level configuration file and must have
+all required configuration provided in each test case.
+
+The PATH arguments specified may be a list of zero or more files or directories.
+When no arguments are given, it defaults to the current directory. When a
+directory is given, all policies ending with the extension `.sentinel` are
+tested.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-json` - Suppress normal output and output test results as a JSON document.
+ See the [testing JSON output](/sentinel/writing/testing#test-json-output)
+ section for more details.
+
+- `-run=regexp` - Run only tests matching the regular expression pattern.
+ A `/` splits policy name and test case name.
+
+- `-verbose` - Show tracing and log output for tests that succeed in addition
+ to tests that fail. By default, traces and logs are only shown for tests that
+ fail.
+
+- `-maxConcurrency` - Allows users to specify the number of tests to be run concurrently.
+ Defaults to the number of logical CPUs if not provided. To run tests sequentially, use `-maxConcurrency=1`
+
+- `-timeout` - Allows users to specify a timeout after which the test command will stop running.
+Defaults to 5 minutes if not provided. The timeout needs to be specified as a Duration type, eg `100ms, 5s etc`
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/concepts/enforcement-levels.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/concepts/enforcement-levels.mdx
new file mode 100644
index 0000000000..fc4b8062d0
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/concepts/enforcement-levels.mdx
@@ -0,0 +1,43 @@
+---
+page_title: Enforcement Levels
+sidebar_current: docs-concepts-levels
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Enforcement Levels
+
+Enforcement levels are a first class concept in Sentinel allowing pass/fail
+behavior to be associated separately from the policy logic. This enables
+any policy to be a warning, allow overrides, or be absolutely mandatory.
+Because this level is not part of the policy body itself, different uses of
+the same policy can have different enforcement levels.
+
+Sentinel has three enforcement levels:
+
+- **Advisory:** The policy is allowed to fail. However, a warning should be
+ shown to the user or logged. Advisory is the default enforcement level.
+
+- **Soft Mandatory:** The policy must pass unless an override is specified.
+ The semantics of "override" are specific to each Sentinel-enabled application.
+ The purpose of this level is to provide a level of privilege separation
+ for a behavior. Additionally, the override provides non-repudiation since
+ at least the primary actor was explicitly overriding a failed policy.
+
+- **Hard Mandatory:** The policy must pass no matter what. The only way to
+ override a hard mandatory policy is to explicitly remove the policy.
+ It should be used in situations where an override is not possible.
+
+## Configuring Enforcement Levels
+
+Enforcement levels are configured when a policy is deployed to a
+Sentinel-enabled application. The exact mechanism that the level is specified
+is determined by each application. Please reference the documentation for
+your Sentinel-enabled application for more information.
+
+Enforcement levels are not configured and are not known by the policy body
+itself. All policies should be written to describe exactly the behavior
+they're attempting to control. For example, a policy that restricts deploys
+to business hours should be written exactly like so. When that policy is
+configured on an application, the operator may specify that it is advisory,
+soft, or hard mandatory.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/concepts/imports.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/concepts/imports.mdx
new file mode 100644
index 0000000000..1306e1512e
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/concepts/imports.mdx
@@ -0,0 +1,32 @@
+---
+page_title: Imports
+sidebar_current: docs-concepts-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom imports](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only
+local context for making policy decisions.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that the
+import calendar allows access to engineer calendars. This import only exists
+for the purpose of this example and at the time of writing is not a real
+available import.
+
+```json sentinel
+{
+ "policy": "import \"calendar\"\n// Get the calendar for Bob for today\nbob_calendar = calendar.of(\"bob\").today\n\n// Allow this policy to pass if Bob is not on vacation.\nmain = rule { not bob_calendar.has_event(\"vacation\") }",
+ "mocks": {
+ "calendar": "has_event = func(event) {\n\treturn true\n}\nof = func(name) {\n\treturn { \"today\": { \"has_event\": has_event } }\n}"
+ }
+}
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/concepts/index.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/concepts/index.mdx
new file mode 100644
index 0000000000..ce7ac51454
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/concepts/index.mdx
@@ -0,0 +1,15 @@
+---
+page_title: Basic Concepts
+sidebar_current: docs-concepts
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Basic Concepts
+
+This section covers some high level basic concepts that are important
+to understand for day to day Sentinel usage. Every page in
+this section is recommended reading for anyone consuming
+or extending Sentinel.
+
+Please use the navigation to the left to learn more about a topic.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/concepts/language.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/concepts/language.mdx
new file mode 100644
index 0000000000..ce91f3eb8f
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/concepts/language.mdx
@@ -0,0 +1,91 @@
+---
+page_title: Policy Language
+sidebar_current: docs-concepts-language
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Policy Language
+
+Sentinel defines and uses its own [policy language](/sentinel/language).
+
+The language was designed to be approachable by non-programmers, since
+there are many use cases where the individual defining policy may not
+be a developer. However, the language includes constructs that
+are familiar to developers to enable powerful policies.
+
+To learn more about the language, please see the
+[writing policy section](/sentinel/writing)
+or the [language reference](/sentinel/language).
+
+An example of the policy language is shown below:
+
+```sentinel playground
+import "time"
+
+# Validate time is between 8 AM and 4 PM
+valid_time = rule { time.now.hour >= 8 and time.now.hour < 16 }
+
+# Validate day is M - Th
+valid_day = rule {
+ time.now.weekday_name in ["Monday", "Tuesday", "Wednesday", "Thursday"]
+}
+
+main = rule { valid_time and valid_day }
+```
+
+## Why?
+
+To explain why Sentinel defines and uses its own language, the question
+can be more easily split into roughly two types of languages: _configuration_ languages
+and _programming_ languages.
+
+### Configuration Languages
+
+Configuration languages are formats such as JSON, YAML, XML, etc. Many applications
+use configuration languages as their ACL format. Configuration languages
+are good for static, declarative information. ACL systems are a good fit
+for this. ACL systems are focused, providing the limiting options necessary
+to restrict access to a system.
+
+Configuration languages are not good for dynamic or logical rules. They
+typically only contain limited conditions, loops, or functions. Further
+configuration is often declarative, which can introduce complexities to
+logical statements that depend on ordering.
+
+The use cases that Sentinel was built for require the ability to perform
+complex behavior that didn't model well into a configuration format or a
+declarative form.
+
+### Programming Languages
+
+The Sentinel language was designed with the following goals:
+
+- **Non-programmer friendly.** Sentinel was built to be used by non-programmers.
+ For the use cases we discovered, non-programmers needed the ability to
+ enforce certain rules within a system. For example, a person responsible for
+ compliance may need to insert rules into a system.
+
+- **Programmer friendly.** At the same time, the language needed to support
+ programmer-friendly constructs such as conditionals, loops, and functions
+ for complex policies that a programmer may be writing.
+
+- **Embeddable.** Sentinel is embedded in existing software. The language
+ itself needs to be easily embeddedable. Further, Sentinel was designed
+ to be embedded in [HashiCorp](https://www.hashicorp.com) software
+ written in Go, so it needed to be easily embeddable in Go.
+
+- **Safe.** The language is used in highly security-sensitive environments.
+ It must not be able to crash its host system or access system resources
+ without explicit approval.
+
+Prior to building our own language, Sentinel evaluated other languages.
+Some languages worked quite well. As we continued to develop Sentinel, we
+found that the flexibility and control over designing our own language
+outweighed the costs.
+
+Because the language itself doesn't need to be general purpose, building
+a language focused on policy proved to be the best route for Sentinel.
+It allows us to build powerful tracing capabilities for debugging, make
+interesting performance choices, and extend the language as needed in the
+future.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/concepts/policy-as-code.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/concepts/policy-as-code.mdx
new file mode 100644
index 0000000000..6dac593e18
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/concepts/policy-as-code.mdx
@@ -0,0 +1,72 @@
+---
+page_title: Policy as Code
+sidebar_current: docs-concepts-pac
+description: >-
+ Policy as code is the idea of writing code in a high-level language to manage
+ and automate policies. By representing policies as code in text files, proven
+ software development best practices can be adopted such as version control,
+ automated testing, and automated deployment.
+layout: docs
+---
+
+# Policy as Code
+
+Policy as code is the idea of writing code in a high-level language to
+manage and automate policies. By representing policies as code in text files,
+proven software development best practices can be adopted such as version
+control, automated testing, and automated deployment.
+
+Many existing policy or ACL systems do not practice policy as code. Many
+policies are set by clicking in a GUI, which isn't easily repeatable nor
+versionable. They usually don't provide any system for testing policies
+other than testing an action that would violate the policy. This makes it
+difficult for automated testing. And the policy language itself varies by
+product.
+
+Sentinel is built around the idea and provides all the benefits of policy as code.
+
+## Benefits
+
+Policy as code provides a number of benefits:
+
+- **Sandboxing.** Policies provide the guardrails for other automated systems.
+ As the number of automated systems grow, there is also a growing need to
+ protect those automated systems from performing dangerous actions. Manual
+ verification is too slow; policies need to be represented as code to
+ keep up with other automated systems.
+
+- **Codification.** By representing policy logic as code, the information
+ and logic about a policy is directly represented in code and can be augmented
+ with comments rather than relying on oral tradition to learn about the
+ reason for policies.
+
+- **Version Control.** Policies are encouraged to be stored as simple text
+ files managed by a version control system. This lets you gain all the
+ benefits of a modern VCS such as history, diffs, pull requests, and more.
+
+- **Testing.** Policies are just code. Their syntax and behavior can be
+ [easily validated with Sentinel](/sentinel/commands/test). This also encourages
+ automated testing such as through a CI. Paired with a VCS system, this
+ allows a pull request workflow to verify that a policy keeps the system
+ behavior as expected before merging.
+
+- **Automation.** With all policies as code in simple text files, various
+ automation tools can be used. For example, it is trivial to create tools
+ to automatically deploy the policies into a system.
+
+## Sentinel and Policy as Code
+
+Sentinel fully embraces policy as code in a number of ways:
+
+- **Language.** All Sentinel policies are written using the
+ [Sentinel language](/sentinel/concepts/language). This language is
+ made to be inputted directly to text files. As an additional benefit,
+ all Sentinel-enabled applications share the same policy language.
+
+- **Development.** Sentinel provides a [CLI](/sentinel/commands)
+ for development and testing. This local CLI can be used to verify policies
+ before deploying them to a system.
+
+- **Testing.** Sentinel provides a [test framework](/sentinel/commands/test)
+ designed specifically for automation. This allows developers and CI systems
+ to further verify policies.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/configuration/index.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/configuration/index.mdx
new file mode 100644
index 0000000000..c7d4a8e3bd
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/configuration/index.mdx
@@ -0,0 +1,422 @@
+---
+page_title: Sentinel CLI Configuration File Syntax
+sidebar_current: docs-configuration
+description: >-
+ The Sentinel CLI's configuration file can be used to control the
+ behavior of the simulator during apply and test operations.
+layout: docs
+---
+
+# Sentinel CLI Configuration File Syntax
+
+The Sentinel CLI's configuration file can be used to control the behavior
+of the simulator during [`apply`](/sentinel/commands/apply) and
+[`test`](/sentinel/commands/test) operations.
+
+## Usage
+
+The configuration file is used in different ways depending on what operation you
+are trying to execute.
+
+### Apply
+
+When using `sentinel apply`, configuration files that have the `.hcl`
+extension are loaded into a single configuration. This allows for configuration
+to be split across multiple files, which is useful for large policy sets. To
+supply a single configuration file, the `-config=FILE` flag can be used, where
+`FILE` is the path to the configuration file. This argument also allows for a
+`.json` configuration file to be supplied. The default is `sentinel.[hcl|json]`.
+
+See the [apply command](/sentinel/commands/apply) reference for more details.
+
+### Test
+
+When using `sentinel test`, each file matching `test//*.[hcl|json]` is a
+configuration file representing a single test case, where `` is the name
+of the policy being tested. Configure assertions for each [test
+case](#test-cases) using the test section.
+
+See the [test command](/sentinel/commands/test) reference for more details.
+
+#### Remote sources
+
+When declaring a `policy` or `module` block within the configuration, a
+`source` attribute must be supplied. This `source` should declare the location
+of the resource, which can be either a local file or a URL pointing to a remote
+file. Examples of local `source` attributes are detailed both in the
+[Policies](#policies) and [Modules](#modules) sections of this page. Below
+are a few examples of remote `source` attributes.
+
+Example:
+
+```hcl
+policy "foo" {
+ source = "git::https://github.com/hashicorp/sentinel-example.git//main.sentinel"
+ enforcement_level = "hard-mandatory"
+}
+```
+
+The above example will fetch the policy from the provided URL and place it into
+the cache ready for use. Be sure to read the
+[Remote Sources](/sentinel/configuration/remote-sources) page for an
+in-depth overview.
+
+## Configuration File Reference
+
+The format of the configuration file is either JSON or HCL. The available
+blocks are:
+
+- [`mock`](#mock-imports) - A mock import specification.
+- [`policy`](#policies) - Configuration for a [policy](/sentinel/writing).
+- [`import`](#imports) - Configuration for a [module](/sentinel/extending/modules), [standard import](/sentinel/imports), [plugin](/sentinel/extending/plugins) or
+ [static import](/sentinel/extending/static-imports).
+- [`global`](#globals) - Data that is inserted into the global scope.
+- [`param`](#parameters) - Values for [parameters](/sentinel/language/parameters) defined in the policy.
+- [`test`](#test-cases) - Test cases for the [test command](/sentinel/commands/test).
+- [`sentinel`](#sentinel) - Configuration to manage the Sentinel runtime
+
+### Mock Imports
+
+Mock imports allow running a policy with an
+[import](/sentinel/concepts/imports) that you may not have access to or is
+too difficult to run. For example, it can be easier to mock data for [Terraform
+Enterprise](https://www.terraform.io/docs/enterprise/sentinel/index.html)
+locally instead of having to run a workspace through a plan/policy check cycle.
+
+Mock imports are specified using the `mock` block, labelled by the import
+name that you want to mock. For example, if you wanted to mock the `time`
+import, you could create an entry with the `time` label pointing to the data
+you want to mock.
+
+The data can take one of two forms:
+
+#### Mocking static data
+
+Static data can be mocked directly via HCl using the `data` attribute on the
+`mock` block.
+
+Example:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ hour = 9
+ minute = 42
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"time\"\n\nmain = rule { time.now.hour is 9 }",
+ "mocks": {
+ "time": "now = { \"hour\": 9, \"minute\": 42 }"
+ }
+}
+```
+
+#### Mocking with Sentinel code
+
+There are some Sentinel types that raw data cannot mock, such as functions,
+and maps that don't have string keys. To mock this data, you can use
+a Sentinel file itself. In this case, you can set the `module` attribute to
+a file with the Sentinel code in it, relative to the current working directory in
+the event of `apply`, or relative to the configuration file location in the
+event of `test`.
+
+Note that `main` is not required in a mock data file.
+
+Example:
+
+```hcl
+mock "foo" {
+ module {
+ source = "mock-foo.sentinel"
+ }
+}
+```
+
+`mock-foo.sentinel` would contain:
+
+```sentinel
+bar = func() {
+ return "baz"
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"foo\"\n\nmain = rule { foo.bar() is \"baz\" }",
+ "mocks": {
+ "foo": "bar = func() { return \"baz\" }"
+ }
+}
+```
+
+### Policies
+
+The `policy` block allows for the the configuration of policies to assist
+with integrating into other commands.
+
+Each `policy` block has a label to identify the policy, with the block
+providing configuration of the policy. The available configuration options for
+policy are `source`, `enforcement_level` and an optional `params` attribute. The
+`source` key provides the location of the policy, while `enforcement_level` is
+currently used by integrations such as [HCP Terraform](/terraform/cloud-docs/policy-enforcement/manage-policy-sets#enforcement-levels).
+For more information on the `source` value, see [Policy and Module Sources](#policy-and-module-sources).
+
+The optional `params` attribute is used to provide values to [parameters](/sentinel/language/parameters)
+defined within the policy file.
+
+```hcl
+policy "foo" {
+ source = "foo.sentinel"
+ enforcement_level = "hard-mandatory"
+ params = {
+ "name" = "Sample"
+ }
+}
+```
+
+If `enforcement_level` is not supplied, it will fallback to the `advisory` level.
+
+### Imports
+
+Import configuration allows you to configure [modules](/sentinel/extending/modules),
+[standard imports](/sentinel/imports) and [import plugins](/sentinel/extending/plugins).
+
+The `import` is a multi-label block, with the first label determining the kind of import
+being configured. Currently the supported values for this label are:
+
+* [`"module"`](#import-modules) for configuring import modules
+* [`"plugin"`](#import-plugins) for configuring standard imports and import plugins
+* [`"static"`](#static-imports) for configuring static data files as imports
+
+#### Import Modules
+
+The `import "module"` block allows you to map a Sentinel import to a
+[module](/sentinel/extending/modules). The Sentinel CLI will parse the module
+source and make it available for evaluation with the policy.
+
+Each block has a label aligning to the name of the import, with
+the block set to the configuration object for the module. Currently, the only
+value within the configuration object is `source`, which points to the
+location of the module. For more information on the `source` value, see
+[Policy and Module Sources](#policy-and-module-sources).
+
+```hcl
+import "module" "foo" {
+ source = "modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "modules/bar.sentinel"
+}
+```
+
+Note when using [`sentinel test`](/sentinel/commands/test), module paths must be
+specified relative to the location of the configuration file.
+
+```hcl
+import "module" "foo" {
+ source = "../../modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "../../modules/bar/sentinel"
+}
+```
+
+See [Writing and Using a
+Module](/sentinel/extending/modules#writing-and-using-a-module) for more details
+on using a module with this configuration.
+
+### Import Plugins
+
+Import `"plugin"` configuration allows you to configure both [standard imports](/sentinel/imports)
+as well as [import plugins](/sentinel/extending/plugins) that can be used within
+a policy. If defining a custom import plugin, the Sentinel CLI will launch the
+plugin, connect to it, configure it, and execute it as needed by the policy.
+
+The available configuration attributes for an `import "plugin"` are:
+
+- `source` (string, required) - Path to the import plugin executable.
+- `args` (list of string, optional) - A list of arguments to pass to the
+ executable when starting it.
+- `env` (map of string to string, optional) - A set of environmental variables
+ to set when launching the plugin.
+- `config` (map, optional) - Configuration for the plugin itself. This is
+ specific to each individual plugin. Please reference the documentation for
+ the plugin for more information.
+
+Standard import example:
+
+```hcl
+import "plugin" "time" {
+ config = { "timezone": "Australia/Brisbane" }
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"time\"\n\nmain = time.now.zone_string is \"+10:00\"",
+ "mocks": {
+ "time": "now = { \"zone_string\": \"+10:00\" }"
+ }
+}
+```
+
+Custom plugin example:
+
+```hcl
+# flipper receives a boolean and returns the opposite
+import "plugin" "flipper" {
+ source = "/path/to/flipper"
+}
+```
+
+```json sentinel
+{
+ "policy": "import \"flipper\"\n\nmain = flipper.flip(false)",
+ "mocks": {
+ "flipper": "flip = func(input) { return not input }"
+ }
+}
+```
+
+#### Static Imports
+
+Static import configuration allows you to supply a data file and make
+it available to a policy via an import statement.
+
+The available configuration attributes for an `import "static"` are:
+
+- `source` (string, required) - Path to the static data file.
+- `format` (string, required) - The format of the static data. Currently, only
+ the "json" value is supported.
+
+Static import example:
+
+```hcl
+import "static" "people" {
+ source = "./data/people.json"
+ format = "json"
+}
+```
+
+```json sentinel
+{
+ "policy": "import \"people\"\n\nmain = length(people.names) > 0",
+ "mocks": {
+ "people": "names = [\"John Smith\", \"Jane Smith\"]"
+ }
+}
+```
+
+### Globals
+
+Global data is injected directly into the global scope of the policy.
+This can be used to simulate global data that may be injected by a
+Sentinel-enabled application.
+
+Global data is specified by the `global` key. The value is a map of
+variables to inject directly into the running policy.
+
+Example:
+
+```hcl
+global "time" {
+ value = {
+ now = {
+ day = 31
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass. Notice
+that we don't have to do any `import`. The values of `global` are
+injected directly into the global scope.
+
+```json sentinel
+{
+ "policy": "main = time.now.day == 31",
+ "globals": {
+ "time": {
+ "now": {
+ "day": 31
+ }
+ }
+ }
+}
+```
+
+### Parameters
+
+The `param` section allows you to supply values for the
+[parameters](/sentinel/language/parameters) found in a policy. Values
+entered here will satisfy required parameters in a policy, in addition to
+override any defaults. Note that any of the manual parameter value methods
+supported by `sentinel apply` will override the values set here.
+
+```hcl
+param "foo" {
+ value = "bar"
+}
+```
+
+### Test Cases
+
+Test cases specify the test cases for the [test command](/sentinel/commands/test).
+
+Tests are specified by the `test` key. The value of this is a map of
+string to boolean. The key is the name of a rule and the value is the
+expected value of that rule. If a rule is not specified, than any value is
+allowed.
+
+Example:
+
+```hcl
+test {
+ rules = {
+ main = true
+ days = []
+ }
+}
+```
+
+For the policy:
+
+```json sentinel
+{
+ "policy": "valid_days = rule {\n\tfilter days as d {\n\td is \"monday\"\n\t}\n}\n\nmain = rule { valid_days is empty }",
+ "globals": {
+ "days": []
+ }
+}
+```
+
+For more information, read about the [test command](/sentinel/commands/test).
+
+### Sentinel
+
+The `sentinel` block provides configuration specific to the Sentinel runtime.
+
+An example of how the `sentinel` block can be used to enable the [terraform](/sentinel/features/terraform)
+feature is as follows:
+
+```hcl
+sentinel {
+ features = {
+ terraform = true
+ }
+}
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/configuration/overrides.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/configuration/overrides.mdx
new file mode 100644
index 0000000000..ca1a6842b1
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/configuration/overrides.mdx
@@ -0,0 +1,80 @@
+---
+page_title: Override Files
+sidebar_current: docs-configuration-overrides
+description: >-
+ The Sentinel CLI's configuration can be overriden using override files.
+ Override files merge additional settings into existing configuration.
+layout: docs
+---
+
+# Override Files
+
+As outlined in the configuration [overview](/sentinel/configuration#Apply),
+the normal behavior of Sentinel is to load all `.hcl` files into a single
+configuration object.
+
+In addition to appending multiple configuration files, Sentinel allows for the
+rare case of overriding configuration through override files. Override files
+are any files that match either `_override.{hcl,json}` or `override.{hcl,json}`.
+
+Sentinel will parse override files after it has loaded the primary
+configuration, and then process each override file in turn (in lexicographical
+order). All top level blocks other than [test](/sentinel/configuration#test-cases)
+require the block to already be defined in the primary configuration. It will
+then attempt to merge the override block into the existing configuration.
+
+## Example
+
+If you had a Sentinel configuration `sentinel.hcl` that contained the following:
+
+```hcl
+policy "main" {
+ source = "./main.sentinel"
+ enforcement_level = "advisory"
+}
+```
+
+And you created a file `override.hcl` that contained the following:
+
+```hcl
+policy "main" {
+ enforcement_level = "soft-mandatory"
+}
+```
+
+Sentinel will merge the contents of the override into the former, providing the
+following configuration:
+
+```hcl
+policy "main" {
+ source = "./main.sentinel"
+ enforcement_level = "soft-mandatory"
+}
+```
+
+## Merging Behavior
+
+The general rule, which applies in most cases, is:
+
+- A top-level block in an override file merges with a block in a normal
+ configuration file that has the same block header. The block header is the
+ block type and any quoted labels that follow it.
+- Within a top-level block, an attribute argument within an override block
+ replaces any argument of the same name in the original block.
+- Within a top-level block, any nested blocks within an override block replace
+ all blocks of the same type in the original block. Any block types that do not
+ appear in the override block remain from the original block.
+- The contents of nested configuration blocks are not merged.
+- The resulting merged block must still comply with any validation rules that
+ apply to the given block type.
+
+If more than one override file defines the same top-level block, the overriding
+effect is compounded, with later blocks taking precedence over earlier blocks.
+Overrides are processed in order first by filename (in lexicographical order)
+and then by position in each file.
+
+## Required Attributes
+
+It is important to note that all attributes inside an override file are
+considered to be optional, meaning that an override file can itself fail
+validation rules for block types.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/configuration/remote-sources.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/configuration/remote-sources.mdx
new file mode 100644
index 0000000000..b64e4d3abc
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/configuration/remote-sources.mdx
@@ -0,0 +1,165 @@
+---
+page_title: Remote Sources
+sidebar_current: docs-configuration-remote-sources
+description: >-
+ There are a number of options for formatting the URL provided to the
+ source attribute on policy and module blocks.
+layout: docs
+---
+
+# Remote Sources
+
+There are a number of options for formatting the URL provided to the
+`source` attribute on `policy` and `import "module"` blocks. This flexibility
+should solve most use cases and allow for reusable policies and modules.
+
+### Supported Protocols
+
+-> **NOTE:** All protocols are supported on the CLI, however each production
+Sentinel integration may not. Be sure to read the appropriate integration
+documentation to check the supported protocols.
+
+- Local filesystem
+- Git
+- Mercurial
+- HTTP
+- Amazon S3
+- Google GCP
+
+### Forced Protocols
+
+Due to the ambiguous nature of URLs, it is possible to force a particular
+protocol to be used during the fetch. To achieve this, simply prefix the url
+with the appropriate protocol as listed below:
+
+- `file` - Local filesystem
+- `git` - Git
+- `hg` - Mercurial
+- `http` or `https` - HTTP
+- `s3` - Amazon S3
+- `gcp` - Google GCP
+
+Example:
+
+```
+git::http://github.com/hashicorp/sentinel.git
+```
+
+The above would download the provided HTTP URL using the Git protocol.
+
+### Protocol Options
+
+In addition to some global options, there are options available to specific
+protocols to perform features such as authentication.
+
+#### Subdirectories / File Pathing
+
+When supplying URLs that point to a directory (eg. `git`), a subdirectory and
+file can be provided by suffixing the URL with `//` and the path. For example,
+if we have a git repository that has a policy, `main.sentinel` in the root, we
+could directly fetch the file by using the following:
+
+```
+git::https://github.com/hashicorp/sentinel-docs-example.git//main.sentinel
+```
+
+Or, if the file was nested in the `policies` folder:
+
+```
+git::https://github.com/hashicorp/sentinel-docs-example.git//policies/main.sentinel
+```
+
+#### Checksumming
+
+For downloads of any protocol, you can automatically verify a checksum. To
+checksum a file, append a `checksum` query parameter to the URL. The parameter
+value can be in the format of type:value or just value, where type is "md5",
+"sha1", "sha256", "sha512" or "file" . The "value" should be the actual
+checksum value or download URL for "file". When type part is omitted, type will
+be guessed based on the length of the checksum string.
+
+```
+./foo.sentinel?checksum=md5:b7d96c89d09d9e204f5fedc4d5d55b21
+
+./foo.sentinel?checksum=b7d96c89d09d9e204f5fedc4d5d55b21
+
+./foo.sentinel?checksum=file:./foo.txt.sha256sum
+```
+
+#### Unarchiving
+
+An `archive` query parameter can be supplied to explicitly state what format
+to attempt to extract, if required. The supported formats are:
+
+- `tar.gz` and `tgz`
+- `tar.bz2` and `tbz2`
+- `tar.xz` and `txz`
+- `zip`
+- `gz`
+- `bz2`
+- `xz`
+
+If a URL has an extension that matches one of the supported formats, it will
+use that format to unarchive.
+
+```
+./file.zip
+```
+
+The above would use the `zip` format to unarchive.
+
+```
+./path/to/file?archive=zip
+```
+
+Would force the `zip` format. If for some reason you required archiving to be
+disabled, this can also be achieved by using the `false` value.
+
+```
+./path/to/file?archive=false
+```
+
+#### Git (`git`)
+
+- `ref` - The Git ref to checkout. This is a ref, so it can point to a commit
+ SHA, a branch name, etc. If it is a named ref such as a branch name, it will
+ be updated to the latest on each get.
+- `sshkey` - An SSH private key to use during clones. The provided key must be
+ a base64-encoded string. For example, to generate a suitable sshkey from a
+ private key file on disk, you would run base64 -w0 \.
+ **Note:** Git 2.3+ is required to use this feature.
+- `depth` - The Git clone depth. The provided number specifies the last `n`
+ revisions to clone from the repository.
+
+The git getter accepts both URL-style SSH addresses like
+`git::ssh://git@example.com/foo/bar`, and "scp-style" addresses like
+`git::git@example.com/foo/bar`. In the latter case, omitting the `git::` forced
+prefix is allowed if the username prefix is exactly git@.
+
+The "scp-style" addresses cannot be used in conjunction with the `ssh://`
+scheme prefix, because in that case the colon is used to mark an optional port
+number to connect on, rather than to delimit the path from the host.
+
+#### Mercurial (`hg`)
+
+- `rev` - The Mercurial revision to checkout.
+
+#### HTTP (`http` or `https`)
+
+Basic Authentication
+
+To use HTTP basic authentication, simply prepend `username:password@` to the
+hostname in the URL such as `https://Aladdin:OpenSesame@www.example.com/index.html`.
+All special characters, including the username and password, must be URL
+encoded.
+
+#### S3 (`s3`)
+
+The following query parameters are available and will take priority when
+authenticating against an S3 bucket.
+
+- `aws_access_key_id` - AWS access key.
+- `aws_access_key_secret` - AWS access key secret.
+- `aws_access_token` - AWS access token if this is being used.
+- `aws_profile` - Use this profile from local ~/.aws/ config. Takes priority
+ over the other three.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/consul.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/consul.mdx
new file mode 100644
index 0000000000..7a3aaaa697
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/consul.mdx
@@ -0,0 +1,47 @@
+---
+page_title: Consul and Sentinel
+sidebar_current: docs-app-consul
+description: >-
+ Consul Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies are applied during writes to
+ the KV Store.
+layout: docs
+---
+
+# Consul
+
+[Consul Enterprise](https://www.hashicorp.com/products/consul/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies are applied during writes to the KV Store.
+
+Sentinel policies have access to the key/value being written. They can be used to
+allow or deny the modification. The information that Sentinel policies have access
+to will expand over time.
+
+The Consul integration with Sentinel is documented in depth in the
+[Consul Enterprise documentation](https://www.consul.io/docs/guides/sentinel.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Input validation depending on the name of the key.**
+
+```sentinel
+main = rule { valid_key() }
+
+required = [
+ ["port", "\\d+"], # ports must be integers
+ ["name", "\\w+"], # name must be a word
+]
+
+valid_key = func() {
+ for required as v {
+ if key is v[0] {
+ return value matches v[1]
+ }
+ }
+
+ return false
+}
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/extending/index.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/extending/index.mdx
new file mode 100644
index 0000000000..a39cb0384d
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/extending/index.mdx
@@ -0,0 +1,52 @@
+---
+page_title: Extending Sentinel
+sidebar_current: docs-extending
+description: Learn how to create your own Sentinel imports to extend Sentinel's functionality.
+layout: docs
+---
+
+# Extending Sentinel
+
+-> **NOTE:** Sentinel's extensibility is currently undergoing large
+improvements - watch this space for future updates!
+
+Sentinel can be extended by writing additional
+[imports](/sentinel/language/imports). These imports can bring new functionality
+to Sentinel by allowing access to new data or by the addition of new functions.
+This section is designed to show you how to accomplish this extensibility and
+describe how it works.
+
+Currently, the only usable way to add additional imports is via
+[modules](#modules). While [plugins](#plugins) currently work under the Sentinel
+CLI, no Sentinel integration currently supports their use.
+
+-> You may also be interested in advanced details on [import
+internals](/sentinel/extending/internals).
+
+## Modules
+
+Modules allow you to re-use Sentinel code as an import. Modules are loaded
+external of policies and mapped to imports. This is usually configured via a
+file such as the [CLI configuration file](/sentinel/configuration).
+
+See the [modules](/sentinel/extending/modules) section for more details.
+
+## Plugins
+
+-> **NOTE:** Plugins are currently only supported on the CLI and not in any
+production Sentinel integration, with the exception of existing configuration
+in [Nomad](https://www.nomadproject.io/docs/configuration/sentinel).
+
+Imports can also be implemented as plugins when Sentinel code itself is too
+restrictive to represent the import, or if you need access to features not
+normally available to the Sentinel runtime.
+
+See the [plugins](/sentinel/extending/plugins) section for more details.
+
+## Static Imports
+
+Static imports provide support for loading data files into the Sentinel runtime,
+making them available as an import.
+
+See the [static imports](/sentinel/extending/static-imports) section for more
+details.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/extending/internals.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/extending/internals.mdx
new file mode 100644
index 0000000000..7133e6df1b
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/extending/internals.mdx
@@ -0,0 +1,252 @@
+---
+page_title: >-
+ Extending Sentinel: Internals
+sidebar_current: docs-extending-internals
+description: This Section covers some details about Sentinel's import internals.
+layout: docs
+---
+
+# Extending Sentinel: Internals
+
+-> **Advanced Topic!** This page covers low-level technical details of Sentinel.
+You don't need to understand these details to effectively use Sentinel. The
+details are documented here for those who wish to learn about them.
+
+This section covers some of the internal details of Sentinel's import system.
+The goal of this section is to help remove notion of "magic" from Sentinel, to
+allow you to trust and understand what Sentinel is doing with imports, and also
+to help practitioners and developers alike understand the differences between
+the ways that imports can be loaded into Sentinel.
+
+## Language and Runtime
+
+Understanding the import system in Sentinel generally requires understanding of
+two key concepts within Sentinel: the _language_, and the _runtime
+implementation_ of the language.
+
+The Sentinel language and the rules governing it are detailed in the [Sentinel
+Language Specification](/sentinel/language/spec). A runtime implementation must
+conform to the rules laid out in the specification at a minimum. However, to
+meet the design goals of a particular implementation, the runtime may implement
+features on top of the language. The subtle implementation details within the
+runtime may also vary while still being compliant with the specification.
+
+The core runtime included within the [Sentinel CLI](/sentinel/commands) is
+sometimes referred to as the _reference implementation_ of the Sentinel
+language. This runtime is also the main runtime embedded within each
+Sentinel-enabled HashiCorp product such as HCP Terraform, Terraform Enterprise, Vault
+Enterprise, Nomad Enterprise, and Consul Enterprise. The runtime includes an API
+to allow these integrations implement Sentinel in as robust or as restrictive of
+a way as possible, so depending on the implementation, your experience may vary.
+
+## Sentinel's Import Model
+
+The concept of Imports within Sentinel is a _language_ feature. You can see the
+exact rules governing imports within the
+[Imports](/sentinel/language/spec#imports) section of the specification.
+
+Within the runtime, there are currently two major classifications of imports:
+
+- **Modules:** The mapping of Sentinel code to an import, essentially mapping a
+ scope of values to a particular package.
+- **Binary Imports:** A grouping of embedded and external plugins written using
+ the Sentinel SDK. These are comprised of internal or embedded imports (usually
+ the [standard imports](/sentinel/imports) and imports included by an
+ integration) and import plugins.
+
+Below is a diagram explaining in brief the relationship between the langauge
+and runtime features.
+
+
+
+This kind of topology ultimately minimizes the visibility of implementation
+internals to the actual policy language. This allows us to keep the Sentinel
+language itself simple and keep concerns such as module or plugin management,
+validation, and configuration out of the language. These kinds of things would
+impact the readability of a policy, possibly present security challenges, and
+would ultimately be of no use to more minimal embedded applications.
+
+### Implementation Differences Between Import Types
+
+As one would imagine, both modules and binary imports are loaded in much
+different ways, and the methods that they expose data to Sentinel differ as
+well. The effect is generally the same however across both, and we take care to
+ensure that both modules and binary imports behave as close as possible to each
+other, however there are some subtle differences:
+
+- Modules currently support the exporting of rules, but do not support function
+ calls with object receiver data (as seen in some standard imports such as
+ [`decimal`](/sentinel/imports/decimal), [`http`](/sentinel/imports/http), and
+ [`time`](/sentinel/imports/time)). This will be coming in a later release.
+- Binary imports cannot export rules. However, they do support object receiver
+ data (see [`framework.New`](/sentinel/extending/plugins#framework-new) in
+ [Extending Sentinel: Plugins](/sentinel/extending/plugins)).
+
+## Modules
+
+_Modules_ are essentially Sentinel code that is intended to be re-used in multiple
+policies as an import.
+
+The mechanism of module loading is fairly straightforward: during initialization
+of the Sentinel runtime, modules are parsed along with policies. The parsed
+_abstract syntax tree_ (AST) for each module is loaded into the runtime under
+the specified _import path_. These are then passed to the lower-level
+interpreter during the evaluation of each policy.
+
+As with policy code, during evaluation, a module's AST is walked to execute the
+code within that specific module. The module data is then stored within an
+specific scope mapped to the import name. The module data can then be accessed
+through the import via selector expressions and function calls (e.g.,
+`foo.some_map` or `foo.some_func()` for a module loaded via `import "foo"`).
+
+The AST for a module is only walked if a policy imports it, and it is _only
+walked once_. That means if a policy and a module import the same module, or a
+policy imports the same module twice (using [import
+aliases](/sentinel/language/imports#aliases)), those instances will share state.
+If you call a function that alters a singleton variable within the module, that
+singleton will be modified for all instances of the import.
+
+-> **Fun fact!** When you [mock an import with Sentinel
+code](/sentinel/configuration#mocking-with-sentinel-code), you are actually
+using a module. The module is loaded with a flag that allows it to override an
+existing import, which would normally give a runtime error.
+
+### Map and List Cloning for Module Calls
+
+It's also worthwhile noting that map and list values are cloned for modules.
+
+Consider the case where you have a module with a map singleton.
+
+```sentinel
+// modules/foo.sentinel
+a_map = {"a": "b"}
+```
+
+Normally, assignment to the singleton, even when using an index expression, is
+blocked, so `foo.a_map["c"] = "d"` would not pass semantic checking.
+
+However, even when you try to do assignment via an intermediary, you will still
+be only modifying the copy, and the original will not be affected:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+a_map_copy = foo.a_map
+a_map_copy["c"] = "d" // Only modifies copy, does not modify module singleton
+print(foo.a_map) // Still only prints {"a": "b"}
+```
+
+This is to ensure that modules are consistent with binary imports in how return
+data is handled, and the expectation that most non-object data within an import
+is read-only, with the exception of modification of singleton data via
+functions. As return of complex object and collection data in a binary import is
+always via copy, it's not possible to modify any value elements within the
+import in a similar fashion.
+
+There are also some other implications of this cloning that are of note:
+
+- Rules are not cloned, either within a list or map, or by themselves. This is
+ to ensure that [memoization](/sentinel/language/rules#lazy-and-memoized)
+ functions correctly. As only a module can export rules at this time, there is no
+ parity for this in binary imports.
+- Functions are _not_ cloned. This mainly has implications for scope - for
+ example, if you assign a function to another value, or assign an object to a
+ value that has a method that utilizes a global module variable, those calls will
+ reference and/or modify those values.
+
+Functions, while represented differently in binary imports, generally follow the
+same logic here - as they would be invoked in the same package within the binary
+import, any singleton values they accessed there would be affected in a similar
+fashion. As rules are pseudo-functions, this generally makes sense here too.
+
+## Binary Imports
+
+_Binary imports_ is a grouping referring to all imports that are designed with
+the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). These can be
+either internally embedded, or executed via a plugin.
+
+All imports found in the [standard library](/sentinel/imports) are binary
+imports, embedded internally.
+
+The main difference between internally embedded imports and external plugins is
+whether or not the runtime needs to execute a plugin binary as part of
+[lifecycle management](#lifecycle), and whether or not it needs to
+[communicate](#communication) over RPC. Internal binary imports do not require
+these steps, so their execution model is somewhat simpler; however, technically,
+they still are the same as external plugins in terms of design model and value
+conversion. Most standard imports are even tested using the [SDK test
+suite](/sentinel/extending/plugins#testing), which builds the import as an
+external plugin.
+
+The remainder of this section discusses topics mostly relevant to external
+plugins. A large amount of technical detail regarding binary import development
+(also applicable to embedded binary imports) can be seen on the
+[Plugins](/sentinel/extending/plugins) page.
+
+-> **NOTE:** At this time, plugins can only be written for the Sentinel CLI, and
+cannot be used with any of Sentinel's integrations.
+
+### Plugin Basics
+
+Sentinel plugins are built on top of the HashiCorp [go-plugin
+system](https://github.com/hashicorp/go-plugin). This is the same plugin system
+powering all pluggable HashiCorp tools such as
+[Terraform](https://www.terraform.io), [Vault](https://www.vaultproject.io), and
+more. go-plugin is a system that has been used in production for millions of
+users for over 5 years.
+
+Plugins are executable binaries. Sentinel is responsible for launching and
+managing the lifecycle of plugins. Once a plugin is running, Sentinel
+communicates with the plugin via [gRPC](https://grpc.io/). The [protocol is open
+source](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/import.proto)
+to allow anyone to write a Sentinel plugin.
+
+### Lifecycle
+
+Sentinel launches plugins and is responsible for plugin lifecycle.
+
+The runtime optimizes for latency by launching the plugin as soon as it is
+configured, rather than when a policy requires it. This ensures that the plugin
+is ready to be used immediately. A plugin is only closed when Sentinel is
+reconfigured to no longer allow that plugin or if the Sentinel-enabled
+application is closing.
+
+When a plugin is removed from the configuration, Sentinel may wait to shut down
+the plugin until all currently executing policies that are using that plugin
+complete. New policy executions will not be allowed to use the old plugins.
+
+Plugins are automatically restarted if they shut down unexpectedly. Policies
+that were executing while this happens may fail. The Sentinel system is
+improving to more gracefully understand locations where it is safe to retry an
+execution.
+
+### Communication
+
+An [initial
+handshake](https://github.com/hashicorp/go-plugin/blob/master/docs/internals.md)
+is done via stdout to determine the main communication location. Following the
+handshake, communication will either occur on a local Unix domain socket or via
+a local-only TCP socket. This communication is done mostly over the low-level
+[`Get`](https://github.com/hashicorp/sentinel-sdk/blob/2b4db07dd12b55142f0c016f301af80aa4422520/proto/import.proto#L12)
+RPC call.
+
+#### Value Conversion
+
+As can generally be seen in [Developing an Import
+Plugin](/sentinel/extending/plugins#developing-an-import-plugin), the Sentinel
+type system is not exposed to binary imports. While explicit values for null and
+undefined exist, values are mostly converted over the wire from their Go values
+to their Sentinel equivalents.
+
+This has a few implications:
+
+- As discussed in [Map and List Cloning for Module
+ Calls](#map-and-list-cloning-for-module-calls), Map and list data returned from
+ binary import value lookups or function calls is always cloned. These can be
+ freely modified without affecting anything within the binary import.
+- It's currently impossible to represent certain Sentinel types such as
+ [rules](/sentinel/language/rules) within a binary import. This is not a major
+ issue at this point in time as practitioners generally do not look to binary
+ imports for rules; we may examine this as a later time if this situation
+ changes.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/extending/modules.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/extending/modules.mdx
new file mode 100644
index 0000000000..9846d263e5
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/extending/modules.mdx
@@ -0,0 +1,135 @@
+---
+page_title: >-
+ Extending Sentinel: Modules
+sidebar_current: docs-extending-modules
+description: >-
+ Modules allow you to re-use Sentinel code as an import.
+layout: docs
+---
+
+# Extending Sentinel: Modules
+
+Modules allow you to re-use Sentinel code as an import. This allows for the
+export of any general construct that Sentinel supports, including values,
+functions, and even rules. As such, it's a great way to package code that is
+useful across multiple policies, reducing boilerplate and making final policy
+code simpler.
+
+You may want to use modules for:
+
+- **Writing a simple re-usable helper library.** If you mostly know Sentinel or
+ have helpers that you have written in Sentinel, moving these helpers to a module
+ will offer a low-friction way of facilitating their re-use.
+- **Writing re-usable rules.** If you have a set of
+ [rules](/sentinel/language/rules) you know you want to use across multiple
+ policies, bundling them into a module is a great way of abstracting the logic
+ away.
+- **Abstracting existing Sentinel imports.** Using a module is a great way to
+ extend existing Sentinel imports by abstracting the common functionality that
+ you use within an import to your own workflow.
+
+This page describes how you can use modules within the context of the Sentinel
+CLI. For instructions for a particular integration, see the documentation for
+that product.
+
+-> **Not all Sentinel-enabled applications support modules.** Please refer
+to the documentation of the specific Sentinel-enabled application. Some
+applications disable modules for security reasons.
+
+## Configuring a Module
+
+A module is configured within the Sentinel CLI by adding an entry for it in
+within the `imports` section of the [CLI configuration
+file](/sentinel/configuration). The key for each entry specifies the path that
+the module is imported as.
+
+```hcl
+import "module" "foo" {
+ source = "modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "modules/bar.sentinel"
+}
+```
+
+In this example, we have two modules:
+
+- Import path `foo`, being loaded from the code within `modules/foo.sentinel`.
+- Import path `bar`, being loaded from the code within `modules/bar.sentinel`.
+
+See the [Import Modules](/sentinel/configuration#import-modules) section within
+the CLI configuration file syntax page for more details.
+
+### Remote Modules
+
+In addition to loading from a local source, modules can be loaded from a remote
+location. This can increase reusability and allow for sharing modules across
+multiple configurations.
+
+### Testing Using Modules
+
+As with [mocks](/sentinel/configuration#mocking-with-sentinel-code), modules
+are loaded relative to the configuration file location when using `sentinel test`. As such, you need to account for this in your test configuration files:
+
+```hcl
+import "module" "foo" {
+ source = "../../modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "../../modules/bar.sentinel"
+}
+```
+
+This could be a test file for a policy (example: `policy.sentinel`), residing
+under the correct test file directory for this policy (example:
+`test/policy/pass.json`).
+
+## Writing and Using a Module
+
+Writing a module is nearly the same as writing a Sentinel policy, except for the
+following two exceptions:
+
+- Modules do not require [`main`](/sentinel/writing/basic#the-simplest-policy)
+ to be present. It can be, but it will not carry any extra significance as it
+ does within a policy.
+- [`param`](/sentinel/language/parameters) declarations cannot be present in a module. If they are encountered,
+ a runtime error is given.
+
+Once you have a complete module ready for use and configured, using the module
+is as simple as importing it.
+
+Building on the above [configuration example](#configuring-a-module), we can
+export a simple test function in the module `foo` by adding this code to
+`modules/foo.sentinel`:
+
+```sentinel
+// modules/foo.sentinel
+hello = func() {
+ print("hello world!")
+ return undefined
+}
+```
+
+We can then import it within our policy and use it:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+// prints "hello world" in the trace
+foo.hello()
+
+main = true
+```
+
+Note that modules are considered [imports](/sentinel/language/spec#imports) by
+the Sentinel runtime and as such conform to the specification. This means that
+you cannot directly assign values to anything behind a module scope. You can,
+however, use functions to change state.
+
+-> **NOTE:** At this time, modules do not support object receiver data in the
+way that some imports do, like [`time`](/sentinel/imports/time),
+[`decimal`](/sentinel/imports/decimal), and [`http`](/sentinel/imports/http).
+Support for this will be coming in later versions of Sentinel.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/extending/plugins.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/extending/plugins.mdx
new file mode 100644
index 0000000000..e80de5d4b1
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/extending/plugins.mdx
@@ -0,0 +1,521 @@
+---
+page_title: >-
+ Extending Sentinel: Plugins
+sidebar_current: docs-extending-plugins
+description: >-
+ Plugins allow you to write imports as standalone executables, allowing you to
+ extend Sentinel in ways not normally possible with policy code alone.
+layout: docs
+---
+
+# Extending Sentinel: Plugins
+
+-> **NOTE:** Plugins are currently only supported on the CLI and not in any
+production Sentinel integration, with the exception of existing configuration
+in [Nomad](https://www.nomadproject.io/docs/configuration/sentinel).
+
+Plugins allow you to write imports as standalone executables, allowing you to
+extend Sentinel in ways not normally possible with policy code alone.
+
+You might want to write or use a plugin when you need to:
+
+- **Use a provider or API integration for Sentinel.** In this case, you will
+ probably be working with a provider SDK, or other libraries that will be making
+ complex network requests to external services. Sentinel only provides limited
+ capabilities for these kinds of operations in the standard library, so writing
+ this kind of import as a module is not optimal.
+- **Introduce novel functionality not currently supported by Sentinel.**
+ Sentinel's policy language is limited by design to encourage simple policies,
+ reduce its runtime footprint, and to keep it efficient and safe. This will
+ mean that some functionality may be difficult or impossible to express as
+ Sentinel code, and would require a plugin to write.
+- **Extend a complex module.** Additionally, modules are restricted to a single
+ file of Sentinel code, so these will naturally become more and more unwieldy as
+ you continue to add to them. Eventually, there might be a time where a plugin is
+ better suited for the functionality, especially if it's a general-purpose
+ library.
+
+This section details how import plugins are currently configured in the Sentinel
+CLI, and some detail on how they are written. As we continue to build on the
+ability to use import plugins, we will extend this section with more details.
+
+-> **Not all Sentinel-enabled applications will support plugins.** Some
+applications will disable plugins for security reasons. For those that do enable
+plugins, the method to configure plugins will vary.
+
+## Installing Plugins
+
+Sentinel plugins are standalone executables. Sentinel-enabled applications such
+as the CLI launch these plugins and communicate with them via
+[RPC](https://en.wikipedia.org/wiki/Remote_procedure_call). To install a plugin,
+the first step is to download the plugin executable.
+
+After downloading the plugin, you must add it in the CLI configuration file,
+setting its name, path, and any arguments, environment variables, or
+configuration. An example is below:
+
+```hcl
+import "plugin" "custom_time" {
+ source = "/path/to/sentinel-import-time"
+ config = { "fixed_time": 1504155600 }
+}
+```
+
+-> **NOTE:** We are using "custom_time" as standard import paths cannot be
+overriden. See [Configuration File Syntax](/sentinel/configuration#imports) for
+more details.
+
+After configuring the plugin, it will be available on the next `sentinel apply`
+or `sentinel test`.
+
+For more details on configuration, see the
+[plugins](/sentinel/configuration#plugins) section of the [CLI configuration
+file syntax](/sentinel/configuration).
+
+## Debugging Plugins
+
+The Sentinel CLI logs when it launches, configures, and closes a plugin. The
+plugin may also log additional information when it is running.
+
+To debug issues with a plugin, you can usually refer to these logs. Depending on
+the plugin configuration, the logs you see may vary - refer to the plugin
+documentation on how to enable logs for a particular plugin.
+
+## Developing an Import Plugin
+
+Anyone can develop a Sentinel import plugin using the [Sentinel
+SDK](https://github.com/hashicorp/sentinel-sdk). The SDK contains a high-level
+framework for writing plugins in [Go](https://golang.org) including a test
+framework. Additionally, the SDK contains the low-level [gRPC protocol buffers
+definition](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/plugin.proto)
+for writing plugins in other languages.
+
+The rest of this page will document how to write a new Sentinel plugin in Go
+using the Sentinel SDK and the high-level framework provided. You are expected
+to already know the basics of Go and have a properly configured Go installation.
+
+Throughout this page, we'll be developing a simple plugin to access time values.
+
+-> **NOTE:** The example here is not reflective of the actual implementation of
+the [`time`](/sentinel/imports/time) standard import, so make sure to not
+get the two confused.
+
+### Plugin Framework
+
+The Sentinel SDK provides a high-level
+[framework](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework)
+for writing plugins. We recommend using this framework.
+
+#### Basic Concepts
+
+This framework works by implementing the correct Go interfaces.
+
+As a general overview:
+[`framework.Plugin`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Plugin)
+implements the
+[`sdk.Plugin`](https://godoc.org/github.com/hashicorp/sentinel-sdk#Plugin)
+interface and therefore is a valid import plugin. You must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface to configure the plugin. The root may then delegate nested access to
+various
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+implementations. Other interfaces are implemented to provide functionality past
+what is provided by the basic `framework.Namespace` implementation - these are
+documented below.
+
+This all may sound like a lot of interfaces, but each interface typically only
+requires a single function implementation and you're only required to implement
+a single namespace (`framework.Namespace`).
+
+As we move on, we'll use real examples to make this clear.
+
+#### Implementing framework.Root
+
+To begin, you must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface. This is the interface representing the root of your plugin. The root
+itself must be implement
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace),
+which allows values to be accessed. Note that the root can optionally implement
+other interfaces, but they're more advanced and omitted here for simplicity.
+
+For our custom time example, our root may look like this:
+
+```sentinel
+type root struct {
+ time time.Time
+}
+
+// framework.Root impl.
+func (m *root) Configure(raw map[string]interface{}) error {
+ if _, ok := raw["timestamp"]; !ok {
+ raw["timestamp"] = time.Now().Unix()
+ }
+
+ v := raw["timestamp"]
+ timestamp, ok := v.(int)
+ if !ok {
+ return fmt.Errorf("invalid timestamp type %T", v)
+ }
+
+ m.time = time.Unix(timestamp, 0).UTC()
+ return nil
+}
+
+// framework.Namespace impl.
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "minute":
+ return m.time.Minute(), nil
+ }
+
+ return nil, nil
+}
+```
+
+This example implements `framework.Root` and `framework.Namespace` and would
+enable the following within a Sentinel policy once the completed plugin is
+installed.
+
+```sentinel
+import "custom_time"
+
+print(custom_time.minute)
+main = true
+```
+
+#### framework.Namespace
+
+The
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+interface implements a namespace of values. You saw above that
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+must itself be a namespace. However, a namespace `Get` implementation may
+further return namespaces to access nested values.
+
+This enables behavior such as `custom_time.month.string` vs. `custom_time.month.index`.
+Notice each of these examples accesses a nested value within `month`. This can
+be modeled as namespaces within the framework.
+
+In the example below, we return a new namespace for `month` to do just this:
+
+```sentinel
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "month":
+ return &namespaceMonth{Month: m.time.Month()}, nil
+ }
+
+ // ...
+}
+
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil nil
+}
+```
+
+#### Primitive Types and Structs
+
+A namespace may also return any primitive Go type as well as structs. The
+framework automatically exposes primitive values as you would expect. For
+structs, exported fields are lowercased and exposed to the policies. The
+`sentinel` struct tag can be used to control how Sentinel can access the field.
+
+In the example below, we expose a struct directly from the root namespace:
+
+```sentinel
+type Location struct {
+ Name string
+ TimeZone string `sentinel:"time_zone"`
+}
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "location":
+ return &Location{Name: "somewhere", TimeZone: "some zone"}, nil
+ }
+
+ // ...
+}
+```
+
+This makes the following values work within a Sentinel policy:
+`time.location.name`, `time.location.time_zone`.
+
+If a field has an empty `sentinel` struct tag (example: `sentinel:""`),
+then that field will not be accessible from a policy.
+
+#### Optional Interfaces
+
+The following are optional interfaces that can be implemented on top of
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace).
+All of these interfaces can be implemented at any level, except for
+[`framework.New`](#framework-new), which only works at the root level.
+
+##### `framework.Call`
+
+The
+[`framework.Call`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Call)
+interface can be implemented to support function calls.
+
+Implementing this interface is very similar to attribute access, except instead
+of returning the attribute value, you return a function. The framework uses Go
+reflection to determine the argument and result types and calls it. If the
+argument types do not match or the signature is otherwise invalid, an error is
+returned.
+
+For example, let's implement a function to add months to the current month:
+
+```sentinel
+func (m *root) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *root) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.time.AddDate(0, n, 0).Month()}
+}
+```
+
+You can now call the function. If today was in the month of September,
+the policy below would pass:
+
+```sentinel
+import "custom_time"
+
+main = custom_time.add_month(4).string == "January"
+```
+
+##### `framework.Map`
+
+The optional
+[`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+interface allows an alternative method to to present a namespace back to a
+Sentinel policy as a map.
+
+`framework.Map` is best paired with
+[`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys),
+which allows you to quickly fetch the necessary data via `Get` calls on the
+namespace:
+
+```
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil, nil
+}
+
+func (m *namespaceMonth) Map() (map[string]interface{}, error) {
+ return framework.MapFromKeys(m, []string{"string", "index"})
+}
+```
+
+##### `framework.New`
+
+The optional
+[`framework.New`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#New)
+interface can be implemented on a root namespace to add _methods_ to your
+namespaces.
+
+Data returned from a namespace is memoized and returned as a map, and is
+normally not callable - to call a function to operate on the data, you would
+need to create a new namespace from the top-level of the plugin, and then make a
+function call on the result within the same expression.
+
+Let's consider the [root example](#implementing-framework-root). Let's say,
+instead of hard-coding the time value at configuration, we wanted to load it
+on-demand using a `time.now` key, and allow `add_month` to be callable on that
+value. Our root and de-coupled time namespace would now look like:
+
+```
+type root struct{}
+
+func (m *root) Configure(raw map[string]interface{}) error { return nil }
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "now":
+ return &namespaceTime{Time: time.Now()}
+ }
+
+ return nil
+}
+
+func (m *root) New(data map[string]interface{}) (framework.Namespace, error) {
+ if v, ok := data["unix"]; ok {
+ if t, ok := v.(int64); !ok {
+ return &namespaceTime{Time: time.Unix(t, 0)}
+ }
+
+ return nil, fmt.Errorf("expected timestamp to be int64, got %T", v)
+ }
+
+ return nil, nil
+}
+
+type namespaceTime struct {
+ Time time.Time
+}
+
+func (m *namespaceTime) Get(key string) interface{} {
+ switch key {
+ case "unix":
+ return m.Time.Unix()
+
+ // case ...
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) Get(key string) (interface{}, error) {
+ return framework.MapFromKeys(m, []string{"unix", "..."})
+}
+
+func (m *namespaceTime) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.Time.AddDate(0, n, 0).Month()}
+}
+```
+
+Via this example, we can now create and assign a `namespaceTime` using `t = custom_time.now`, and then call `t.add_month(months_to_add)` to return a
+`namespaceMonth` for the corresponding month.
+
+Methods on a namespace can also _modfiy_ the receiver data. So you can, for
+example, add a method named `increment_month` that takes no arguments, but
+increments the time stored in `namespaceTime.Time` by a month. Subsequent
+statements using the receiver would see the new value.
+
+Note that there are some restrictions and guidelines that you should take into
+account when using `framework.New`:
+
+- Only data that makes it back to a policy can be used as receiver data to
+ instantiate new namespaces. As such it's recommended to make use of
+ [`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+ and
+ [`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys)
+ to return all of the attribute data you need to construct new objects.
+- `framework.New` is only supported on the root namespace and has no effect on
+ namespaces below the root. Check all possible cases of receiver data within
+ the top-level `New` function and return the appropriate namespace based on the
+ input data.
+- Do not return general errors from your `New` method. When an unknown lookup is
+ made off of memoized data, it will hit your `New` method for possible
+ instantiation and key calls. This will ensure `undefined` is correctly
+ returned for these cases.
+- `framework.New` is designed to add utility to plugins where calling methods on
+ a value is more intuitive than just making a function call, or just returning
+ all of a data set as a memoized map. Use it sparingly and avoid using it on
+ recursively complex data sets.
+
+### Testing
+
+The SDK exposes a testing framework to verify your plugin works as
+expected. This test framework integrates directly into `go test` so it
+is part of a familiar workflow.
+
+The test framework works by dynamically building your plugin and
+running it against the `sentinel` CLI to verify it behaves as expected.
+This ensures that your plugin builds, your plugin communicates via RPC
+correctly, and that the behavior within a policy is also correct.
+
+The example below shows a complete ready table-driven test for our time plugin.
+You can run this with a normal `go test`.
+
+```sentinel
+package main
+
+import (
+ "os"
+ "testing"
+
+ "github.com/hashicorp/sentinel-sdk"
+ plugintesting "github.com/hashicorp/sentinel-sdk/testing"
+)
+
+func TestMain(m *testing.M) {
+ exitCode := m.Run()
+ plugintesting.Clean()
+ os.Exit(exitCode)
+}
+
+func TestPlugin(t *testing.T) {
+ cases := []struct {
+ Name string
+ Data map[string]interface{}
+ Source string
+ }{
+ {
+ "month",
+ map[string]interface{}{"timestamp": 1495483674},
+ `main = subject.month.string == "September"`,
+ },
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.Name, func(t *testing.T) {
+ plugintesting.TestPlugin(t, plugintesting.TestPluginCase{
+ Config: tc.Data,
+ Source: tc.Source,
+ })
+ })
+ }
+}
+```
+
+### Building Your Plugin
+
+To build your plugin, you must first implement the `main` function.
+The main function should just use the `rpc.Serve` method to serve the
+plugin over the Sentinel RPC layer:
+
+```sentinel
+package main
+
+import (
+ "github.com/hashicorp/sentinel-sdk"
+ "github.com/hashicorp/sentinel-sdk/rpc"
+)
+
+func main() {
+ rpc.Serve(&rpc.ServeOpts{
+ PluginFunc: func() sdk.Plugin {
+ return &framework.Plugin{Root: &root{}}
+ },
+ })
+}
+```
+
+You can then build this using `go build`. The output can be named
+anything. Once your plugin is built, [install it](#installing-plugins)
+and try it out!
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/extending/static-imports.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/extending/static-imports.mdx
new file mode 100644
index 0000000000..cb51beb5d8
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/extending/static-imports.mdx
@@ -0,0 +1,48 @@
+---
+page_title: >-
+ Extending Sentinel: Static Imports
+sidebar_current: docs-extending-static-imports
+description: >-
+ Static imports allow you to write imports that use static data files.
+layout: docs
+---
+
+# Extending Sentinel: Static Imports
+
+Static imports allow you to write imports that use static data files. This can
+provide methods of consuming data in ways that are not available via
+[modules](./modules) or [plugins](./plugins).
+
+### Configuring a Static Import
+
+The [configuration page](/sentinel/configuration#static-imports) details how to
+configure static imports.
+
+### Usage
+
+Using a static import is not that different to using a plugin or module. The
+import must first be introduced to the policy via an import statement.
+
+```sentinel
+import "people"
+```
+
+From here, the static data can be used throughout the policy as you would any
+normal Sentinel value.
+
+```sentinel
+admins = filter people as person {
+ person.role is "Admin"
+}
+```
+
+One differentiator for static imports is that you can directly reference the
+import without the use of selectors. For example, to print the value;
+
+```sentinel
+print(people)
+```
+
+This is due to the static data being directly loaded and converted into a
+Sentinel value, ready for use. Static imports, like modules and plugins, cannot
+be assigned a new value.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/features/index.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/features/index.mdx
new file mode 100644
index 0000000000..e035be978c
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/features/index.mdx
@@ -0,0 +1,28 @@
+---
+page_title: Features
+sidebar_current: docs-features
+description: Learn how features can enhance the Sentinel runtime by enabling further capabilities.
+layout: docs
+---
+
+# Features
+
+Features are a set of capabilities that enhance the runtime experience. They
+are enabled through the [`sentinel`](/sentinel/configuration#sentinel) block of the
+configuration, using the features attribute. An example of how to enable features
+is as follows:
+
+```hcl
+sentinel {
+ features = {
+ apply-all = true
+ terraform = true
+ }
+}
+```
+
+The current set of features are:
+
+- [terraform](/sentinel/features/terraform) - Allowing the Sentinel runtime
+ to interact with Terraform data.
+- apply-all - Force Sentinel to evaluate all policies within a policy set without prematurely terminating on failures.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/features/terraform/index.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/features/terraform/index.mdx
new file mode 100644
index 0000000000..a810dbe478
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/features/terraform/index.mdx
@@ -0,0 +1,24 @@
+---
+page_title: terraform
+sidebar_current: docs-features-terraform
+description: Learn about the terraform feature and its capabilities.
+layout: docs
+---
+
+# terraform feature
+
+The `terraform` feature enhances the Sentinel runtime to work with Terraform
+data. It will add the following imports to the standard library:
+
+- [tfplan/v1](/sentinel/features/terraform/tfplan-v1)
+- [tfplan/v2](/sentinel/features/terraform/tfplan-v2)
+- [tfconfig/v1](/sentinel/features/terraform/tfconfig-v1)
+- [tfconfig/v2](/sentinel/features/terraform/tfconfig-v2)
+- [tfstate/v1](/sentinel/features/terraform/tfstate-v1)
+- [tfstate/v2](/sentinel/features/terraform/tfstate-v2)
+
+-> **NOTE:** The above imports will only work with Terraform 0.12 and above,
+as they rely on the output from the `terraform show -json` command.
+
+It is recommended that the `/v2` suffixed imports are used, as they provide
+the best experience when interacting with the underlying data structures.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/features/terraform/tfconfig-v1.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/features/terraform/tfconfig-v1.mdx
new file mode 100644
index 0000000000..95fb5840ab
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/features/terraform/tfconfig-v1.mdx
@@ -0,0 +1,949 @@
+---
+page_title: tfconfig/v1 - terraform - Features
+sidebar_current: docs-features-terraform-config-v1
+description: The tfconfig/v1 import provides access to a Terraform configuration.
+layout: docs
+---
+
+# Import: tfconfig/v1
+
+~> **Warning:** The `tfconfig/v1` import is deprecated and will be permanently removed in August 2025.
+Use the [tfconfig/v2](/sentinel/docs/features/terraform/tfconfig-v2) import as soon as possible to avoid disruptions.
+The `tfconfig/v2` import offers improved functionality and is designed to better support your policy enforcement needs.
+
+The `tfconfig/v1` import provides access to a Terraform configuration.
+
+The Terraform configuration is the set of `*.tf` files that are used to
+describe the desired infrastructure state. Policies using the `tfconfig/v1`
+import can access all aspects of the configuration: providers, resources,
+data sources, modules, and variables.
+
+Some use cases for `tfconfig/v1` include:
+
+* **Organizational naming conventions**: requiring that configuration elements
+ are named in a way that conforms to some organization-wide standard.
+* **Required inputs and outputs**: organizations may require a particular set
+ of input variable names across all workspaces or may require a particular
+ set of outputs for asset management purposes.
+* **Enforcing particular modules**: organizations may provide a number of
+ "building block" modules and require that each workspace be built only from
+ combinations of these modules.
+* **Enforcing particular providers or resources**: an organization may wish to
+ require or prevent the use of providers and/or resources so that configuration
+ authors cannot use alternative approaches to work around policy
+ restrictions.
+
+Note with these use cases that this import is concerned with object _names_
+in the configuration. Since this is the configuration and not an invocation
+of Terraform, you can't see values for variables, the state, or the diff for
+a pending plan. If you want to write policy around expressions used
+within configuration blocks, you likely want to use the
+[`tfplan/v1`](/sentinel/features/terraform/tfplan-v1) import.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfconfig/v1" {
+ config = {
+ "plan": "./path/to/plan.json"
+ }
+}
+```
+
+## Namespace Overview
+
+The following is a tree view of the import namespace. For more detail on a
+particular part of the namespace, see below.
+
+-> **Note:** The root-level alias keys shown here (`data`, `modules`,
+`providers`, `resources`, and `variables`) are shortcuts to a [module
+namespace](#namespace-module) scoped to the root module. For more details, see
+the section on [root namespace aliases](#root-namespace-aliases).
+
+```
+tfconfig/v1
+├── module() (function)
+│ └── (module namespace)
+│ ├── data
+│ │ └── TYPE.NAME
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── provisioners
+│ │ └── NUMBER
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── type (string)
+│ ├── modules
+│ │ └── NAME
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ ├── source (string)
+│ │ └── version (string)
+│ ├──outputs
+│ │ └── NAME
+│ │ ├── depends_on (list of strings)
+│ │ ├── description (string)
+│ │ ├── sensitive (boolean)
+│ │ ├── references (list of strings)
+│ │ └── value (value)
+│ ├── providers
+│ │ └── TYPE
+│ │ ├── alias
+│ │ │ └── ALIAS
+│ │ │ ├── config (map of keys)
+│ │ | ├── references (map of keys)
+│ │ │ └── version (string)
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── version (string)
+│ ├── resources
+│ │ └── TYPE.NAME
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── provisioners
+│ │ └── NUMBER
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── type (string)
+│ └── variables
+│ └── NAME
+│ ├── default (value)
+│ └── description (string)
+├── module_paths ([][]string)
+│
+├── data (root module alias)
+├── modules (root module alias)
+├── outputs (root module alias)
+├── providers (root module alias)
+├── resources (root module alias)
+└── variables (root module alias)
+```
+
+### `references` Overview
+
+As an example, consider the following resource block:
+
+```hcl
+resource "local_file" "accounts" {
+ content = "some text"
+ filename = "${var.subdomain}.${var.domain}/accounts.txt"
+}
+```
+
+In this example, one might want to ensure `domain` and `subdomain` input
+variables are used within `filename` in this configuration.
+
+-> Any non-static values (such as interpolated strings) are not present within the
+configuration value and `references` should be used instead:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+# filename_references is a list of string values containing the references used in the expression
+filename_references = tfconfig.resources.local_file.accounts.references.filename
+
+main = rule {
+ filename_references contains "var.domain" and
+ filename_references contains "var.subdomain"
+}
+```
+
+The `references` value is present in any namespace where non-constant
+configuration values can be expressed. This is essentially every namespace
+which has a `config` value as well as the `outputs` namespace.
+
+-> **Note:** Remember, this import enforces policy around the literal Terraform
+configuration and not the final values as a result of invoking Terraform. If
+you want to write policy around the _result_ of expressions used within
+configuration blocks (for example, if you wanted to ensure the final value of
+`filename` above includes `accounts.txt`), you likely want to use the
+[`tfplan/v1`](/sentinel/features/terraform/tfplan-v1) import.
+
+## Namespace: Root
+
+The root-level namespace consists of the values and functions documented below.
+
+In addition to this, the root-level `data`, `modules`, `providers`, `resources`,
+and `variables` keys all alias to their corresponding namespaces within the
+[module namespace](#namespace-module).
+
+
+
+### Function: `module()`
+
+```
+module = func(ADDR)
+```
+
+* **Return Type:** A [module namespace](#namespace-module).
+
+The `module()` function in the [root namespace](#namespace-root) returns the
+[module namespace](#namespace-module) for a particular module address.
+
+The address must be a list and is the module address, split on the period (`.`),
+excluding the root module.
+
+Hence, a module with an address of simply `foo` (or `root.foo`) would be
+`["foo"]`, and a module within that (so address `foo.bar`) would be read as
+`["foo", "bar"]`.
+
+[`null`][ref-null] is returned if a module address is invalid, or if the module
+is not present in the configuration.
+
+[ref-null]: /sentinel/language/spec#null
+
+As an example, given the following module block:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+If the module contained the following content:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { subject.module(["foo"]).resources.null_resource.foo.config.triggers[0].foo is "bar" }
+```
+
+
+
+### Value: `module_paths`
+
+* **Value Type:** List of a list of strings.
+
+The `module_paths` value within the [root namespace](#namespace-root) is a list
+of all of the modules within the Terraform configuration.
+
+Modules not present in the configuration will not be present here, even if they
+are present in the diff or state.
+
+This data is represented as a list of a list of strings, with the inner list
+being the module address, split on the period (`.`).
+
+The root module is included in this list, represented as an empty inner list.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The value of `module_paths` would be:
+
+```
+[
+ [],
+ ["foo"],
+]
+```
+
+And the following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.module_paths contains ["foo"] }
+```
+
+#### Iterating Through Modules
+
+Iterating through all modules to find particular resources can be useful. This
+[example][iterate-over-modules] shows how to use `module_paths` with the
+[`module()` function](#function-module-) to find all resources of a
+particular type from all modules using the `tfplan` import. By changing `tfplan`
+in this function to `tfconfig`, you could make a similar function find all
+resources of a specific type in the Terraform configuration.
+
+[iterate-over-modules]: https://developer.hashicorp.com/terraform/cloud-docs/policy-enforcement/sentinel#sentinel-imports
+
+## Namespace: Module
+
+The **module namespace** can be loaded by calling [`module()`](#root-function-module)
+for a particular module.
+
+It can be used to load the following child namespaces:
+
+* `data` - Loads the [resource namespace](#namespace-resources-data-sources),
+ filtered against data sources.
+* `modules` - Loads the [module configuration
+ namespace](#namespace-module-configuration).
+* `outputs` - Loads the [output namespace](#namespace-outputs).
+* `providers` - Loads the [provider namespace](#namespace-providers).
+* `resources` - Loads the [resource
+ namespace](#namespace-resources-data-sources), filtered against resources.
+* `variables` - Loads the [variable namespace](#namespace-variables).
+
+### Root Namespace Aliases
+
+The root-level `data`, `modules`, `providers`, `resources`, and `variables` keys
+all alias to their corresponding namespaces within the module namespace, loaded
+for the root module. They are the equivalent of running `module([]).KEY`.
+
+
+
+## Namespace: Resources/Data Sources
+
+The **resource namespace** is a namespace _type_ that applies to both resources
+(accessed by using the `resources` namespace key) and data sources (accessed
+using the `data` namespace key).
+
+Accessing an individual resource or data source within each respective namespace
+can be accomplished by specifying the type and name, in the syntax
+`[resources|data].TYPE.NAME`.
+
+In addition, each of these namespace levels is a map, allowing you to filter
+based on type and name. Some examples of multi-level access are below:
+
+* To fetch all `aws_instance` resources within the root module, you can specify
+ `tfconfig.resources.aws_instance`. This would give you a map of resource
+ namespaces indexed from the names of each resource (`foo`, `bar`, and so
+ on).
+* To fetch all resources within the root module, irrespective of type, use
+ `tfconfig.resources`. This is indexed by type, as shown above with
+ `tfconfig.resources.aws_instance`, with names being the next level down.
+
+As an example, perhaps you wish to deny use of the `local_file` resource
+in your configuration. Consider the following resource block:
+
+```hcl
+resource "local_file" "foo" {
+ content = "foo!"
+ filename = "${path.module}/foo.bar"
+}
+```
+
+The following policy would fail:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.resources not contains "local_file" }
+```
+
+Further explanation of the namespace will be in the context of resources. As
+mentioned, when operating on data sources, use the same syntax, except with
+`data` in place of `resources`.
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+The `config` value within the [resource
+namespace](#namespace-resources-data-sources) is a map of key-value pairs that
+directly map to Terraform config keys and values.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#resources-value-references) should be used instead.
+
+As an example, consider the following resource block:
+
+```hcl
+resource "local_file" "accounts" {
+ content = "some text"
+ filename = "accounts.txt"
+}
+```
+
+In this example, one might want to access `filename` to validate that the correct
+file name is used. Given the above example, the following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule {
+ tfconfig.resources.local_file.accounts.config.filename is "accounts.txt"
+}
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [resource namespace](#namespace-resources-data-sources)
+contains the identifiers within non-constant expressions found in [`config`](#resources-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+
+
+### Value: `provisioners`
+
+* **Value Type:** List of [provisioner namespaces](#namespace-provisioners).
+
+The `provisioners` value within the [resource namespace](#namespace-resources)
+represents the [provisioners][ref-tf-provisioners] within a specific resource.
+
+Provisioners are listed in the order they were provided in the configuration
+file.
+
+While the `provisioners` value will be present within data sources, it will
+always be an empty map `null` (in Terraform 0.12) since data sources cannot
+actually have provisioners.
+
+The data within a provisioner can be inspected via the returned [provisioner
+namespace](#namespace-provisioners).
+
+[ref-tf-provisioners]: https://developer.hashicorp.com/terraform/language/resources/provisioners/syntax
+
+## Namespace: Provisioners
+
+The **provisioner namespace** represents the configuration for a particular
+[provisioner][ref-tf-provisioners] within a specific resource.
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+The `config` value within the [provisioner namespace](#namespace-provisioners)
+represents the values of the keys within the provisioner.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#provisioners-value-references) should be used instead.
+
+As an example, given the following resource block:
+
+```hcl
+resource "null_resource" "foo" {
+ # ...
+
+ provisioner "local-exec" {
+ command = "echo ${self.private_ip} > file.txt"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule {
+ tfconfig.resources.null_resource.foo.provisioners[0].config.command is "echo ${self.private_ip} > file.txt"
+}
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [provisioner namespace](#namespace-provisioners)
+contains the identifiers within non-constant expressions found in [`config`](#provisioners-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+
+
+### Value: `type`
+
+* **Value Type:** String.
+
+The `type` value within the [provisioner namespace](#namespace-provisioners)
+represents the type of the specific provisioner.
+
+As an example, in the following resource block:
+
+```hcl
+resource "null_resource" "foo" {
+ # ...
+
+ provisioner "local-exec" {
+ command = "echo ${self.private_ip} > file.txt"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.resources.null_resource.foo.provisioners[0].type is "local-exec" }
+```
+
+## Namespace: Module Configuration
+
+The **module configuration** namespace displays data on _module configuration_
+as it is given within a `module` block. This means that the namespace concerns
+itself with the contents of the declaration block (example: the `source`
+parameter and variable assignment keys), not the data within the module
+(example: any contained resources or data sources). For the latter, the module
+instance would need to be looked up with the [`module()`
+function](#root-function-module).
+
+
+
+### Value: `source`
+
+* **Value Type:** String.
+
+The `source` value within the [module configuration
+namespace](#namespace-module-configuration) represents the module source path as
+supplied to the module configuration.
+
+As an example, given the module declaration block:
+
+```hcl
+module "foo" {
+ source = "./foo"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.modules.foo.source is "./foo" }
+```
+
+
+
+### Value: `version`
+
+* **Value Type:** String.
+
+The `version` value within the [module configuration
+namespace](#namespace-module-configuration) represents the [version
+constraint][module-version-constraint] for modules that support it, such as
+modules within the [Terraform Module Registry][terraform-module-registry] or the
+[HCP Terraform private module registry][tfe-private-registry].
+
+[module-version-constraint]: https://developer.hashicorp.com/terraform/language/modules#module-versions
+
+[terraform-module-registry]: https://registry.terraform.io/
+
+[tfe-private-registry]: https://developer.hashicorp.com/terraform/cloud-docs/registry
+
+As an example, given the module declaration block:
+
+```hcl
+module "foo" {
+ source = "foo/bar"
+ version = "~> 1.2"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.modules.foo.version is "~> 1.2" }
+```
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#modules-value-references) should be used instead.
+
+The `config` value within the [module configuration
+namespace](#namespace-module-configuration) represents the values of the keys
+within the module configuration. This is every key within a module declaration
+block except [`source`](#modules-value-source) and [`version`](#modules-value-version), which
+have their own values.
+
+As an example, given the module declaration block:
+
+```hcl
+module "foo" {
+ source = "./foo"
+
+ bar = "baz"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.modules.foo.config.bar is "baz" }
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [module configuration namespace](#namespace-module-configuration)
+contains the identifiers within non-constant expressions found in [`config`](#modules-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+## Namespace: Outputs
+
+The **output namespace** represents _declared_ output data within a
+configuration. As such, configuration for the [`value`](#outputs-value-value) attribute
+will be in its raw form, and not yet interpolated. For fully interpolated output
+values, see the [`tfstate` import][ref-tfe-sentinel-tfstate].
+
+[ref-tfe-sentinel-tfstate]: /sentinel/features/terraform/tfstate-v1
+
+This namespace is indexed by output name.
+
+
+
+### Value: `depends_on`
+
+* **Value Type:** A list of strings.
+
+The `depends_on` value within the [output namespace](#namespace-outputs)
+represents any _explicit_ dependencies for this output. For more information,
+see the [depends_on output setting][ref-depends_on] within the general Terraform
+documentation.
+
+[ref-depends_on]: https://developer.hashicorp.com/terraform/language/values/outputs#depends_on
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ depends_on = ["null_resource.bar"]
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.depends_on[0] is "null_resource.bar" }
+```
+
+
+
+### Value: `description`
+
+* **Value Type:** String.
+
+The `description` value within the [output namespace](#namespace-outputs)
+represents the defined description for this output.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ description = "foobar"
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.description is "foobar" }
+```
+
+
+
+### Value: `sensitive`
+
+* **Value Type:** Boolean.
+
+The `sensitive` value within the [output namespace](#namespace-outputs)
+represents if this value has been marked as sensitive or not.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ sensitive = true
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { subject.outputs.id.sensitive }
+```
+
+
+
+### Value: `value`
+
+* **Value Type:** Any primitive type, list or map.
+
+The `value` value within the [output namespace](#namespace-outputs) represents
+the defined value for the output as declared in the configuration. Primitives
+will bear the implicit type of their declaration (string, int, float, or bool),
+and maps and lists will be represented as such.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#outputs-value-references) should be used instead.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ value = "foo"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.value is "foo" }
+```
+
+
+
+### Value: `references`
+
+* **Value Type:**. List of strings.
+
+The `references` value within the [output namespace](#namespace-outputs)
+contains the names of any referenced identifiers when [`value`](#outputs-value-value)
+is a non-constant expression.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.references contains "null_resource.foo.id" }
+```
+
+## Namespace: Providers
+
+The **provider namespace** represents data on the declared providers within a
+namespace.
+
+This namespace is indexed by provider type and _only_ contains data about
+providers when actually declared. If you are using a completely implicit
+provider configuration, this namespace will be empty.
+
+This namespace is populated based on the following criteria:
+
+* The top-level namespace [`config`](#providers-value-config) and
+ [`version`](#providers-value-version) values are populated with the configuration and
+ version information from the default provider (the provider declaration that
+ lacks an alias).
+* Any aliased providers are added as namespaces within the
+ [`alias`](#providers-value-alias) value.
+* If a module lacks a default provider configuration, the top-level `config` and
+ `version` values will be empty.
+
+
+
+### Value: `alias`
+
+* **Value Type:** A map of [provider namespaces](#namespace-providers), indexed
+ by alias.
+
+The `alias` value within the [provider namespace](#namespace-providers)
+represents all declared [non-default provider
+instances][ref-tf-provider-instances] for a specific provider type, indexed by
+their specific alias.
+
+[ref-tf-provider-instances]: https://developer.hashicorp.com/terraform/language/providers/configuration#alias-multiple-provider-configurations
+
+The return type is a provider namespace with the data for the instance in
+question loaded. The `alias` key will not be available within this namespace.
+
+As an example, given the following provider declaration block:
+
+```hcl
+provider "aws" {
+ alias = "east"
+ region = "us-east-1"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.providers.aws.alias.east.config.region is "us-east-1" }
+```
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#providers-value-references) should be used instead.
+
+The `config` value within the [provider namespace](#namespace-providers)
+represents the values of the keys within the provider's configuration, with the
+exception of the provider version, which is represented by the
+[`version`](#providers-value-version) value. [`alias`](#providers-value-alias) is also not included
+when the provider is aliased.
+
+As an example, given the following provider declaration block:
+
+```hcl
+provider "aws" {
+ region = "us-east-1"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.providers.aws.config.region is "us-east-1" }
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [provider namespace](#namespace-providers)
+contains the identifiers within non-constant expressions found in [`config`](#providers-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+
+
+### Value: `version`
+
+* **Value Type:** String.
+
+The `version` value within the [provider namespace](#namespace-providers)
+represents the explicit expected version of the supplied provider. This includes
+the pessimistic operator.
+
+As an example, given the following provider declaration block:
+
+```hcl
+provider "aws" {
+ version = "~> 1.34"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.providers.aws.version is "~> 1.34" }
+```
+
+## Namespace: Variables
+
+The **variable namespace** represents _declared_ variable data within a
+configuration. As such, static data can be extracted, such as defaults, but not
+dynamic data, such as the current value of a variable within a plan (although
+this can be extracted within the [`tfplan` import][ref-tfe-sentinel-tfplan]).
+
+[ref-tfe-sentinel-tfplan]: /sentinel/features/terraform/tfplan-v1
+
+This namespace is indexed by variable name.
+
+
+
+### Value: `default`
+
+* **Value Type:** Any primitive type, list, map, or `null`.
+
+The `default` value within the [variable namespace](#namespace-variables)
+represents the default for the variable as declared in the configuration.
+
+The actual value will be as configured. Primitives will bear the implicit type
+of their declaration (string, int, float, or bool), and maps and lists will be
+represented as such.
+
+If no default is present, the value will be [`null`][ref-sentinel-null] (not to
+be confused with [`undefined`][ref-sentinel-undefined]).
+
+[ref-sentinel-null]: /sentinel/language/spec#null
+
+[ref-sentinel-undefined]: /sentinel/language/undefined
+
+As an example, given the following variable blocks:
+
+```hcl
+variable "foo" {
+ default = "bar"
+}
+
+variable "number" {
+ default = 42
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+default_foo = rule { tfconfig.variables.foo.default is "bar" }
+default_number = rule { tfconfig.variables.number.default is 42 }
+
+main = rule { default_foo and default_number }
+```
+
+
+
+### Value: `description`
+
+* **Value Type:** String.
+
+The `description` value within the [variable namespace](#namespace-variables)
+represents the description of the variable, as provided in configuration.
+
+As an example, given the following variable block:
+
+```hcl
+variable "foo" {
+ description = "foobar"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.variables.foo.description is "foobar" }
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/features/terraform/tfconfig-v2.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/features/terraform/tfconfig-v2.mdx
new file mode 100644
index 0000000000..1fe4fff092
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/features/terraform/tfconfig-v2.mdx
@@ -0,0 +1,441 @@
+---
+page_title: tfconfig/v2 - terraform - Features
+sidebar_current: docs-features-terraform-tfconfig-v2
+description: The tfconfig/v2 import provides access to a Terraform configuration.
+layout: docs
+---
+
+# Import: tfconfig/v2
+
+The `tfconfig/v2` import provides access to a Terraform configuration.
+
+The Terraform configuration is the set of `*.tf` files that are used to
+describe the desired infrastructure state. Policies using the `tfconfig`
+import can access all aspects of the configuration: providers, resources,
+data sources, modules, and variables.
+
+Some use cases for `tfconfig` include:
+
+* **Organizational naming conventions**: requiring that configuration elements
+ are named in a way that conforms to some organization-wide standard.
+* **Required inputs and outputs**: organizations may require a particular set
+ of input variable names across all workspaces or may require a particular
+ set of outputs for asset management purposes.
+* **Enforcing particular modules**: organizations may provide a number of
+ "building block" modules and require that each workspace be built only from
+ combinations of these modules.
+* **Enforcing particular providers or resources**: an organization may wish to
+ require or prevent the use of providers and/or resources so that configuration
+ authors cannot use alternative approaches to work around policy
+ restrictions.
+
+The data in the `tfconfig/v2` import is sourced from the JSON configuration file
+that is generated by the [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) command. For more information on
+the file format, see the [JSON Output Format](https://developer.hashicorp.com/terraform/internals/json-format)
+page.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfconfig/v2" {
+ config = {
+ "plan": "./path/to/plan.json"
+ }
+}
+```
+
+## Import Overview
+
+The `tfconfig/v2` import is structured as a series of _collections_, keyed as a
+specific format, such as resource address, module address, or a
+specifically-formatted provider key.
+
+```
+tfconfig/v2
+├── strip_index() (function)
+├── providers
+│ └── (indexed by [module_address:]provider[.alias])
+│ ├── provider_config_key (string)
+│ ├── name (string)
+│ ├── full_name (string)
+│ ├── alias (string)
+│ ├── module_address (string)
+│ ├── config (block expression representation)
+│ └── version_constraint (string)
+├── resources
+│ └── (indexed by address)
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── provider_config_key (string)
+│ ├── provisioners (list)
+│ │ └── (ordered provisioners for this resource only)
+│ ├── config (block expression representation)
+│ ├── count (expression representation)
+│ ├── for_each (expression representation)
+│ └── depends_on (list of strings)
+├── provisioners
+│ └── (indexed by resource_address:index)
+│ ├── resource_address (string)
+│ ├── type (string)
+│ ├── index (string)
+│ └── config (block expression representation)
+├── variables
+│ └── (indexed by module_address:name)
+│ ├── module_address (string)
+│ ├── name (string)
+│ ├── default (value)
+│ └── description (string)
+├── outputs
+│ └── (indexed by module_address:name)
+│ ├── module_address (string)
+│ ├── name (string)
+│ ├── sensitive (boolean)
+│ ├── value (expression representation)
+│ ├── description (string)
+│ └── depends_on (list of strings)
+└── module_calls
+ └── (indexed by module_address:name)
+ ├── module_address (string)
+ ├── name (string)
+ ├── source (string)
+ ├── config (block expression representation)
+ ├── count (expression representation)
+ ├── depends_on (expression representation)
+ ├── for_each (expression representation)
+ └── version_constraint (string)
+```
+
+The collections are:
+
+* [`providers`](#the-providers-collection) - The configuration for all provider
+ instances across all modules in the configuration.
+* [`resources`](#the-resources-collection) - The configuration of all resources
+ across all modules in the configuration.
+* [`variables`](#the-variables-collection) - The configuration of all variable
+ definitions across all modules in the configuration.
+* [`outputs`](#the-outputs-collection) - The configuration of all output
+ definitions across all modules in the configuration.
+* [`module_calls`](#the-module_calls-collection) - The configuration of all module
+ calls (individual [`module`](/terraform/language/modules) blocks) across
+ all modules in the configuration.
+
+These collections are specifically designed to be used with the
+[`filter`](/sentinel/language/collection-operations#filter-expression)
+quantifier expression in Sentinel, so that one can collect a list of resources
+to perform policy checks on without having to write complex module or
+configuration traversal. As an example, the following code will return all
+`aws_instance` resource types within the configuration, regardless of what
+module they are in:
+
+```
+all_aws_instances = filter tfconfig.resources as _, r {
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+You can add specific attributes to the filter to narrow the search, such as the
+module address. The following code would return resources in a module named
+`foo` only:
+
+```
+all_aws_instances = filter tfconfig.resources as _, r {
+ r.module_address is "module.foo" and
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+### Address Differences Between `tfconfig`, `tfplan`, and `tfstate`
+
+This import deals with configuration before it is expanded into a
+resource graph by Terraform. As such, it is not possible to compute an index as
+the import is building its collections and computing addresses for resources and
+modules.
+
+As such, addresses found here may not always match the expanded addresses found
+in the [`tfplan/v2`](/sentinel/features/terraform/tfplan-v2) and [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2)
+imports, specifically when
+[`count`](https://developer.hashicorp.com/terraform/language/resources#count-multiple-resource-instances-by-count)
+and
+[`for_each`](https://developer.hashicorp.com/terraform/language/resources#for_each-multiple-resource-instances-defined-by-a-map-or-set-of-strings),
+are used.
+
+As an example, consider a resource named `null_resource.foo` with a count of `2`
+located in a module named `bar`. While there will possibly be entries in the
+other imports for `module.bar.null_resource.foo[0]` and
+`module.bar.null_resource.foo[1]`, in `tfconfig/v2`, there will only be a
+`module.bar.null_resource.foo`. As mentioned in the start of this section, this
+is because configuration actually _defines_ this scaling, whereas _expansion_
+actually happens when the resource graph is built, which happens as a natural
+part of the refresh and planning process.
+
+The `strip_index` helper function, found in this import, can assist in
+removing the indexes from addresses found in the `tfplan/v2` and `tfstate/v2`
+imports so that data from those imports can be used to reference data in this
+one.
+
+## The `strip_index` Function
+
+The `strip_index` helper function can be used to remove indexes from addresses
+found in [`tfplan/v2`](/sentinel/features/terraform/tfplan-v2) and [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2),
+by removing the indexes from each resource.
+
+This can be used to help facilitate cross-import lookups for data between plan,
+state, and config.
+
+```
+import "tfconfig/v2" as tfconfig
+import "tfplan/v2" as tfplan
+
+main = rule {
+ all filter tfplan.resource_changes as _, rc {
+ rc.mode is "managed" and
+ rc.type is "aws_instance"
+ } as _, rc {
+ tfconfig.resources[tfconfig.strip_index(rc.address)].config.ami.constant_value is "ami-abcdefgh012345"
+ }
+}
+```
+
+## Expression Representations
+
+Most collections in this import will have one of two kinds of _expression
+representations_. This is a verbose format for expressing a (parsed)
+configuration value independent of the configuration source code, which is not
+100% available to a policy check in HCP Terraform.
+
+```
+(expression representation)
+├── constant_value (value)
+└── references (list of strings)
+```
+
+There are two major parts to an expression representation:
+
+* Any _strictly constant value_ is expressed as an expression with a
+ `constant_value` field.
+* Any expression that requires some degree of evaluation to generate the final
+ value - even if that value is known at plan time - is not expressed in
+ configuration. Instead, any particular references that are made are added to
+ the `references` field. More details on this field can be found in the
+ [expression
+ representation](https://developer.hashicorp.com/terraform/internals/json-format#expression-representation)
+ section of the JSON output format documentation.
+
+For example, to determine if an output is based on a particular
+resource value, one could do:
+
+```
+import "tfconfig/v2" as tfconfig
+
+main = rule {
+ tfconfig.outputs["instance_id"].value.references is ["aws_instance.foo"]
+}
+```
+
+-> **Note:** The representation does not account for
+complex interpolations or other expressions that combine constants with other
+expression data. For example, the partially constant data in `"foo${var.bar}"` would be lost.
+
+### Block Expression Representation
+
+Expanding on the above, a multi-value expression representation (such as the
+kind found in a [`resources`](#the-resources-collection) collection element) is
+similar, but the root value is a keyed map of expression representations. This
+is repeated until a "scalar" expression value is encountered, ie: a field that
+is not a block in the resource's schema.
+
+```
+(block expression representation)
+└── (attribute key)
+ ├── (child block expression representation)
+ │ └── (...)
+ ├── constant_value (value)
+ └── references (list of strings)
+```
+
+As an example, one can validate expressions in an
+[`aws_instance`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance) resource using the
+following:
+
+```
+import "tfconfig/v2" as tfconfig
+
+main = rule {
+ tfconfig.resources["aws_instance.foo"].config.ami.constant_value is "ami-abcdefgh012345"
+}
+```
+
+Note that _nested blocks_, sometimes known as _sub-resources_, will be nested in
+configuration as as list of blocks (reflecting their ultimate nature as a list
+of objects). An example would be the `aws_instance` resource's
+[`ebs_block_device`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#ebs-ephemeral-and-root-block-devices) block:
+
+```
+import "tfconfig/v2" as tfconfig
+
+main = rule {
+ tfconfig.resources["aws_instance.foo"].config.ebs_block_device[0].volume_size < 10
+}
+```
+
+## The `providers` Collection
+
+The `providers` collection is a collection representing the configurations of
+all provider instances across all modules in the configuration.
+
+This collection is indexed by an opaque key. This is currently
+`module_address:provider.alias`, the same value as found in the
+`provider_config_key` field. `module_address` and the colon delimiter are
+omitted for the root module.
+
+The `provider_config_key` field is also found in the `resources` collection and
+can be used to locate a provider that belongs to a configured resource.
+
+The fields in this collection are as follows:
+
+* `provider_config_key` - The opaque configuration key, used as the index key.
+* `name` - The name of the provider, ie: `aws`.
+* `full_name` - The fully-qualified name of the provider, e.g. `registry.terraform.io/hashicorp/aws`.
+* `alias` - The alias of the provider, ie: `east`. Empty for a default provider.
+* `module_address` - The address of the module this provider appears in.
+* `config` - A [block expression
+ representation](#block-expression-representation) with provider configuration
+ values.
+* `version_constraint` - The defined version constraint for this provider.
+
+## The `resources` Collection
+
+The `resources` collection is a collection representing all of the resources
+found in all modules in the configuration.
+
+This collection is indexed by the resource address.
+
+The fields in this collection are as follows:
+
+* `address` - The resource address. This is the index of the collection.
+* `module_address` - The module address that this resource was found in.
+* `mode` - The resource mode, either `managed` (resources) or `data` (data
+ sources).
+* `type` - The type of resource, ie: `null_resource` in `null_resource.foo`.
+* `name` - The name of the resource, ie: `foo` in `null_resource.foo`.
+* `provider_config_key` - The opaque configuration key that serves as the index
+ of the [`providers`](#the-providers-collection) collection.
+* `provisioners` - The ordered list of provisioners for this resource. The
+ syntax of the provisioners matches those found in the
+ [`provisioners`](#the-provisioners-collection) collection, but is a list
+ indexed by the order the provisioners show up in the resource.
+* `config` - The [block expression
+ representation](#block-expression-representation) of the configuration values
+ found in the resource.
+* `count` - The [expression data](#expression-representations) for the `count`
+ value in the resource.
+* `for_each` - The [expression data](#expression-representations) for the
+ `for_each` value in the resource.
+* `depends_on` - The contents of the `depends_on` config directive, which
+ declares explicit dependencies for this resource.
+
+## The `provisioners` Collection
+
+The `provisioners` collection is a collection of all of the provisioners found
+across all resources in the configuration.
+
+While normally bound to a resource in an ordered fashion, this collection allows
+for the filtering of provisioners within a single expression.
+
+This collection is indexed with a key following the format
+`resource_address:index`, with each field matching their respective field in the
+particular element below:
+
+* `resource_address`: The address of the resource that the provisioner was found
+ in. This can be found in the [`resources`](#the-resources-collection)
+ collection.
+* `type`: The provisioner type, ie: `local_exec`.
+* `index`: The provisioner index as it shows up in the resource provisioner
+ order.
+* `config`: The [block expression
+ representation](#block-expression-representation) of the configuration values
+ in the provisioner.
+
+## The `variables` Collection
+
+The `variables` collection is a collection of all variables across all modules
+in the configuration.
+
+Note that this tracks variable definitions, not values. See the [`tfplan/v2`
+`variables` collection](/sentinel/features/terraform/tfplan-v2#the-variables-collection) for variable
+values set within a plan.
+
+This collection is indexed by the key format `module_address:name`, with each
+field matching their respective name below. `module_address` and the colon
+delimiter are omitted for the root module.
+
+* `module_address` - The address of the module the variable was found in.
+* `name` - The name of the variable.
+* `default` - The defined default value of the variable.
+* `description` - The description of the variable.
+
+## The `outputs` Collection
+
+The `outputs` collection is a collection of all outputs across all modules in
+the configuration.
+
+Note that this tracks variable definitions, not values. See the [`tfstate/v2`
+`outputs` collection](/sentinel/features/terraform/tfstate-v2#the-outputs-collection) for the final
+values of outputs set within a state. The [`tfplan/v2` `output_changes`
+collection](/sentinel/features/terraform/tfplan-v2#the-output_changes-collection) also contains a more
+complex collection of planned output changes.
+
+This collection is indexed by the key format `module_address:name`, with each
+field matching their respective name below. `module_address` and the colon
+delimiter are omitted for the root module.
+
+* `module_address` - The address of the module the output was found in.
+* `name` - The name of the output.
+* `sensitive` - Indicates whether or not the output was marked as
+ [`sensitive`](https://developer.hashicorp.com/terraform/language/values/outputs#sensitive-suppressing-values-in-cli-output).
+* `value` - An [expression representation](#expression-representations) for the output.
+* `description` - The description of the output.
+* `depends_on` - A list of resource names that the output depends on. These are
+ the hard-defined output dependencies as defined in the
+ [`depends_on`](https://developer.hashicorp.com/terraform/language/values/outputs#depends_on-explicit-output-dependencies)
+ field in an output declaration, not the dependencies that get derived from
+ natural evaluation of the output expression (these can be found in the
+ `references` field of the expression representation).
+
+## The `module_calls` Collection
+
+The `module_calls` collection is a collection of all module declarations at all
+levels within the configuration.
+
+Note that this is the
+[`module`](https://developer.hashicorp.com/terraform/language/modules#calling-a-child-module) stanza in
+any particular configuration, and not the module itself. Hence, a declaration
+for `module.foo` would actually be declared in the root module, which would be
+represented by a blank field in `module_address`.
+
+This collection is indexed by the key format `module_address:name`, with each
+field matching their respective name below. `module_address` and the colon
+delimiter are omitted for the root module.
+
+* `module_address` - The address of the module the declaration was found in.
+* `name` - The name of the module.
+* `source` - The contents of the `source` field.
+* `config` - A [block expression
+ representation](#block-expression-representation) for all parameter values
+ sent to the module.
+* `count` - An [expression representation](#expression-representations) for the
+ `count` field.
+* `depends_on`: An [expression representation](#expression-representations) for the
+ `depends_on` field.
+* `for_each` - An [expression representation](#expression-representations) for
+ the `for_each` field.
+* `version_constraint` - The string value found in the `version` field of the
+ module declaration.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/features/terraform/tfplan-v1.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/features/terraform/tfplan-v1.mdx
new file mode 100644
index 0000000000..1e9f9f1f9b
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/features/terraform/tfplan-v1.mdx
@@ -0,0 +1,614 @@
+---
+page_title: tfplan/v1 - terraform - Features
+sidebar_current: docs-features-terraform-tfplan-v1
+description: >-
+ The tfplan/v1 import provides access to a Terraform plan. A Terraform plan is the
+ file created as a result of `terraform plan` and is the input to `terraform
+ apply`. The plan represents the changes that Terraform needs to make to
+ infrastructure to reach the desired state represented by the configuration.
+layout: docs
+---
+
+# Import: tfplan/v1
+
+~> **Warning:** The `tfplan/v1` import is deprecated and will be permanently removed in August 2025.
+Use the updated [tfplan/v2](/sentinel/docs/features/terraform/tfplan-v2) import as soon as possible to avoid disruptions.
+The `tfplan/v2` import offers improved functionality and is designed to better support your policy enforcement needs.
+
+The `tfplan/v1` import provides access to a Terraform plan. A Terraform plan is the
+file created as a result of `terraform plan` and is the input to `terraform
+apply`. The plan represents the changes that Terraform needs to make to
+infrastructure to reach the desired state represented by the configuration.
+
+In addition to the diff data available in the plan, there is an
+[`applied`](#value-applied) state available that merges the plan with the state
+to create the planned state after apply.
+
+Finally, this import also allows you to access the configuration files and the
+Terraform state at the time the plan was run. See the section on [accessing a
+plan's state and configuration
+data](#accessing-a-plan-39-s-state-and-configuration-data) for more information.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan and schemas file.
+
+```hcl
+import "plugin" "tfplan/v1" {
+ config = {
+ "plan_path": "./path/to/plan.json",
+ "schemas_path": "./path/to/schemas.json"
+ }
+}
+```
+
+## Namespace Overview
+
+The following is a tree view of the import namespace. For more detail on a
+particular part of the namespace, see below.
+
+-> Note that the root-level alias keys shown here (`data`, `path`, and
+`resources`) are shortcuts to a [module namespace](#namespace-module) scoped to
+the root module. For more details, see the section on [root namespace
+aliases](#root-namespace-aliases).
+
+```
+tfplan/v1
+├── module() (function)
+│ └── (module namespace)
+│ ├── path ([]string)
+│ ├── data
+│ │ └── TYPE.NAME[NUMBER]
+│ │ ├── applied (map of keys)
+│ │ └── diff
+│ │ └── KEY
+│ │ ├── computed (bool)
+│ │ ├── new (string)
+│ │ └── old (string)
+│ └── resources
+│ └── TYPE.NAME[NUMBER]
+│ ├── applied (map of keys)
+│ ├── destroy (bool)
+│ ├── requires_new (bool)
+│ └── diff
+│ └── KEY
+│ ├── computed (bool)
+│ ├── new (string)
+│ └── old (string)
+├── module_paths ([][]string)
+├── terraform_version (string)
+├── variables (map of keys)
+│
+├── data (root module alias)
+├── path (root module alias)
+├── resources (root module alias)
+│
+├── config (tfconfig namespace alias)
+└── state (tfstate import alias)
+```
+
+## Namespace: Root
+
+The root-level namespace consists of the values and functions documented below.
+
+In addition to this, the root-level `data`, `path`, and `resources` keys alias
+to their corresponding namespaces or values within the [module
+namespace](#namespace-module).
+
+### Accessing a Plan's State and Configuration Data
+
+The `config` and `state` keys alias to the [`tfconfig`](/sentinel/features/terraform/tfconfig-v1) and
+[`tfstate`](/sentinel/features/terraform/tfstate-v1) namespaces, respectively, with the data sourced from
+the Terraform _plan_ (as opposed to actual configuration and state).
+
+-> Note that these aliases are not represented as maps. While they will appear
+empty when viewed as maps, the specific import namespace keys will still be
+accessible.
+
+### Function: `module()`
+
+```
+module = func(ADDR)
+```
+
+* **Return Type:** A [module namespace](#namespace-module).
+
+The `module()` function in the [root namespace](#namespace-root) returns the
+[module namespace](#namespace-module) for a particular module address.
+
+The address must be a list and is the module address, split on the period (`.`),
+excluding the root module.
+
+Hence, a module with an address of simply `foo` (or `root.foo`) would be
+`["foo"]`, and a module within that (so address `foo.bar`) would be read as
+`["foo", "bar"]`.
+
+[`null`](/sentinel/language/spec#null) is returned if a module address is
+invalid, or if the module is not present in the diff.
+
+As an example, given the following module block:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+If the module contained the following content:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.module(["foo"]).resources.null_resource.foo[0].applied.triggers.foo is "bar" }
+```
+
+### Value: `module_paths`
+
+* **Value Type:** List of a list of strings.
+
+The `module_paths` value within the [root namespace](#namespace-root) is a list
+of all of the modules within the Terraform diff for the current plan.
+
+Modules not present in the diff will not be present here, even if they are
+present in the configuration or state.
+
+This data is represented as a list of a list of strings, with the inner list
+being the module address, split on the period (`.`).
+
+The root module is included in this list, represented as an empty inner list, as
+long as there are changes.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The value of `module_paths` would be:
+
+```
+[
+ [],
+ ["foo"],
+]
+```
+
+And the following policy would evaluate to `true`:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.module_paths contains ["foo"] }
+```
+
+-> Note the above example only applies if the module is present in the diff.
+
+#### Iterating Through Modules
+
+Iterating through all modules to find particular resources can be useful. This
+[example][iterate-over-modules] shows how to use `module_paths` with the
+[`module()` function](#function-module-) to find all resources of a
+particular type from all modules that have pending changes using the `tfplan/v1`
+import.
+
+[iterate-over-modules]: https://developer.hashicorp.com/terraform/cloud-docs/policy-enforcement/sentinel#iterate-over-modules-and-find-resources
+
+### Value: `terraform_version`
+
+* **Value Type:** String.
+
+The `terraform_version` value within the [root namespace](#namespace-root)
+represents the version of Terraform used to create the plan. This can be used to
+enforce a specific version of Terraform in a policy check.
+
+As an example, the following policy would evaluate to `true`, as long as the
+plan was made with a version of Terraform in the 0.11.x series, excluding any
+pre-release versions (example: `-beta1` or `-rc1`):
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.terraform_version matches "^0\\.11\\.\\d+$" }
+```
+
+### Value: `variables`
+
+* **Value Type:** A string-keyed map of values.
+
+The `variables` value within the [root namespace](#namespace-root) represents
+all of the variables that were set when creating the plan. This will only
+contain variables set for the root module.
+
+Note that unlike the [`default`][import-tfconfig-variables-default] value in the
+[`tfconfig` variables namespace][import-tfconfig-variables], primitive values
+here are stringified, and type conversion will need to be performed to perform
+comparison for int, float, or boolean values. This only applies to variables
+that are primitives themselves and not primitives within maps and lists, which
+will be their original types.
+
+[import-tfconfig-variables-default]: /sentinel/features/terraform/tfconfig-v1#value-default
+
+[import-tfconfig-variables]: /sentinel/features/terraform/tfconfig-v1#namespace-variables
+
+If a default was accepted for the particular variable, the default value will be
+populated here.
+
+As an example, given the following variable blocks:
+
+```hcl
+variable "foo" {
+ default = "bar"
+}
+
+variable "number" {
+ default = 42
+}
+
+variable "map" {
+ default = {
+ foo = "bar"
+ number = 42
+ }
+}
+```
+
+The following policy would evaluate to `true`, if no values were entered to
+change these variables:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+default_foo = rule { tfplan.variables.foo is "bar" }
+default_number = rule { tfplan.variables.number is "42" }
+default_map_string = rule { tfplan.variables.map["foo"] is "bar" }
+default_map_int = rule { tfplan.variables.map["number"] is 42 }
+
+main = rule { default_foo and default_number and default_map_string and default_map_int }
+```
+
+## Namespace: Module
+
+The **module namespace** can be loaded by calling
+[`module()`](#function-module-) for a particular module.
+
+It can be used to load the following child namespaces, in addition to the values
+documented below:
+
+* `data` - Loads the [resource namespace](#namespace-resources-data-sources),
+ filtered against data sources.
+* `resources` - Loads the [resource
+ namespace](#namespace-resources-data-sources), filtered against resources.
+
+### Root Namespace Aliases
+
+The root-level `data` and `resources` keys both alias to their corresponding
+namespaces within the module namespace, loaded for the root module. They are the
+equivalent of running `module([]).KEY`.
+
+### Value: `path`
+
+* **Value Type:** List of strings.
+
+The `path` value within the [module namespace](#namespace-module) contains the
+path of the module that the namespace represents. This is represented as a list
+of strings.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The following policy would evaluate to `true` _only_ if the diff had changes for
+that module:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.module(["foo"]).path contains "foo" }
+```
+
+## Namespace: Resources/Data Sources
+
+The **resource namespace** is a namespace _type_ that applies to both resources
+(accessed by using the `resources` namespace key) and data sources (accessed
+using the `data` namespace key).
+
+Accessing an individual resource or data source within each respective namespace
+can be accomplished by specifying the type, name, and resource number (as if the
+resource or data source had a `count` value in it) in the syntax
+`[resources|data].TYPE.NAME[NUMBER]`. Note that NUMBER is always needed, even if
+you did not use `count` in the resource.
+
+In addition, each of these namespace levels is a map, allowing you to filter
+based on type and name.
+
+-> The (somewhat strange) notation here of `TYPE.NAME[NUMBER]` may imply that
+the inner resource index map is actually a list, but it's not - using the square
+bracket notation over the dotted notation (`TYPE.NAME.NUMBER`) is required here
+as an identifier cannot start with a number.
+
+Some examples of multi-level access are below:
+
+* To fetch all `aws_instance.foo` resource instances within the root module, you
+ can specify `tfplan.resources.aws_instance.foo`. This would then be indexed by
+ resource count index (`0`, `1`, `2`, and so on). Note that as mentioned above,
+ these elements must be accessed using square-bracket map notation (so `[0]`,
+ `[1]`, `[2]`, and so on) instead of dotted notation.
+* To fetch all `aws_instance` resources within the root module, you can specify
+ `tfplan.resources.aws_instance`. This would be indexed from the names of
+ each resource (`foo`, `bar`, and so on), with each of those maps containing
+ instances indexed by resource count index as per above.
+* To fetch all resources within the root module, irrespective of type, use
+ `tfplan.resources`. This is indexed by type, as shown above with
+ `tfplan.resources.aws_instance`, with names being the next level down, and so
+ on.
+
+~> When [resource targeting](https://developer.hashicorp.com/terraform/cli/commands/plan#resource-targeting) is
+ in effect, `tfplan.resources` will only include the resources specified as
+ targets for the run. This may lead to unexpected outcomes if a policy expects
+ a resource to be present in the plan.
+
+Further explanation of the namespace will be in the context of resources. As
+mentioned, when operating on data sources, use the same syntax, except with
+`data` in place of `resources`.
+
+### Value: `applied`
+
+* **Value Type:** A string-keyed map of values.
+
+The `applied` value within the [resource
+namespace](#namespace-resources-data-sources) contains a "predicted"
+representation of the resource's state post-apply. It's created by merging the
+pending resource's diff on top of the existing data from the resource's state
+(if any). The map is a complex representation of these values with data going
+as far down as needed to represent any state values such as maps, lists, and
+sets.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if the resource was in the diff:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].applied.triggers.foo is "bar" }
+```
+
+-> Note that some values will not be available in the `applied` state because
+they cannot be known until the plan is actually applied. In Terraform 0.11 or
+earlier, these values are represented by a placeholder (the UUID value
+`74D93920-ED26-11E3-AC10-0800200C9A66`) and in Terraform 0.12 or later they
+are `undefined`. **In either case**, you should instead use the
+[`computed`](#value-computed) key within the [diff
+namespace](#namespace-resource-diff) to determine that a computed value will
+exist.
+
+-> If a resource is being destroyed, its `applied` value is omitted from the
+namespace and trying to fetch it will return undefined.
+
+### Value: `diff`
+
+* **Value Type:** A map of [diff namespaces](#namespace-resource-diff).
+
+The `diff` value within the [resource
+namespace](#namespace-resources-data-sources) contains the diff for a particular
+resource. Each key within the map links to a [diff
+namespace](#namespace-resource-diff) for that particular key.
+
+Note that unlike the [`applied`](#value-applied) value, this map is not complex;
+the map is only 1 level deep with each key possibly representing a diff for a
+particular complex value within the resource.
+
+See the below section for more details on the diff namespace, in addition to
+usage examples.
+
+### Value: `destroy`
+
+* **Value Type:** Boolean.
+
+The `destroy` value within the [resource
+namespace](#namespace-resources-data-sources) is `true` if a resource is being
+destroyed for _any_ reason, including cases where it's being deleted as part of
+a resource re-creation, in which case [`requires_new`](#value-requires_new) will
+also be set.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` when `null_resource.foo` is being
+destroyed:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].destroy }
+```
+
+### Value: `requires_new`
+
+* **Value Type:** Boolean.
+
+The `requires_new` value within the [resource
+namespace](#namespace-resources-data-sources) is `true` if the resource is still
+present in the configuration, but must be replaced to satisfy its current diff.
+Whenever `requires_new` is `true`, [`destroy`](#value-destroy) is also `true`.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if one of the `triggers` in
+`null_resource.foo` was being changed:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].requires_new }
+```
+
+## Namespace: Resource Diff
+
+The **diff namespace** is a namespace that represents the diff for a specific
+attribute within a resource. For details on reading a particular attribute,
+see the [`diff`](#value-diff) value in the [resource
+namespace](#namespace-resources-data-sources).
+
+### Value: `computed`
+
+* **Value Type:** Boolean.
+
+The `computed` value within the [diff namespace](#namespace-resource-diff) is
+`true` if the resource key in question depends on another value that isn't yet
+known. Typically, that means the value it depends on belongs to a resource that
+either doesn't exist yet, or is changing state in such a way as to affect the
+dependent value so that it can't be known until the apply is complete.
+
+-> Keep in mind that when using `computed` with complex structures such as maps,
+lists, and sets, it's sometimes necessary to test the count attribute for the
+structure, versus a key within it, depending on whether or not the diff has
+marked the whole structure as computed. This is demonstrated in the example
+below. Count keys are `%` for maps, and `#` for lists and sets. If you are
+having trouble determining the type of specific field within a resource, contact
+the support team.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+
+resource "null_resource" "bar" {
+ triggers = {
+ foo_id = "${null_resource.foo.id}"
+ }
+}
+```
+
+The following policy would evaluate to `true`, if the `id` of
+`null_resource.foo` was currently not known, such as when the resource is
+pending creation, or is being deleted and re-created:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.bar[0].diff["triggers.%"].computed }
+```
+
+### Value: `new`
+
+* **Value Type:** String.
+
+The `new` value within the [diff namespace](#namespace-resource-diff) contains
+the new value of a changing attribute, _if_ the value is known at plan time.
+
+-> `new` will be an empty string if the attribute's value is currently unknown.
+For more details on detecting unknown values, see [`computed`](#value-computed).
+
+Note that this value is _always_ a string, regardless of the actual type of the
+value changing. [Type conversion][ref-sentinel-type-conversion] within policy
+may be necessary to achieve the comparison needed.
+
+[ref-sentinel-type-conversion]: https://docs.hashicorp.com/sentinel/language/values#type-conversion
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`, if the resource was in the diff
+and each of the concerned keys were changing to new values:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].diff["triggers.foo"].new is "bar" }
+```
+
+### Value: `old`
+
+* **Value Type:** String.
+
+The `old` value within the [diff namespace](#namespace-resource-diff) contains
+the old value of a changing attribute.
+
+Note that this value is _always_ a string, regardless of the actual type of the
+value changing. [Type conversion][ref-sentinel-type-conversion] within policy
+may be necessary to achieve the comparison needed.
+
+If the value did not exist in the previous state, `old` will always be an empty
+string.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "baz"
+ }
+}
+```
+
+If that resource was previously in config as:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].diff["triggers.foo"].old is "bar" }
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/features/terraform/tfplan-v2.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/features/terraform/tfplan-v2.mdx
new file mode 100644
index 0000000000..61fb7b2bf2
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/features/terraform/tfplan-v2.mdx
@@ -0,0 +1,402 @@
+---
+page_title: tfplan/v2 - terraform - Features
+sidebar_current: docs-features-terraform-tfplan-v2
+description: >-
+ The tfplan import provides access to a Terraform plan. A Terraform plan is the
+ file created as a result of `terraform plan` and is the input to `terraform
+ apply`. The plan represents the changes that Terraform needs to make to
+ infrastructure to reach the desired state represented by the configuration.
+layout: docs
+---
+
+# Import: tfplan/v2
+
+The `tfplan/v2` import provides access to a Terraform plan.
+
+A Terraform plan is the file created as a result of `terraform plan` and is the
+input to `terraform apply`. The plan represents the changes that Terraform needs
+to make to infrastructure to reach the desired state represented by the
+configuration.
+
+In addition to the diff data available in the plan, there is a "planned state"
+that is available through this import, via the
+[`planned_values`](#the-planned_values-collection) collection. This collection
+presents the Terraform state as how it might look after the plan data is
+applied, but is not guaranteed to be the final state.
+
+The data in the `tfplan/v2` import is sourced from the JSON configuration file
+that is generated by the [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) command. For more information on
+the file format, see the [JSON Output Format](https://developer.hashicorp.com/terraform/internals/json-format)
+page.
+
+The entirety of the JSON output file is exposed as a Sentinel map via the
+[`raw`](#the-raw-collection) collection. This allows direct, low-level access to
+the JSON data, but should only be used in complex situations where the
+higher-level collections do not serve the purpose.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfplan/v2" {
+ config = {
+ "plan_path": "./path/to/plan.json"
+ }
+}
+```
+
+## Import Overview
+
+The `tfplan/v2` import is structured as a series of _collections_, keyed as a
+specific format depending on the collection.
+
+```
+tfplan/v2
+├── terraform_version (string)
+├── variables
+│ └── (indexed by name)
+│ ├── name (string)
+│ └── value (value)
+├── planned_values
+│ ├── outputs (tfstate/v2 outputs representation)
+│ └── resources (tfstate/v2 resources representation)
+├── resource_changes
+│ └── (indexed by address[:deposed])
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── index (float (number) or string)
+│ ├── provider_name (string)
+│ ├── deposed (string)
+│ └── change (change representation)
+├── resource_drift
+│ └── (indexed by address[:deposed])
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── index (float (number) or string)
+│ ├── provider_name (string)
+│ ├── deposed (string)
+│ └── change (change representation)
+├── output_changes
+│ └── (indexed by name)
+│ ├── name (string)
+│ └── change (change representation)
+└── raw (map)
+```
+
+The collections are:
+
+* [`variables`](#the-variables-collection) - The values of variables that have
+ been set in the plan itself. This collection only contains variables set in
+ the root module.
+* [`planned_values`](#the-planned_values-collection) - The state representation
+ of _planned values_, or an estimation of what the state will look like after
+ the plan is applied.
+* [`resource_changes`](#the-resource_changes-and-resource_drift-collections) - The set of change
+ operations for resources and data sources within this plan.
+* [`resource_drift`](#the-resource_changes-and-resource_drift-collections) - A description of the
+ changes Terraform detected when it compared the most recent state to the prior saved state.
+* [`output_changes`](#the-output_changes-collection) - The changes to outputs
+ within this plan. This collection only contains outputs set in the root
+ module.
+* [`raw`](#the-raw-collection) - Access to the raw plan data.
+
+These collections are specifically designed to be used with the
+[`filter`](/sentinel/language/collection-operations#filter-expression)
+quantifier expression in Sentinel, so that one can collect a list of resources
+to perform policy checks on without having to write complex discovery code. As
+an example, the following code will return all `aws_instance` resource changes,
+across all modules in the plan:
+
+```
+all_aws_instances = filter tfplan.resource_changes as _, rc {
+ rc.mode is "managed" and
+ rc.type is "aws_instance"
+}
+```
+
+You can add specific attributes to the filter to narrow the search, such as the
+module address, or the operation being performed. The following code would
+return resources in a module named `foo` only, and further narrow the search
+down to only resources that were being created:
+
+```
+all_aws_instances = filter tfplan.resource_changes as _, rc {
+ rc.module_address is "module.foo" and
+ rc.mode is "managed" and
+ rc.type is "aws_instance" and
+ rc.change.actions is ["create"]
+}
+```
+
+### Change Representation
+
+Certain collections in this import contain a _change representation_, an object
+with details about changes to a particular entity, such as a resource (within
+the [`resource_changes`](#the-resource_changes-collection) collection), or
+output (within the [`output_changes`](#the-output_changes-collection)
+collection).
+
+```
+(change representation)
+├── actions (list)
+├── before (value, or map)
+├── after (value, or map)
+└── after_unknown (boolean, or map of booleans)
+```
+
+This change representation contains the following fields:
+
+* `actions` - A list of actions being carried out for this change. The order is
+ important, for example a regular replace operation is denoted by `["delete",
+ "create"]`, but a
+ [`create_before_destroy`](https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle#create_before_destroy)
+ resource will have an operation order of `["create", "delete"]`.
+* `before` - The representation of the resource data object value before the
+ action. For create-only actions, this is unset. For no-op actions, this value
+ will be identical with `after`.
+* `after` - The representation of the resource data object value after the
+ action. For delete-only actions, this is unset. For no-op actions, this value
+ will be identical with `before`. Note that unknown values will not show up in
+ this field.
+* `after_unknown` - A deep object of booleans that denotes any values that are
+ unknown in a resource. These values were previously referred to as "computed"
+ values. If the value cannot be found in this map, then its value should be
+ available within `after`, so long as the operation supports it.
+
+#### Actions
+
+As mentioned above, actions show up within the `actions` field of a change
+representation and indicate the type of actions being performed as part of the
+change, and the order that they are being performed in.
+
+The current list of actions are as follows:
+
+* `create` - The action will create the associated entity. Depending on the
+ order this appears in, the entity may be created alongside a copy of the
+ entity before replacing it.
+* `read` - The action will read the associated entity. In practice, seeing this
+ change type should be rare, as reads generally happen before a plan is
+ executed (usually during a refresh).
+* `update` - The action will update the associated entity in a way that alters its state
+ in some way.
+* `delete` - The action will remove the associated entity, deleting any
+ applicable state and associated real resources or infrastructure.
+* `no-op` - No action will be performed on the associated entity.
+
+The `actions` field is a list, as some real-world actions are actually a
+composite of more than one primitive action. At this point in time, this
+is generally only applicable to resource replacement, in which the following
+action orders apply:
+
+* **Normal replacement:** `["delete", "create"]` - Applies to default lifecycle
+ configurations.
+* **Create-before-destroy:** `["create", "delete"]` - Applies when
+ [`create_before_destroy`](https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle#create_before_destroy)
+ is used in a lifecycle configuration.
+
+Note that, in most situations, the plan will list all "changes", including no-op
+changes. This makes filtering on change type crucial to the accurate selection
+of data if you are concerned with the state change of a particular resource.
+
+To filter on a change type, use exact list comparison. For example, the
+following example from the [Import Overview](#import-overview) filters on
+exactly the resources being created _only_:
+
+```
+all_aws_instances = filter tfplan.resource_changes as _, rc {
+ rc.module_address is "module.foo" and
+ rc.mode is "managed" and
+ rc.type is "aws_instance" and
+ rc.change.actions is ["create"]
+}
+```
+
+#### `before`, `after`, and `after_unknown`
+
+The exact attribute changes for a particular operation are outlined in the
+`before` and `after` attributes. Depending on the entity being operated on, this
+will either be a map (as with
+[`resource_changes`](#the-resource_changes-collection)) or a singular value (as
+with [`output_changes`](#the-output_changes-collection)).
+
+What you can expect in these fields varies depending on the operation:
+
+* For fresh create operations, `before` will generally be `null`, and `after`
+ will contain the data you can expect to see after the change.
+* For full delete operations, this will be reversed - `before` will contain
+ data, and `after` will be `null`.
+* Update or replace operations will have data in both fields relevant to their
+ states before and after the operation.
+* No-op operations should have identical data in `before` and `after`.
+
+For resources, if a field cannot be found in `after`, it generally means one of
+two things:
+
+* The attribute does not exist in the resource schema. Generally, known
+ attributes that do not have a value will show up as `null` or otherwise empty
+ in `after`.
+* The attribute is _unknown_, that is, it was unable to be determined at plan
+ time and will only be available after apply-time values have been able to be
+ calculated.
+
+In the latter case, there should be a value for the particular attribute in
+`after_unknown`, which can be checked to assert that the value is indeed
+unknown, versus invalid:
+
+```
+import "tfplan/v2" as tfplan
+
+no_unknown_amis = rule {
+ all filter tfplan.resource_changes as _, rc {
+ rc.module_address is "module.foo" and
+ rc.mode is "managed" and
+ rc.type is "aws_instance" and
+ rc.change.actions is ["create"]
+ } as _, rc {
+ rc.change.after_unknown.ami else false is false
+ }
+}
+```
+
+For output changes, `after_unknown` will simply be `true` if the value won't be
+known until the plan is applied.
+
+## The `terraform_version` Value
+
+The top-level `terraform_version` value in this import gives the Terraform
+version that made the plan. This can be used to do version validation.
+
+```
+import "tfplan/v2" as tfplan
+import "strings"
+
+v = strings.split(tfplan.terraform_version, ".")
+version_major = int(v[1])
+version_minor = int(v[2])
+
+main = rule {
+ version_major is 12 and version_minor >= 19
+}
+```
+
+## The `variables` Collection
+
+The `variables` collection is a collection of the variables set in the root
+module when creating the plan.
+
+This collection is indexed on the name of the variable.
+
+The valid values are:
+
+* `name` - The name of the variable, also used as the collection key.
+* `value` - The value of the variable assigned during the plan.
+
+## The `planned_values` Collection
+
+The `planned_values` collection is a special collection in that it contains two
+fields that alias to state collections with the _planned_ state set. This is the
+best prediction of what the state will look like after the plan is executed.
+
+The two fields are:
+
+* `outputs` - The prediction of what output values will look like after the
+ state is applied. For more details on the structure of this collection, see
+ the [`outputs`](/sentinel/features/terraform/tfstate-v2#the-outputs-collection) collection in the
+ [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2) documentation.
+* `resources` - The prediction of what resource values will look like after the
+ state is applied. For more details on the structure of this collection, see
+ the [`resources`](/sentinel/features/terraform/tfstate-v2#the-resources-collection) collection in the
+ [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2) documentation.
+
+-> **NOTE:** Unknown values are omitted from the `planned_values` state
+representations, regardless of whether or not they existed before. Use
+[`resource_changes`](#the-resource_changes-collection) if awareness of unknown
+data is important.
+
+## The `resource_changes` and `resource_drift` Collections
+
+The `resource_changes` and `resource_drift` collections are a set of change operations for resources
+and data sources within this plan.
+
+The `resource_drift` collection provides a description of the changes Terraform detected
+when it compared the most recent state to the prior saved state.
+
+The `resource_changes` collection includes all resources that have been found in the configuration and state,
+regardless of whether or not they are changing.
+
+~> When [resource targeting](/terraform/cli/commands/plan#resource-targeting) is in effect, the `resource_changes` collection will only include the resources specified as targets for the run. This may lead to unexpected outcomes if a policy expects a resource to be present in the plan. To prohibit targeted runs altogether, ensure [`tfrun.target_addrs`](/terraform/cloud-docs/policy-enforcement/sentinel/import/tfrun#value-target_addrs) is undefined or empty.
+
+This collection is indexed on the complete resource address as the key. If
+`deposed` is non-empty, it is appended to the end, and may look something like
+`aws_instance.foo:deposed-abc123`.
+
+An element contains the following fields:
+
+* `address` - The absolute resource address - also the key for the collection's
+ index, if `deposed` is empty.
+
+* `module_address` - The module portion of the absolute resource address.
+
+* `mode` - The resource mode, either `managed` (resources) or `data` (data
+ sources).
+
+* `type` - The resource type, example: `aws_instance` for `aws_instance.foo`.
+
+* `name` - The resource name, example: `foo` for `aws_instance.foo`.
+
+* `index` - The resource index. Can be either a number or a string.
+
+* `provider_name` - The name of the provider this resource belongs to. This
+ allows the provider to be interpreted unambiguously in the unusual situation
+ where a provider offers a resource type whose name does not start with its own
+ name, such as the `googlebeta` provider offering `google_compute_instance`.
+
+ -> **Note:** Starting with Terraform 0.13, the `provider_name` field contains the
+ _full_ source address to the provider in the Terraform Registry. Example:
+ `registry.terraform.io/hashicorp/null` for the null provider.
+
+* `deposed` - An identifier used during replacement operations, and can be used
+ to identify the exact resource being replaced in state.
+
+* `change` - The data describing the change that will be made to this resource.
+ For more details, see [Change Representation](#change-representation).
+
+## The `output_changes` Collection
+
+The `output_changes` collection is a collection of the change operations for
+outputs within this plan.
+
+Only outputs for the root module are included.
+
+This collection is indexed by the name of the output. The fields in a collection
+value are below:
+
+* `name` - The name of the output, also the index key.
+* `change` - The data describing the change that will be made to this output.
+ For more details, see [Change Representation](#change-representation).
+
+## The `raw` Collection
+
+The `raw` collection exposes the raw, unprocessed plan data.
+
+This is the same data that is produced by [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) on the plan file for the run this
+policy check is attached to.
+
+Use of this data is only recommended in expert situations where the data the
+collections present may not exactly serve the needs of the policy. For more
+information on the file format, see the [JSON Output
+Format](https://developer.hashicorp.com/terraform/internals/json-format) page.
+
+-> **NOTE:** Although designed to be relatively stable, the actual makeup for
+the JSON output format is a Terraform CLI concern and as such not managed by
+Sentinel. Use at your own risk, follow the [Terraform CLI
+project](https://github.com/hashicorp/terraform), and watch the file format
+documentation for any changes.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/features/terraform/tfstate-v1.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/features/terraform/tfstate-v1.mdx
new file mode 100644
index 0000000000..7ff2690947
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/features/terraform/tfstate-v1.mdx
@@ -0,0 +1,556 @@
+---
+page_title: tfstate/v1 - terraform - Features
+sidebar_current: docs-features-terraform-tfstate-v1
+description: The tfstate/v1 import provides access to a Terraform state.
+layout: docs
+---
+
+# Import: tfstate/v1
+
+~> **Warning:** The `tfstate/v1` import is deprecated and will be permanently removed in August 2025.
+Use the updated [tfstate/v2](/sentinel/docs/features/terraform/tfstate-v2) import as soon as possible to avoid disruptions.
+The `tfstate/v2` import offers improved functionality and is designed to better support your policy enforcement needs.
+
+The `tfstate/v1` import provides access to the Terraform state.
+
+The _state_ is the data that Terraform has recorded about a workspace at a
+particular point in its lifecycle, usually after an apply. You can read more
+general information about how Terraform uses state [here][ref-tf-state].
+
+[ref-tf-state]: https://developer.hashicorp.com/terraform/language/state
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfstate/v1" {
+ config = {
+ "path": "./path/to/plan.json"
+ }
+}
+```
+
+## Namespace Overview
+
+The following is a tree view of the import namespace. For more detail on a
+particular part of the namespace, see below.
+
+-> Note that the root-level alias keys shown here (`data`, `outputs`, `path`,
+and `resources`) are shortcuts to a [module namespace](#namespace-module) scoped
+to the root module. For more details, see the section on [root namespace
+aliases](#root-namespace-aliases).
+
+```
+tfstate/v1
+├── module() (function)
+│ └── (module namespace)
+│ ├── path ([]string)
+│ ├── data
+│ │ └── TYPE.NAME[NUMBER]
+│ │ ├── attr (map of keys)
+│ │ ├── depends_on ([]string)
+│ │ ├── id (string)
+│ │ └── tainted (boolean)
+│ ├── outputs (root module only in TF 0.12 or later)
+│ │ └── NAME
+│ │ ├── sensitive (bool)
+│ │ ├── type (string)
+│ │ └── value (value)
+│ └── resources
+│ └── TYPE.NAME[NUMBER]
+│ ├── attr (map of keys)
+│ ├── depends_on ([]string)
+│ ├── id (string)
+│ └── tainted (boolean)
+│
+├── module_paths ([][]string)
+├── terraform_version (string)
+│
+├── data (root module alias)
+├── outputs (root module alias)
+├── path (root module alias)
+└── resources (root module alias)
+```
+
+## Namespace: Root
+
+The root-level namespace consists of the values and functions documented below.
+
+In addition to this, the root-level `data`, `outputs`, `path`, and `resources`
+keys alias to their corresponding namespaces or values within the [module
+namespace](#namespace-module).
+
+### Function: `module()`
+
+```
+module = func(ADDR)
+```
+
+* **Return Type:** A [module namespace](#namespace-module).
+
+The `module()` function in the [root namespace](#namespace-root) returns the
+[module namespace](#namespace-module) for a particular module address.
+
+The address must be a list and is the module address, split on the period (`.`),
+excluding the root module.
+
+Hence, a module with an address of simply `foo` (or `root.foo`) would be
+`["foo"]`, and a module within that (so address `foo.bar`) would be read as
+`["foo", "bar"]`.
+
+[`null`][ref-null] is returned if a module address is invalid, or if the module
+is not present in the state.
+
+[ref-null]: /sentinel/language/spec#null
+
+As an example, given the following module block:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+If the module contained the following content:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if the resource was present in
+the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.module(["foo"]).resources.null_resource.foo[0].attr.triggers.foo is "bar" }
+```
+
+### Value: `module_paths`
+
+* **Value Type:** List of a list of strings.
+
+The `module_paths` value within the [root namespace](#namespace-root) is a list
+of all of the modules within the Terraform state at plan-time.
+
+Modules not present in the state will not be present here, even if they are
+present in the configuration or the diff.
+
+This data is represented as a list of a list of strings, with the inner list
+being the module address, split on the period (`.`).
+
+The root module is included in this list, represented as an empty inner list, as
+long as it is present in state.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The value of `module_paths` would be:
+
+```
+[
+ [],
+ ["foo"],
+]
+```
+
+And the following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.module_paths contains ["foo"] }
+```
+
+-> Note the above example only applies if the module is present in the state.
+
+#### Iterating Through Modules
+
+Iterating through all modules to find particular resources can be useful. This
+[example][iterate-over-modules] shows how to use `module_paths` with the
+[`module()` function](#function-module-) to find all resources of a
+particular type from all modules using the `tfplan` import. By changing `tfplan`
+in this function to `tfstate`, you could make a similar function find all
+resources of a specific type in the current state.
+
+[iterate-over-modules]: https://developer.hashicorp.com/terraform/cloud-docs/policy-enforcement/sentinel#sentinel-imports
+
+### Value: `terraform_version`
+
+* **Value Type:** String.
+
+The `terraform_version` value within the [root namespace](#namespace-root)
+represents the version of Terraform in use when the state was saved. This can be
+used to enforce a specific version of Terraform in a policy check.
+
+As an example, the following policy would evaluate to `true` as long as the
+state was made with a version of Terraform in the 0.11.x series, excluding any
+pre-release versions (example: `-beta1` or `-rc1`):
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.terraform_version matches "^0\\.11\\.\\d+$" }
+```
+
+-> **NOTE:** This value is also available via the [`tfplan`](/sentinel/features/terraform/tfplan-v1)
+import, which will be more current when a policy check is run against a plan.
+It's recommended you use the value in `tfplan` until HCP Terraform
+supports policy checks in other stages of the workspace lifecycle. See the
+[`terraform_version`][import-tfplan-terraform-version] reference within the
+`tfplan` import for more details.
+
+[import-tfplan-terraform-version]: /sentinel/features/terraform/tfplan-v1#value-terraform_version
+
+## Namespace: Module
+
+The **module namespace** can be loaded by calling
+[`module()`](#function-module-) for a particular module.
+
+It can be used to load the following child namespaces, in addition to the values
+documented below:
+
+* `data` - Loads the [resource namespace](#namespace-resources-data-sources),
+ filtered against data sources.
+* `outputs` - Loads the [output namespace](#namespace-outputs), which supply the
+ outputs present in this module's state. Note that with Terraform 0.12 or
+ later, this value is only available for the root namespace.
+* `resources` - Loads the [resource
+ namespace](#namespace-resources-data-sources), filtered against resources.
+
+### Root Namespace Aliases
+
+The root-level `data`, `outputs`, and `resources` keys both alias to their
+corresponding namespaces within the module namespace, loaded for the root
+module. They are the equivalent of running `module([]).KEY`.
+
+### Value: `path`
+
+* **Value Type:** List of strings.
+
+The `path` value within the [module namespace](#namespace-module) contains the
+path of the module that the namespace represents. This is represented as a list
+of strings.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The following policy would evaluate to `true`, _only_ if the module was present
+in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.module(["foo"]).path contains "foo" }
+```
+
+## Namespace: Resources/Data Sources
+
+The **resource namespace** is a namespace _type_ that applies to both resources
+(accessed by using the `resources` namespace key) and data sources (accessed
+using the `data` namespace key).
+
+Accessing an individual resource or data source within each respective namespace
+can be accomplished by specifying the type, name, and resource number (as if the
+resource or data source had a `count` value in it) in the syntax
+`[resources|data].TYPE.NAME[NUMBER]`. Note that NUMBER is always needed, even if
+you did not use `count` in the resource.
+
+In addition, each of these namespace levels is a map, allowing you to filter
+based on type and name.
+
+-> The (somewhat strange) notation here of `TYPE.NAME[NUMBER]` may imply that
+the inner resource index map is actually a list, but it's not - using the square
+bracket notation over the dotted notation (`TYPE.NAME.NUMBER`) is required here
+as an identifier cannot start with number.
+
+Some examples of multi-level access are below:
+
+* To fetch all `aws_instance.foo` resource instances within the root module, you
+ can specify `tfstate.resources.aws_instance.foo`. This would then be indexed
+ by resource count index (`0`, `1`, `2`, and so on). Note that as mentioned
+ above, these elements must be accessed using square-bracket map notation (so
+ `[0]`, `[1]`, `[2]`, and so on) instead of dotted notation.
+* To fetch all `aws_instance` resources within the root module, you can specify
+ `tfstate.resources.aws_instance`. This would be indexed from the names of
+ each resource (`foo`, `bar`, and so on), with each of those maps containing
+ instances indexed by resource count index as per above.
+* To fetch all resources within the root module, irrespective of type, use
+ `tfstate.resources`. This is indexed by type, as shown above with
+ `tfstate.resources.aws_instance`, with names being the next level down, and so
+ on.
+
+Further explanation of the namespace will be in the context of resources. As
+mentioned, when operating on data sources, use the same syntax, except with
+`data` in place of `resources`.
+
+### Value: `attr`
+
+* **Value Type:** A string-keyed map of values.
+
+The `attr` value within the [resource
+namespace](#namespace-resources-data-sources) is a direct mapping to the state
+of the resource.
+
+The map is a complex representation of these values with data going as far down
+as needed to represent any state values such as maps, lists, and sets.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if the resource was in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.resources.null_resource.foo[0].attr.triggers.foo is "bar" }
+```
+
+### Value: `depends_on`
+
+* **Value Type:** A list of strings.
+
+The `depends_on` value within the [resource
+namespace](#namespace-resources-data-sources) contains the dependencies for the
+resource.
+
+This is a list of full resource addresses, relative to the module (example:
+`null_resource.foo`).
+
+As an example, given the following resources:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+
+resource "null_resource" "bar" {
+ # ...
+
+ depends_on = [
+ "null_resource.foo",
+ ]
+}
+```
+
+The following policy would evaluate to `true` if the resource was in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.resources.null_resource.bar[0].depends_on contains "null_resource.foo" }
+```
+
+### Value: `id`
+
+* **Value Type:** String.
+
+The `id` value within the [resource
+namespace](#namespace-resources-data-sources) contains the id of the resource.
+
+-> **NOTE:** The example below uses a _data source_ here because the
+[`null_data_source`][ref-tf-null-data-source] data source gives a static ID,
+which makes documenting the example easier. As previously mentioned, data
+sources share the same namespace as resources, but need to be loaded with the
+`data` key. For more information, see the
+[synopsis](#namespace-resources-data-sources) for the namespace itself.
+
+[ref-tf-null-data-source]: https://registry.terraform.io/providers/hashicorp/null/latest/docs/data-sources/data_source
+
+As an example, given the following data source:
+
+```hcl
+data "null_data_source" "foo" {
+ # ...
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.data.null_data_source.foo[0].id is "static" }
+```
+
+### Value: `tainted`
+
+* **Value Type:** Boolean.
+
+The `tainted` value within the [resource
+namespace](#namespace-resources-data-sources) is `true` if the resource is
+marked as tainted in Terraform state.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`, if the resource was marked as
+tainted in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.resources.null_resource.foo[0].tainted }
+```
+
+## Namespace: Outputs
+
+The **output namespace** represents all of the outputs present within a
+[module](#namespace-module). Outputs are present in a state if they were saved
+during a previous apply, or if they were updated with known values during the
+pre-plan refresh.
+
+**With Terraform 0.11 or earlier** this can be used to fetch both the outputs
+of the root module, and the outputs of any module in the state below the root.
+This makes it possible to see outputs that have not been threaded to the root
+module.
+
+**With Terraform 0.12 or later** outputs are available in the top-level (root
+module) namespace only and not accessible within submodules.
+
+This namespace is indexed by output name.
+
+### Value: `sensitive`
+
+* **Value Type:** Boolean.
+
+The `sensitive` value within the [output namespace](#namespace-outputs) is
+`true` when the output has been [marked as sensitive][ref-tf-sensitive-outputs].
+
+[ref-tf-sensitive-outputs]: https://developer.hashicorp.com/terraform/language/values/outputs#sensitive-suppressing-values-in-cli-output
+
+As an example, given the following output:
+
+```hcl
+output "foo" {
+ sensitive = true
+ value = "bar"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.outputs.foo.sensitive }
+```
+
+### Value: `type`
+
+* **Value Type:** String.
+
+The `type` value within the [output namespace](#namespace-outputs) gives the
+output's type. This will be one of `string`, `list`, or `map`. These are
+currently the only types available for outputs in Terraform.
+
+As an example, given the following output:
+
+```hcl
+output "string" {
+ value = "foo"
+}
+
+output "list" {
+ value = [
+ "foo",
+ "bar",
+ ]
+}
+
+output "map" {
+ value = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+type_string = rule { tfstate.outputs.string.type is "string" }
+type_list = rule { tfstate.outputs.list.type is "list" }
+type_map = rule { tfstate.outputs.map.type is "map" }
+
+main = rule { type_string and type_list and type_map }
+```
+
+### Value: `value`
+
+* **Value Type:** String, list, or map.
+
+The `value` value within the [output namespace](#namespace-outputs) is the value
+of the output in question.
+
+Note that the only valid primitive output type in Terraform is currently a
+string, which means that any int, float, or boolean value will need to be
+converted before it can be used in comparison. This does not apply to primitives
+within maps and lists, which will be their original types.
+
+As an example, given the following output blocks:
+
+```hcl
+output "foo" {
+ value = "bar"
+}
+
+output "number" {
+ value = "42"
+}
+
+output "map" {
+ value = {
+ foo = "bar"
+ number = 42
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+value_foo = rule { tfstate.outputs.foo.value is "bar" }
+value_number = rule { int(tfstate.outputs.number.value) is 42 }
+value_map_string = rule { tfstate.outputs.map.value["foo"] is "bar" }
+value_map_int = rule { tfstate.outputs.map.value["number"] is 42 }
+
+main = rule { value_foo and value_number and value_map_string and value_map_int }
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/features/terraform/tfstate-v2.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/features/terraform/tfstate-v2.mdx
new file mode 100644
index 0000000000..9b29aa2c51
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/features/terraform/tfstate-v2.mdx
@@ -0,0 +1,176 @@
+---
+page_title: tfstate/v2 - terraform - Features
+sidebar_current: docs-features-terraform-tfstate-v2
+description: The tfstate/v2 import provides access to a Terraform state.
+layout: docs
+---
+
+# Import: tfstate/v2
+
+The `tfstate/v2` import provides access to a Terraform state.
+
+The _state_ is the data that Terraform has recorded about a workspace at a
+particular point in its lifecycle, usually after an apply. You can read more
+general information about how Terraform uses state
+[here](https://developer.hashicorp.com/terraform/language/state).
+
+The data in the `tfstate/v2` import is sourced from the JSON configuration file
+that is generated by the [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) command. For more information on
+the file format, see the [JSON Output Format](https://developer.hashicorp.com/terraform/internals/json-format)
+page.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfstate/v2" {
+ config = {
+ "path": "./path/to/plan.json"
+ }
+}
+```
+
+## Import Overview
+
+The `tfstate/v2` import is structured as currently two _collections_, keyed in
+resource address and output name, respectively.
+
+```
+(tfstate/v2)
+├── terraform_version (string)
+├── resources
+│ └── (indexed by address)
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── index (float (number) or string)
+│ ├── provider_name (string)
+│ ├── values (map)
+│ ├── depends_on (list of strings)
+│ ├── tainted (boolean)
+│ └── deposed_key (string)
+└── outputs
+ └── (indexed by name)
+ ├── name (string)
+ ├── sensitive (boolean)
+ └── value (value)
+```
+
+The collections are:
+
+* [`resources`](#the-resources-collection) - The state of all resources across
+ all modules in the state.
+* [`outputs`](#the-outputs-collection) - The state of all outputs from the root module in the state.
+
+These collections are specifically designed to be used with the
+[`filter`](/sentinel/language/collection-operations#filter-expression)
+quantifier expression in Sentinel, so that one can collect a list of resources
+to perform policy checks on without having to write complex module traversal. As
+an example, the following code will return all `aws_instance` resource types
+within the state, regardless of what module they are in:
+
+```
+all_aws_instances = filter tfstate.resources as _, r {
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+You can add specific attributes to the filter to narrow the search, such as the
+module address. The following code would return resources in a module named
+`foo` only:
+
+```
+all_aws_instances = filter tfstate.resources as _, r {
+ r.module_address is "module.foo" and
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+## The `terraform_version` Value
+
+The top-level `terraform_version` value in this import gives the Terraform
+version that recorded the state. This can be used to do version validation.
+
+```
+import "tfstate/v2" as tfstate
+import "strings"
+
+v = strings.split(tfstate.terraform_version, ".")
+version_major = int(v[1])
+version_minor = int(v[2])
+
+main = rule {
+ version_major is 12 and version_minor >= 19
+}
+```
+
+## The `resources` Collection
+
+The `resources` collection is a collection representing all of the resources in
+the state, across all modules.
+
+This collection is indexed on the complete resource address as the key.
+
+An element in the collection has the following values:
+
+* `address` - The absolute resource address - also the key for the collection's
+ index.
+
+* `module_address` - The address portion of the absolute resource address.
+
+* `mode` - The resource mode, either `managed` (resources) or `data` (data
+ sources).
+
+* `type` - The resource type, example: `aws_instance` for `aws_instance.foo`.
+
+* `name` - The resource name, example: `foo` for `aws_instance.foo`.
+
+* `index` - The resource index. Can be either a number or a string.
+
+* `provider_name` - The name of the provider this resource belongs to. This
+ allows the provider to be interpreted unambiguously in the unusual situation
+ where a provider offers a resource type whose name does not start with its own
+ name, such as the `googlebeta` provider offering `google_compute_instance`.
+
+ -> **Note:** Starting with Terraform 0.13, the `provider_name` field contains the
+ _full_ source address to the provider in the Terraform Registry. Example:
+ `registry.terraform.io/hashicorp/null` for the null provider.
+
+* `values` - An object (map) representation of the attribute values of the
+ resource, whose structure depends on the resource type schema. When accessing
+ proposed state through the [`planned_values`](/sentinel/features/terraform/tfplan-v2#the-planned_values-collection)
+ collection of the tfplan/v2 import, unknown values will be omitted.
+
+* `depends_on` - The addresses of the resources that this resource depends on.
+
+* `tainted` - `true` if the resource has been explicitly marked as
+ [tainted](https://developer.hashicorp.com/terraform/cli/commands/taint) in the state.
+
+* `deposed_key` - Set if the resource has been marked deposed and will be
+ destroyed on the next apply. This matches the deposed field in the
+ [`resource_changes`](/sentinel/features/terraform/tfplan-v2#the-resource_changes-collection)
+ collection in the [`tfplan/v2`](/sentinel/features/terraform/tfplan-v2) import.
+
+## The `outputs` Collection
+
+The `outputs` collection is a collection of outputs from the root module of the
+state.
+
+Note that no child modules are included in this output set, and there is no way
+to fetch child module output values. This is to encourage the correct flow of
+outputs to the recommended root consumption level.
+
+The collection is indexed on the output name, with the following fields:
+
+* `name`: The name of the output, also the collection key.
+* `sensitive`: Whether or not the value was marked as
+ [sensitive](https://developer.hashicorp.com/terraform/language/values/outputs#sensitive-suppressing-values-in-cli-output)
+ in
+ configuration.
+* `value`: The value of the output.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/functions/append.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/functions/append.mdx
new file mode 100644
index 0000000000..81395154f6
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/functions/append.mdx
@@ -0,0 +1,46 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Append'
+sidebar_current: docs-funcs-append
+description: The built-in function `append` appends a value to the end of a list.
+layout: docs
+---
+
+# Builtin Function: append
+
+The built-in function `append` appends a value to the end of a list. The list
+is modified in-place. The return value will always be [undefined](/sentinel/language/undefined).
+
+`append` called on any value other than a list or undefined will result
+in an immediate error.
+
+Examples:
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+### Why does this function return undefined?
+
+This function returns `undefined` to avoid unintended side effects of the function in the context
+of a [rule](/sentinel/language/rules).
+
+For example, if `append` returned the list value as a result, the following policy would depend
+on the order in which rules are referenced:
+
+```sentinel
+list = []
+a = rule { append(list, 1) contains 1 }
+b = rule { append(list, 2) contains 2 }
+
+// Scenario 1: list becomes [1, 2]
+main = rule { a and b }
+
+// Scenario 2: list becomes [2, 1]
+main = rule { b and a }
+```
+
+This sort of side effect behavior introduces complex and difficult to track logical errors in programs.
+As a result, functions like this return `undefined` and modify values in-place.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/functions/compare.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/functions/compare.mdx
new file mode 100644
index 0000000000..ed55775454
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/functions/compare.mdx
@@ -0,0 +1,42 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Compare'
+sidebar_current: docs-funcs-compare
+description: The built-in function `compare` compares two values.
+layout: docs
+---
+
+# Builtin Function: compare
+
+**_compare(value1, value2)_**
+
+The built-in function `compare` compares two values. The only valid types that
+can be provided are integers, floats or strings. Strings are compared according
+to lexicographic ordering; which is comparable to alphabetical
+ordering, but also takes into account symbols.
+
+The following table provides an overview of the possible return values:
+
+| Result | Description |
+|--------|-------------|
+| -1 | `value1` is less than `value2` |
+| 0 | `value1` is equal to `value2` |
+| +1 | `value1` is greater than `value2` |
+
+## Examples
+
+```sentinel
+// ints
+compare(1, 4) // -1
+compare(1, 4) // 0
+compare(4, 1) // +1
+
+// floats
+compare(1.0, 4.0) // -1
+compare(1.0, 4.0) // 0
+compare(4.0, 1.0) // +1
+
+// strings
+compare("apple", "banana") // -1
+compare("apple", "apple") // 0
+compare("banana", "apple") // +1
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/functions/delete.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/functions/delete.mdx
new file mode 100644
index 0000000000..3b60803997
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/functions/delete.mdx
@@ -0,0 +1,25 @@
+---
+page_title: 'Sentinel Language - Builtin Function: delete'
+sidebar_current: docs-funcs-delete
+description: The built-in function `delete` deletes an element from a map.
+layout: docs
+---
+
+# Builtin Function: delete
+
+The built-in function `delete` deletes an element from a map.
+
+If the element to delete does not exist, the map is left unchanged.
+Calling `delete` on a value that is not a map or
+[undefined](/sentinel/language/undefined)
+is an immediate error. The result of `delete` is always `undefined`.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/functions/error.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/functions/error.mdx
new file mode 100644
index 0000000000..96d253b662
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/functions/error.mdx
@@ -0,0 +1,15 @@
+---
+page_title: 'Sentinel Language - Builtin Function: error'
+sidebar_current: docs-funcs-error
+description: >-
+ The built-in function `error` is used to exit immediately with an error
+ message.
+layout: docs
+---
+
+# Builtin Function: error
+
+The built-in function `error` is used to exit immediately with an error message.
+
+Please see the [page on errors](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/functions/index.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/functions/index.mdx
new file mode 100644
index 0000000000..4e12143ad0
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/functions/index.mdx
@@ -0,0 +1,13 @@
+---
+page_title: Sentinel Language - Builtin Functions
+sidebar_current: docs-funcs
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Language: Builtin Functions
+
+Builtin functions are
+[functions](/sentinel/language/functions)
+that are available in all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in function.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/functions/keys.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/functions/keys.mdx
new file mode 100644
index 0000000000..a96cafe897
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/functions/keys.mdx
@@ -0,0 +1,22 @@
+---
+page_title: 'Sentinel Language - Builtin Function: keys'
+sidebar_current: docs-funcs-keys
+description: The built-in function `keys` returns the keys of a map.
+layout: docs
+---
+
+# Builtin Function: keys
+
+The built-in function `keys` returns the keys of a map.
+
+`keys` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned keys are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/functions/length.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/functions/length.mdx
new file mode 100644
index 0000000000..8b874b14c4
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/functions/length.mdx
@@ -0,0 +1,23 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Length'
+sidebar_current: docs-funcs-length
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Builtin Function: length
+
+The built-in function `length` returns the length of a collection or string.
+
+The length of a string is the number of bytes in the string. It is not
+necessarilly the number of characters. The length of a collection is the
+number of elements in that collection. The length of
+[undefined](/sentinel/language/undefined) is `undefined`.
+
+Examples:
+
+```sentinel
+length("foo") // 3
+length([1, 2, 3, 4]) // 4
+length({ "key": "value" }) // 1
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/functions/print.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/functions/print.mdx
new file mode 100644
index 0000000000..bcf441d09d
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/functions/print.mdx
@@ -0,0 +1,13 @@
+---
+page_title: 'Sentinel Language - Builtin Function: print'
+sidebar_current: docs-funcs-print
+description: The built-in function `print` is used for logging and debugging.
+layout: docs
+---
+
+# Builtin Function: print
+
+The built-in function `print` is used for logging and debugging.
+
+Please see the [page on logging](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/functions/range.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/functions/range.mdx
new file mode 100644
index 0000000000..0f86bfc402
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/functions/range.mdx
@@ -0,0 +1,49 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Range'
+sidebar_current: docs-funcs-range
+description: The built-in function `range` returns a list of numbers in a range.
+layout: docs
+---
+
+# Builtin Function: range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it
+defaults to `1`.
+
+Negative steps are permitted and count down from `start` towards `end`, instead
+of up.
+
+The range ends when the next value given by the sum of the last value and `step`
+would exceed `end`, regardless of if it has been reached.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4] - starts at 1 instead of 0
+range(1, 5, 2) // [1,3] - 3+2 does not satisfy r[-1] < end
+range(0, -3, -1) // [0,-1,-2] - example of negative range
+```
+
+Impossible ranges, or ranges where `start` will never reach `end` given `step`,
+yield an empty list.
+
+```
+range(1, 1) // [] - already starting at 1
+range(0, 1, -1) // [] - negative step will never reach 1
+```
+
+Supplying an undefined value to any argument of `range` yields an undefined
+value back.
+
+A range with a zero step is a runtime error.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/functions/values.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/functions/values.mdx
new file mode 100644
index 0000000000..8f98d3ab56
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/functions/values.mdx
@@ -0,0 +1,22 @@
+---
+page_title: 'Sentinel Language - Builtin Function: values'
+sidebar_current: docs-funcs-values
+description: The built-in function `values` returns the values of a map.
+layout: docs
+---
+
+# Builtin Function: values
+
+The built-in function `values` returns the values of a map.
+
+`values` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned values are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+values(data) // [3, 2]
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/imports/base64.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/imports/base64.mdx
new file mode 100644
index 0000000000..4e58429268
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/imports/base64.mdx
@@ -0,0 +1,46 @@
+---
+page_title: 'Import: base64'
+sidebar_current: docs-imports-base64
+description: The base64 import enables a Sentinel policy to encode and decode Base64 values.
+layout: docs
+---
+
+# Import: base64
+
+The base64 import enables a Sentinel policy to encode and decode Base64 values.
+
+### base64.encode(str)
+
+Encodes the string `str` into a Base64 encoded string, using the standard
+encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-4).
+
+```sentinel
+enc = base64.encode("foo") // "Zm9v"
+```
+
+### base64.decode(str)
+
+Decodes the Base64 encoded string `str`, returning the string result,
+using the standard encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-4).
+
+```sentinel
+dec = base64.decode("Zm9v") // "foo"
+```
+
+### base64.urlencode(str)
+
+Encodes the string `str` into a Base64 encoded string, using the alternate
+encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-5).
+
+```sentinel
+enc = base64.urlencode("foo") // "Zm9v"
+```
+
+### base64.urldecode(str)
+
+Decodes the Base64 encoded string `str`, returning the string result, using the
+alternate encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-5).
+
+```sentinel
+dec = base64.urldecode("Zm9v") // "foo"
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/imports/collection/index.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/imports/collection/index.mdx
new file mode 100644
index 0000000000..ae6db20bbb
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/imports/collection/index.mdx
@@ -0,0 +1,207 @@
+---
+page_title: 'Import: collection'
+sidebar_current: docs-imports-collection
+description: The collection import provides useful helpers for working with maps and lists.
+layout: docs
+---
+
+# Import: collection
+
+The `collection` import provides helpers for working with [maps](/sentinel/language/maps) and [lists](/sentinel/language/lists).
+
+## filter
+
+**_filter(items, predicate)_**
+
+Calls [predicate](#predicates) for each element in collection, returning a list of elements that the
+predicate __does__ return true for.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| items | the list or map to perform the filter against. |
+| predicate | a [predicate](#predicates) function to call for each element in the collection. A response of `true` will add the element to the result list. |
+
+### Examples
+
+Return all even numbers:
+
+```sentinel playground
+import "collection"
+
+items = [2, 3, 4, 5, 6, 7, 8]
+result = collection.filter(items, func(el) {
+ return el % 2 is 0
+})
+main = result is [2, 4, 6, 8]
+```
+
+## find
+
+**_find(items, predicate)_**
+
+Find and return an element within a collection according to the provided [predicate](#predicates). If nothing is found, returns [undefined](/sentinel/language/undefined).
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| items | the list or map to perform the find against. |
+| predicate | a [predicate](#predicates) function to call for each element in the collection. A response of `true` will return the element and complete the find. |
+
+### Examples
+
+Find an element in a collection based on its value:
+
+```sentinel playground
+import "collection"
+
+items = ["foo", "bar", "qux"]
+item = collection.find(items, func(el) {
+ return el is "bar"
+})
+main = item is "bar"
+```
+
+Find an element in a collection based on its key:
+
+```sentinel playground
+import "collection"
+
+items = {"foo": 2, "bar": 4}
+item = collection.find(items, func(el, key) {
+ return key is "bar"
+})
+main = item is 4
+```
+
+## matches
+
+**_matches(items, partial)_**
+
+Compare each element in a collection against a partial map using deep comparison. Returns
+the list of elements that returned true for the partial comparison. If no matches are found, returns an empty list.
+
+### Arguments
+
+| Name | Description |
+|---------|-------------|
+| items | the list or map to perform the match against. |
+| partial | the map used for partial deep comparison against each element in the collection. |
+
+### Examples
+
+Return all items that contain `{"foo": {"bar": "wip"}}`:
+
+```sentinel playground
+import "collection"
+
+items = [
+ # This item should match
+ {
+ "foo": { "bar": "wip"},
+ "baz": "qux",
+ },
+ # This item will not match
+ {
+ "foo": "bar",
+ "baz": "bar",
+ },
+]
+result = collection.matches(items, {"foo": {"bar": "wip"}})
+main = result is [{"foo": {"bar": "wip"}, "baz": "qux"}]
+```
+
+## reduce
+
+**_reduce(items, accumulator[, initial])_**
+
+Call an accumulator function for each element in collection, supplying the
+previous accumulated value as the accumulation parameter.
+
+### Arguments
+
+| Name | Description |
+|-------------|-------------|
+| items | the list or map to perform the reduce against. |
+| accumulator | a function that is called for each element in the collection, used to accumulate the value. The first argument is the current accumulated value, the remaining arguments use the same rules as [predicate](#predicates) functions. |
+| initial | the initial value to use. It is an optional argument, and if not provided the first element in the collection is used as the initial value. |
+
+### Examples
+
+Reduce the collection by adding each element together:
+
+```sentinel playground
+import "collection"
+
+items = [1, 2, 3, 4, 5]
+result = collection.reduce(items, func(acc, el) {
+ return acc + el
+}, 0)
+main = result is 15
+```
+
+## reject
+
+**_reject(items, predicate)_**
+
+Calls [predicate](#predicates) for each element in collection, returning a list of elements that the
+predicate __does not__ return true for.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| items | the list or map to perform the reject against. |
+| predicate | a [predicate](#predicates) function to call for each element in the collection. A response of `false` will add the element to the result list. |
+
+### Examples
+
+Return all odd numbers by rejecting even ones:
+
+```sentinel playground
+import "collection"
+
+items = [2, 3, 4, 5, 6, 7, 8]
+result = collection.reject(items, func(el) {
+ return el % 2 is 0
+})
+main = result is [3, 5, 7]
+```
+
+## Predicates
+
+Some helpers accept a predicate function, which has the purpose of making an
+assertion. Each predicate may accept differing arguments and return differing
+types. If the collection is a list, the parameters will be the element and index.
+If the collection is a Map, the parameters will be the value and the key.
+
+### Examples
+
+A predicate for a list of items:
+
+```sentinel
+// including index
+func(item, index) {
+ return true
+}
+
+// excluding index
+func(item) {
+ return true
+}
+```
+
+A predicate for a map of items:
+```sentinel
+// including key
+func(value, key) {
+ return true
+}
+
+// excluding key
+func(value) {
+ return true
+}
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/imports/collection/lists.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/imports/collection/lists.mdx
new file mode 100644
index 0000000000..7804de05ca
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/imports/collection/lists.mdx
@@ -0,0 +1,124 @@
+---
+page_title: 'Import: collection/lists'
+sidebar_current: docs-imports-collection-lists
+description: The collection/lists import provides useful helpers for working with lists.
+layout: docs
+---
+
+# Import: collection/lists
+
+The `collection/lists` import provides helpers for working with [lists](/sentinel/language/lists).
+
+## concat
+
+**_concat(items, others[, ...additional])_**
+
+Join multiple lists together, returning the resulting list. This helper must
+have at least two arguments supplied. Order is important,
+as the order of arguments is the order that lists will be appended.
+
+### Arguments
+
+| Name | Description |
+|------------|-------------|
+| items | the first list to add to the resulting value |
+| others | the second list to add to the resulting value |
+| additional | any number of additional lists to add to the resulting value |
+
+### Examples
+
+Concatenate two lists:
+
+```sentinel playground
+import "collection/lists" as lists
+
+result = lists.concat([1], [2, 3])
+main = result is [1, 2, 3]
+```
+
+Concatenate many lists:
+```sentinel playground
+import "collection/lists" as lists
+
+result = lists.concat([1, 2], [10, 20], [100, 200])
+main = result is [1, 2, 10, 20, 100, 200]
+```
+
+## sort
+
+**_sort(items, sort_function)_**
+
+Sort will sort a list of elements according to the provided sort function, returning
+a new, sorted list.
+
+### Arguments
+
+| Name | Description |
+|------|-------------|
+| items | the list to sort |
+| sort_function | the function that is used to perform the sort |
+
+### Sort Function
+
+The provided sort function accepts two arguments, which can be considered as the
+`current` and `next` item. The function should return one of the following values:
+
+| Result | Description |
+|--------|-------------|
+| -1 | `current` is less than `next` |
+| 0 | `current` is equal to `next` |
+| +1 | `current` is greater than `next` |
+
+The [compare](/sentinel/functions/compare) built-in method provides a way of
+performing comparisons against integers, floats or strings to return a supported
+value.
+
+### Examples
+
+Sort a list of words:
+
+```sentinel playground
+import "collection/lists" as lists
+
+items = ["zebra", "bat", "horse"]
+result = lists.sort(items, func(x, y) {
+ return compare(x, y)
+})
+main = result is ["bat", "horse", "zebra"]
+```
+
+Sort a list of objects by a key:
+
+```sentinel playground
+import "collection/lists" as lists
+
+items = [{"foo": 8}, {"foo": 4}, {"foo": 100}]
+result = lists.sort(items, func(x, y) {
+ return compare(x.foo, y.foo)
+})
+main = result is [{"foo": 4}, {"foo": 8}, {"foo": 100}]
+```
+
+## sum
+
+**_sum(items)_**
+
+Sum will add all elements within the provided list. The
+list must only contain integers or floats. The return value will always be a float.
+
+### Arguments
+
+| Name | Description |
+|------|-------------|
+| items | the list to sum |
+
+### Examples
+
+Perform a sum on a list of numbers:
+
+```sentinel playground
+import "collection/lists" as lists
+
+items = [2, 5, 10, 500]
+main = lists.sum(items) is 517
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/imports/collection/maps.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/imports/collection/maps.mdx
new file mode 100644
index 0000000000..d96bb15639
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/imports/collection/maps.mdx
@@ -0,0 +1,360 @@
+---
+page_title: 'Import: collection/maps'
+sidebar_current: docs-imports-collection-maps
+description: The collection/maps import provides useful helpers for working with maps.
+layout: docs
+---
+
+# Import: collection/maps
+
+The `collection/maps` import provides helpers for working with [maps](/sentinel/language/maps).
+
+## get
+
+**_get(object, path[, default])_**
+
+Get the value from the provided object using the [path](#paths).
+When the path is invalid or the object doesn't contain a value at the path, then the default is returned. The default return value is undefined.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the get against. |
+| path | a [path](#paths) to a key within object to retreive the value |
+| default | an optional value that will return if the path does not exist or returns undefined |
+
+### Examples
+
+Get a value from a simple object:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+item = maps.get(object, "foo")
+main = item is "bar"
+```
+
+Get a nested value from a complex object:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [{"bar": {"baz": 4}}]}
+item = maps.get(object, "foo.0.bar.baz")
+main = item is 4
+```
+
+Get a list of values using [splat](#splat):
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [{"bar": 4}, {"bar": 8}, {"bar": 45}]}
+item = maps.get(object, "foo.*.bar")
+main = item is [4, 8, 45]
+```
+
+Get an invalid path, providing a default:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {}
+item = maps.get(object, "foo", "bar")
+main = item is "bar"
+```
+
+Get an invalid path, not providing a default:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {}
+item = maps.get(object, "foo")
+main = item is not defined
+```
+
+## has
+
+**_has(object, path)_**
+
+Return a boolean value if the object has the path within its structure.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the has against. |
+| path | a [path](#paths) to a key within object to determine if it exists |
+
+### Examples
+
+An object has a valid key:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+main = maps.has(object, "foo")
+```
+
+An object does not have a valid key:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {}
+main = maps.has(object, "foo") is false
+```
+
+## omit
+
+**_omit(object, paths)_**
+
+Return a map, removing each path from the paths provided from the source object.
+The original structure of the map is maintained, as seen in the below examples.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the omit against. |
+| paths | a list of [paths](#paths) to be removed from the source object |
+
+### Examples
+
+Omitting a single value:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+main = maps.omit(object, ["foo"]) is {}
+```
+
+Omitting multiple values:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar", "baz": "qux", "nit": "nat"}
+main = maps.omit(object, ["foo", "baz"]) is {"nit": "nat"}
+```
+
+Omitting deep values:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {
+ "foo": "bar",
+ "baz": [
+ { "qux": 4 },
+ { "qux": 10 }
+ ],
+ "nit": {
+ "nat": "pak"
+ }
+}
+main = maps.omit(object, ["baz.1.qux", "nit.nat"]) is {"foo": "bar", "baz": [{"qux": 4}]}
+```
+
+## pick
+
+**_pick(object, paths)_**
+
+Return a map consisting of each value found in object from the paths provided.
+The original structure of the map is maintained, as seen in the below examples.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the pick against. |
+| paths | a list of [paths](#paths), each used to select a value from the object. |
+
+### Examples
+
+Picking a single value:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+main = maps.pick(object, ["foo"]) is {"foo": "bar"}
+```
+
+Picking multiple values:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar", "baz": "qux", "nit": "nat"}
+main = maps.pick(object, ["foo", "baz"]) is {"foo": "bar", "baz": "qux"}
+```
+
+Picking deep values:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {
+ "foo": "bar",
+ "baz": [
+ { "qux": 4 },
+ { "qux": 10 }
+ ],
+ "nit": {
+ "nat": "pak"
+ }
+}
+main = maps.pick(object, ["baz.1.qux", "nit.nat"]) is {"baz": [{"qux": 10}], "nit": {"nat": "pak"}}
+```
+
+## set
+
+**_set(object, path, value)_**
+
+Return a new map, assigning the provided value to the provided path. It will
+not modify the provided map in place.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the set against. |
+| path | the [path](#paths) to assign value |
+| value | the value to be assigned |
+
+### Examples
+
+Set a value on a simple path:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+object = maps.set(object, "foo", "qux")
+main = object is {"foo": "qux"}
+```
+
+Set a value on a deep path:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {}
+object = maps.set(object, "foo.bar.baz", 10)
+main = object is {"foo": { "bar": { "baz": 10 } } }
+```
+
+Set a value on an index in a list:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [ 2, 5 ] }
+object = maps.set(object, "foo.0", 10)
+main = object is {"foo": [ 10, 5 ] }
+```
+
+Set a value on every key within a list key:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [ { "bar": true }, { "bar": false } ] }
+object = maps.set(object, "foo.*.bar", true)
+main = object is {"foo": [ { "bar": true }, { "bar": true } ] }
+```
+
+## unset
+
+**_unset(object, path)_**
+
+Return a new map, removing the provided path from the source object. It will
+not modify the provided map in place.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the unset against. |
+| path | the [path](#paths) to remove |
+
+### Examples
+
+Unset on a simple path:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+object = maps.unset(object, "foo")
+main = object is {}
+```
+
+Unset on a deep path:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": { "bar": { "baz": 10, "qux": 15 } } }
+object = maps.unset(object, "foo.bar.baz")
+main = object is {"foo": { "bar": { "qux": 15 } } }
+```
+
+Unset an index in a list:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [ 2, 5 ] }
+object = maps.unset(object, "foo.0")
+main = object is {"foo": [ 5 ] }
+```
+
+Unset a list:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [ { "bar": true }, { "bar": false } ] }
+object = maps.unset(object, "foo.*", true)
+main = object is {"foo": [] }
+```
+
+## Paths
+
+Map helpers often receive a **path** argument that allows for looking up a nested
+key. Generally speaking, a path is a series of keys separated by `.`, however there
+are some additional capabilites that need to be explained.
+
+### Lists
+
+When traversing a nested map that contains a list, a specific index can be retrieved
+by providing the index as the part of the path.
+
+In the following code sample, the path will first enter the key `"foo"` within the
+map, followed by entering the first index of the list.
+
+```sentinel
+path = "foo.0"
+object = {"foo": [1]}
+```
+
+### Splat
+
+To provide advanced capabilities when using paths, you can also use the splat (`*`)
+operator to iterate through **all** elements in a list, with all parts of the path
+following the splat occuring on each entry.
+
+In the following code sample, the path will first enter the key `"foo"` within the map.
+It will then enter each item in the list, entering the `"bar"` key for each nested object.
+
+```sentinel
+path = "foo.*.bar"
+object = {"foo": [{"bar": 1}, {"bar": 2}]}
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/imports/decimal.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/imports/decimal.mdx
new file mode 100644
index 0000000000..a6633a38e0
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/imports/decimal.mdx
@@ -0,0 +1,318 @@
+---
+page_title: 'Import: decimal'
+sidebar_current: docs-imports-decimal
+description: |-
+ The decimal import provides functions for operating on numbers represented
+ as decimals.
+layout: docs
+---
+
+# Import: decimal
+
+The decimal import provides functions for operating on numbers represented
+as decimals.
+
+Binary floating-point numbers provided by the `float` type are unable to
+fully represent numbers like `1.1` and `2.2`. The decimal import can
+represent these numbers exactly, and so should be used when exactness is
+required.
+
+Decimals are created through the `new` function, which takes any type that
+can represent a number. Arguments to the decimal operators can also take
+a value of any of these types. The full list is:
+
+- float
+- int
+- string
+- existing decimal value
+
+### decimal.infinity(sign)
+
+Creates an infinite-value decimal. Infinite-value decimals are always larger
+or smaller, depending on sign, than any non-infinite decimals.
+
+```sentinel
+decimal.infinity(1) // +Infinity
+decimal.infinity(-1) // -Infinity
+```
+
+Also available as `decimal.inf`.
+
+### decimal.nan
+
+A decimal representing a "not-a-number" value. NaNs are not equal to any
+other number, even themselves.
+
+```sentinel
+decimal.new(-1).sqrt() // NaN
+decimal.new(0).mul(decimal.inf(1)) // NaN
+decimal.nan.is(decimal.nan) // false
+```
+
+### decimal.is_infinite(d)
+
+Evaluates to true if the decimal is infinite.
+
+```sentinel
+decimal.is_infinite(100) // false
+decimal.is_infinite("Infinity") // true
+```
+
+Also available as `decimal.is_inf`, `decimal.isinf` and `decimal.isinfinite`.
+
+### decimal.is_nan(d)
+
+Evaluates to true if the decimal is not-a-number.
+
+```sentinel
+decimal.is_nan("NaN") // true
+```
+
+Also available as `decimal.isnan`.
+
+### decimal.new(v)
+
+Constructs a decimal from another value.
+
+```sentinel
+decimal.new("1.1") // Constructs a decimal from a string.
+decimal.new(2.2) // Constructs a decimal from a float.
+decimal.new(3) // Constructs a decimal from an integer.
+decimal.new("1.1234E+400") // Constructs a decimal from a string using E-notation.
+```
+
+## Type: number
+
+Numbers are represented internally by a sign, coefficient, and exponent.
+The value is calculated with the formula `sign * coefficient * 10^exponent`.
+Exponent is limited to 32 bits, and the coefficient is limited by the total
+precision.
+
+The maximum precision a number can represent is 100 digits. This includes
+both before and after the decimal place.
+
+### number.string
+
+A string representation of the decimal.
+
+```sentinel
+decimal.new(1.5).string // 1.5
+```
+
+### number.sign
+
+A number between `-1` and `+1` representing the sign of the decimal.
+
+```sentinel
+decimal.new(1.5).sign // 1
+```
+
+### number.coefficient
+
+The coefficient component of the decimal.
+
+```sentinel
+decimal.new(1.5).coefficient // 15
+```
+
+### number.exponent
+
+The exponent component of the decimal.
+
+```sentinel
+decimal.new(1.5).exponent // -1
+```
+
+### number.float
+
+A floating-point representation of the decimal.
+
+```sentinel
+decimal.new(1.5).float // 1.500000
+```
+
+### number.int
+
+An integer representation of the decimal. This always rounds down to the nearest integer.
+
+```sentinel
+decimal.new(1.5).int // 1
+```
+
+### number.is(v)
+
+Test for equality with another value.
+
+```sentinel
+decimal.new(1.5).is(1) // false
+```
+
+### number.is_not(v)
+
+Test for inequality with another value.
+
+```sentinel
+decimal.new(1.5).is_not(1) // true
+```
+
+### number.less_than(v)
+
+Test that the decimal is less than another value.
+Can also be called as `number.lt(v)`
+
+```sentinel
+decimal.new(1.5).less_than(1) // false
+```
+
+### number.less_than_or_equals(v)
+
+Test that the decimal is less than or equals to another value.
+Can also be called as `number.lte(v)`
+
+```sentinel
+decimal.new(1.5).less_than_or_equals(1) // false
+```
+
+### number.greater_than(v)
+
+Test that the decimal is greater than another value.
+Can also be called as `number.gt(v)`
+
+```sentinel
+decimal.new(1.5).greater_than(1) // true
+```
+
+### number.greater_than_or_equals(v)
+
+Test that the decimal is greater than or equals to another value.
+Can also be called as `number.gte(v)`
+
+```sentinel
+decimal.new(1.5).greater_than_or_equals(1) // true
+```
+
+### number.add(v)
+
+Add another number to the decimal.
+
+```sentinel
+decimal.new(1.5).add(1) // 2.5
+```
+
+### number.subtract(v)
+
+Subtract another number from the decimal.
+Can also be called as `number.sub(v)`
+
+```sentinel
+decimal.new(1.5).subtract(1) // 0.5
+```
+
+### number.multiply(v)
+
+Multiply the decimal by a number.
+Can also be called as `number.mul(v)`
+
+```sentinel
+decimal.new(1.5).multiply(2) // 3.0
+```
+
+### number.divide(v)
+
+Divide the decimal by a number.
+Can also be called as `number.div(v)`
+
+```sentinel
+decimal.new(3.0).divide(1.5) // 2
+```
+
+### number.modulo(v)
+
+Find the remainder after dividing the decimal by a number.
+Can also be called as `mod`, `remainder`, or `rem`.
+
+```sentinel
+decimal.new(1.5).modulo(1) // 0.5
+```
+
+### number.power(v)
+
+Raise the decimal to a power.
+Can also be called as `number.pow(v)`
+
+```sentinel
+decimal.new(1.5).power(2) // 2.25
+```
+
+### number.exp()
+
+The natural exponent of the decimal. This calculates `e^number`.
+
+```sentinel
+decimal.new(1.5).exp() // 4.4816...
+```
+
+### number.loge()
+
+The natural logarithm of the decimal.
+Can also be called as `number.ln()`
+
+```sentinel
+decimal.new(1.5).loge() // 0.4054...
+```
+
+### number.log10()
+
+The base-10 logarithm of the decimal.
+Can also be called as `number.log()`
+
+```sentinel
+decimal.new(1.5).log10() // 0.1760...
+```
+
+### number.square_root()
+
+The square root of the decimal.
+Can also be called as `number.sqrt()`
+
+```sentinel
+decimal.new(1.5).square_root() // 1.2247...
+```
+
+### number.ceiling()
+
+Round the decimal to the smallest integer greater or equal to itself.
+Can also be called as `number.ceil()`
+
+```sentinel
+decimal.new(1.5).ceiling() // 2
+```
+
+### number.floor()
+
+Round the decimal to the largest integer less than or equal to itself.
+
+```sentinel
+decimal.new(1.5).floor() // 1
+```
+
+### number.absolute()
+
+The absolute value of the decimal.
+Can also be called as `number.abs()`
+
+```sentinel
+decimal.new(1.5).absolute() // 1.5
+decimal.new(-1.5).absolute() // 1.5
+```
+
+### number.negate()
+
+The negated value of the decimal. A positive decimal will become negative,
+and a negative decimal will become positive.
+Can also be called as `number.neg()`
+
+```sentinel
+decimal.new(1.5).negate() // -1.5
+decimal.new(-1.5).negate() // 1.5
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/imports/http.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/imports/http.mdx
new file mode 100644
index 0000000000..f94ae98c27
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/imports/http.mdx
@@ -0,0 +1,370 @@
+---
+page_title: 'Import: http'
+sidebar_current: docs-imports-http
+description: The http import enables the use of HTTP-accessible data from outside the runtime in Sentinel policy rules.
+layout: docs
+---
+
+# Import: http
+
+The http import enables the use of HTTP-accessible data from outside
+the runtime in Sentinel policy rules.
+
+The simplest example of the import in use would be:
+
+```sentinel
+import "http"
+
+resp = http.get("https://example.hashicorp.com")
+main = rule { resp.body contains "something" }
+```
+
+By default, any request response that is not a successful "200 OK" HTTP
+response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information and
+how to customize this behavior.
+
+For more advanced requests which require setting request headers, use a
+[`request` type](#type-request):
+
+```sentinel
+import "http"
+import "json"
+
+param token
+
+req = http.request("https://example.hashicorp.com").with_header("Authorization", "token "+token)
+resp = json.unmarshal(http.get(req).body)
+main = rule { resp["some_key"] is true }
+```
+
+The `with_header` function above returns the `request` object with the
+`Authorization` HTTP header set to value of the variable `some_value`. Many
+of the methods within the http import API are designed around this concept
+of method chaining, allowing for complex building of HTTP requests
+encapsulated in functions. The following is an idiomatic example further
+demonstrating this pattern:
+
+```sentinel
+import "http"
+import "json"
+
+param github_user
+param github_token
+
+client = func(req) {
+ return http.with_timeout(5).with_retries(3)
+}
+
+github_request = func(path) {
+ full_url = "https://api.github.com" + path
+ return http.request(full_url).
+ with_basic_auth(github_user, github_token).
+ with_header("Accept", "application/vnd.github.v3+json").
+ with_header("Custom", "foo"),
+}
+
+default_response = client.get(github_request("/example"))
+
+# Override the Custom header for this single request
+custom_header_response = json.unmarshal(
+ client.get(
+ github_request("/example").with_header("Custom", "bar"),
+ ),
+)
+
+# Override the timeout value for a known slower request
+slow_response = json.unmarshal(
+ client.with_timeout(15).get(github_request("/slow-endpoint")),
+)
+
+some_key_is_true = rule { json.unmarshal(default_response.body)["some_key"] is true }
+body_contains_text = rule { custom_header_response.body contains "something" }
+response_is_ok = rule { slow_response.status_code is 200 }
+
+main = rule { some_key_is_true and body_contains_text and response_is_ok }
+```
+
+### http.client
+
+Retrieve the base HTTP client with default settings. The returned client may be
+configured by calling configuration functions on it; all of these configuration
+functions on are also aliased as top-level functions in this namespace. See
+[`client`](#type-client) below.
+
+### http.request(url)
+
+Create a new [`request`](#type-request) to the specified `url` (string). A
+`request` type enables customization of headers and other concerns before then
+providing the constructed `request` to [`get()`](#client-get-url_or_request),
+[`post()`](#client-post-url_or_request) or [`send()`](#client-send-request).
+
+```sentinel
+req = http.request("example.hashicorp.com/foo").with_header("Foo", "bar")
+resp = http.get(req)
+```
+
+### http.methodPost
+
+Returns a string containing the accepted verb for POST requests, `"POST"`.
+
+### http.methodGet
+
+Returns a string containing the accepted verb for GET requests, `"GET"`.
+
+## Type: client
+
+All of the following configuration functions on the default client are also
+aliased as top-level functions in the import. This allows a short-hand (and
+idiomatic) way of configuring a new client without having to directly access
+`http.client` each time:
+
+```sentinel
+# The following two lines are semantically the same:
+resp = http.client.with_timeout(5).get(url)
+resp = http.with_timeout(5).get(url)
+```
+
+### url_or_request
+
+The [`get()`](#client-get-url_or_request) and [`post()`](#client-post-url_or_request)
+functions accept either a URL string or a request object, noted as
+`url_or_request` throughout the documentation.
+
+When `url_or_request` is a string, a request is made with the URL
+specified by the string. To customize HTTP headers and other settings, pass
+a request. By default, a request has a timeout value of 10 seconds and 1
+retry. These settings can be adjusted with [`with_timeout()`](#clientwith_timeoutinteger) and
+[`with_retries()`](#clientwith_retriesinteger), respectively.
+
+### Redirects
+
+If the response is one of the following redirect codes, the client follows the
+redirect, up to a maximum of 10 redirects:
+
+- 301 (Moved Permanently)
+- 302 (Found)
+- 303 (See Other)
+- 307 (Temporary Redirect)
+- 308 (Permanent Redirect)
+
+If the redirect limit is exceeded, the result is a policy error.
+
+By default, after any redirects are followed (up to the maximum), any request
+response that is not a successful "200 OK" response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information
+and how to customize this behavior.
+
+### client.get(url_or_request)
+
+Issue a GET request with a URL string or a `request` type. Returns a `response`
+type.
+
+```sentinel
+import "http"
+
+resp = http.get("https://example.hashicorp.com")
+main = rule { resp.body contains "something" }
+```
+
+### client.post(url_or_request)
+
+Issue a POST request with a URL string or a `request` type. Returns a `response`
+type.
+
+```sentinel
+import "http"
+
+req = http.request("https://example.hashicorp.com")
+ .with_body(json.marshal({"foo": "bar"}))
+resp = http.post(req)
+main = rule { resp.body contains "something" }
+```
+
+### client.send(request)
+
+Make a request using the supplied `request` type. Returns a `response`.
+
+```sentinel
+import "http"
+
+req = http.request("https://example.hashicorp.com")
+resp = http.send(req)
+main = rule { resp.body contains "something" }
+```
+
+### client.accept_status_codes(list)
+
+Configures a client to not automatically cause a policy failure if
+the resulting response's status code is in `list`. Any status code received
+that is not present in this list will trigger a runtime error.
+
+By default, any request response that is not a successful "200 OK" response
+causes a policy error. This is for convenience, as the most common scenario
+by far is to receive a response and immediately signal a policy error if the
+status code is not 200. Use this scoping to specify a list of non-redirect
+HTTP codes that you wish to accept without error and evaluate the response
+yourself.
+
+Redirects are not considered final status codes in this context. That is,
+redirects are always followed up to the maximum allowed value (see [`Redirects`](#redirects))
+and the final response code in the redirect chain is validated against
+`list`.
+
+```sentinel
+resp = http.accept_status_codes([200, 404]).get(url)
+main = rule { resp.status_code is 404 }
+```
+
+### client.accepted_status_codes
+
+Returns a list of the client's accepted status codes.
+
+```sentinel
+client = http.accept_status_codes([200, 404])
+main = rule { client.accepted_status_codes contains 404 }
+```
+
+### client.with_retries(integer)
+
+Sets the maximum number of retries, specified by `integer`, that should be
+attempted on an unsuccessful request. An unsuccessful request is considered
+to be any 5xx response except `501 (Not Implemented)` or a request timeout.
+By default, one retry is attempted.
+
+The maximum number of retries is 10, for a total of 11 request attempts.
+When a request exceeds the maximum number of retries without a response, the
+result is a policy error. Specifying a number larger than this will result
+in a runtime error.
+
+Between each retry, an exponentially increasing delay is observed, allowing
+time for the server to recover. This delay starts at 1 second for the first
+retry and increases each attempt thereafter. The maximum delay for a single
+attempt is 30 seconds.
+
+Retries can be disabled by specifying 0.
+
+### client.retries
+
+Returns the client's configured number of retries as an integer.
+
+### client.with_timeout(integer)
+
+Sets the request timeout to the number of seconds indicated by `integer`.
+
+Each individual request performed by the HTTP client will be limited by the
+given request timeout. This timeout includes connection time, any redirects,
+and reading the response body.
+
+A maximum of 30 seconds is allowed. Specifying a number larger than this
+will result in a runtime error.
+
+By default, requests will time out after 10 seconds.
+
+### client.timeout
+
+Returns the client's configured timeout in whole seconds as an integer.
+
+### client.without_certificate_verification()
+
+Skips verification of TLS certificates during secure HTTP operations,
+allowing for requests to `https://` endpoints which are secured with a TLS
+certificate signed by an untrusted certificate authority. Takes no arguments.
+
+By default, the HTTP client will trigger a runtime error if a TLS certificate
+presented by a server is from an untrusted certificate authority.
+
+Do not change this setting unless you are aware of the risks involved.
+Without verification, TLS accepts any certificate presented by the server
+and any host name in that certificate. In this mode, TLS is susceptible to
+man-in-the-middle attacks. This option should only be used for testing or in
+trusted networks with self-signed certificates.
+
+```sentinel
+http.without_certificate_verification().get(url)
+```
+
+### client.certificate_verification
+
+Returns true if the client requires TLS certificates to be verifiable.
+
+## Type: request
+
+### request.url
+
+Returns the request URL as a string.
+
+### request.headers
+
+Returns the configured request headers as a string-keyed map of strings.
+
+### request.body
+
+Returns the configured body as a string.
+
+### request.with_header(key, value)
+
+Returns a `request` with the header `key` (string) set to `value` (string).
+
+Values are overridden on subsequent calls to the same `key`. To specify
+multiple values, use the existing value when setting the new one.
+
+```sentinel
+original = http.request("http://example.hashicorp.com").with_header("Foo", "bar")
+overridden = original.with_header("Foo", "baz")
+multi_value = original.with_header("Foo", original.headers["Foo"] + ",baz")
+
+main = rule {
+ original.headers["Foo"] is "bar" and
+ overridden.headers["Foo"] is "baz" and
+ multi_value.headers["Foo"] is "bar,baz"
+}
+```
+
+### request.with_basic_auth(username, password)
+
+Returns a `request` with the `Authorization` header set to use HTTP Basic
+Authentication with the provided username and password.
+
+Note that with HTTP Basic Authentication the provided username and password
+are encoded, but not encrypted.
+
+### request.with_body(body)
+
+Returns a `request` with the `body` set. This value will then be sent as a body
+as part of the request. Commonly used along with the [`post()`](#client-post-url_or_request) function.
+
+```sentinel
+req = http.request("http://example.hashicorp.com")
+ .with_header("Content-Type", "application/json")
+ .with_body(json.marshal({ "foo": "bar" }))
+
+resp = http.post(req)
+```
+
+## Type: response
+
+### response.status_code
+
+The response status code as an integer, e.g. `200`.
+
+### response.headers
+
+The response headers as a map.
+
+### response.body
+
+The response body as a string. Callers should unmarshal the content to a useful
+representation as necessary based on the content type. For example, if the
+response is in JSON:
+
+```sentinel
+import "http"
+import "json"
+
+# This example endpoint returns {"some_key": true}
+req = http.request("https://example.hashicorp.com/some-json")
+
+resp = json.unmarshal(http.get(req).body)
+main = rule { keys(resp) contains "some_key" and resp["some_key"] is true }
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/imports/index.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/imports/index.mdx
new file mode 100644
index 0000000000..e45bf81b81
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/imports/index.mdx
@@ -0,0 +1,18 @@
+---
+page_title: Sentinel Language - Standard Imports
+sidebar_current: docs-imports
+description: >-
+ Standard Imports are the built-in imports that are available to all Sentinel
+ policies.
+layout: docs
+---
+
+# Standard Imports
+
+Standard Imports are the built-in [imports](/sentinel/language/imports)
+that are available to all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in import.
+
+Sentinel-embedded applications can choose to allow or deny certain
+standard imports. Please reference the documentation for the Sentinel-enabled
+application you're using to determine if all standard imports are available.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/imports/json.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/imports/json.mdx
new file mode 100644
index 0000000000..3355458f14
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/imports/json.mdx
@@ -0,0 +1,31 @@
+---
+page_title: 'Import: json'
+sidebar_current: docs-imports-json
+description: The json import enables a Sentinel policy to parse and access a JSON document.
+layout: docs
+---
+
+# Import: json
+
+The json import enables a Sentinel policy to parse and access a JSON
+document.
+
+### json.unmarshal(obj)
+
+Unmarshals the JSON object `obj` into a native Sentinel structure.
+
+All native JSON types can be represented perfectly as Sentinel native types.
+
+```sentinel
+// Typically the input for this would come from an external source.
+config = json.unmarshal("{ \"foo\": 42 }")
+config.foo // 42
+config.bar // undefined (as usual for accessing a non-existent map key)
+```
+
+### json.marshal(obj)
+
+Marshals the Sentinel object `obj` into a JSON object encoded as a string.
+
+Functions and `undefined` cannot be natively encoded as JSON. If values of
+either type are found in the structure, this will return undefined.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/imports/runtime.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/imports/runtime.mdx
new file mode 100644
index 0000000000..6493bbbb22
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/imports/runtime.mdx
@@ -0,0 +1,16 @@
+---
+page_title: 'Import: runtime'
+sidebar_current: docs-imports-runtime
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: runtime
+
+The runtime import contains various information about the Sentinel runtime. It
+can be used to get information about the runtime as it's embedded in the CLI or
+a specific integration, such as validating a specific runtime version.
+
+### runtime.version
+
+Returns the version of Sentinel within this implementation.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/imports/sockaddr.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/imports/sockaddr.mdx
new file mode 100644
index 0000000000..a6842ba5f0
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/imports/sockaddr.mdx
@@ -0,0 +1,41 @@
+---
+page_title: 'Import: sockaddr'
+sidebar_current: docs-imports-sockaddr
+description: The sockaddr import makes working with IP addresses easy.
+layout: docs
+---
+
+# Import: sockaddr
+
+The sockaddr import makes working with IP addresses easy.
+
+### sockaddr.is_contained(outer, inner)
+
+The is_contained function returns true if `inner` is an address that
+is contained within `outer`.
+
+```sentinel
+sockaddr.is_contained("192.168.0.0/24", "192.168.0.32") // true
+sockaddr.is_contained("192.168.0.0/24", "192.174.1.1") // false
+```
+
+### sockaddr.is_equal(addr1, addr2)
+
+The is_equal function returns true if addr1 is equal to addr2.
+
+```sentinel
+sockaddr.is_equal("192.168.12.24", "192.168.12.24") // true
+```
+
+### sockaddr.is_ipv4(addr)
+
+The is_ipv4 function returns true if addr is an IPv4 address.
+
+```sentinel
+sockaddr.is_ipv4("192.168.12.24") // true
+sockaddr.is_ipv4("2001:0db8:85a3:0000:0000:8a2e:0370:7334") // false
+```
+
+### sockaddr.is_ipv6(addr)
+
+The is_ipv6 function returns true if addr is an IPv6 address.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/imports/strings.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/imports/strings.mdx
new file mode 100644
index 0000000000..e1ba9a9e2a
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/imports/strings.mdx
@@ -0,0 +1,150 @@
+---
+page_title: 'Import: strings'
+sidebar_current: docs-imports-strings
+description: The strings import exposes common string operations.
+layout: docs
+---
+
+# Import: strings
+
+The strings import exposes common string operations.
+
+### strings.has_prefix(s, prefix)
+
+Returns true if `s` starts with `prefix`.
+
+```sentinel
+strings.has_prefix("billing-id", "billing-") // true
+strings.has_prefix("bill-id", "billing-") // false
+```
+
+### strings.has_suffix(s, suffix)
+
+Returns true if `s` ends with `suffix`.
+
+```sentinel
+strings.has_suffix("billing-id", "id") // true
+strings.has_suffix("billing-name", "id") // false
+```
+
+### strings.join(a, sep)
+
+Joins a list with the string supplied by `sep`.
+
+Multi-dimensional lists are supported, and non-string primitive
+types will be converted to their string equivalents. Maps and
+other complex non-list types are not supported.
+
+```sentinel
+strings.join(["foo", "bar", "baz"], ".") // "foo.bar.baz"
+strings.join([["foo", "bar"], "baz"], ".") // "foo.bar.baz"
+strings.join(["a", 1, 1.01, true], ",") // "a,1,1.01,true"
+```
+
+### strings.replace(s, old, new, times)
+
+Replace the substring `old` with the substring `new` in the string `s`
+by how many times the int parameter `times` is specified.
+If the string `s` doesn't the substrings or if `times` is zero,
+then the string is returned without any changes applied.
+
+```
+strings.replace("foobar", "foo", "bar") // "barbar"
+strings.replace("foofoofoobar", "foo", "bar", 1) // "barfoofoobar"
+strings.replace("foofoofoobar", "foo", "bar", 2) // "barbarfoobar"
+strings.replace("foobar", "test", "bar") // "foobar"
+strings.replace("foobar", "foo", "bar", 0) // "foobar"
+```
+
+### strings.trim(s, cutset)
+
+Trim the leading and trailing characters in `cutset` from the string `s`.
+If the string doesn't have the cutset, then the string is returned
+unmodified.
+
+```
+strings.trim("iiifoobariii", "i") // "foobar"
+strings.trim("!1!bar foo!!1", "!1") // "bar foo"
+```
+
+### strings.trim_left(s, cutset)
+
+Trim the leading characters contained in `cutset` from the string `s`.
+If the string doesn't have the cutset, then the string is returned
+unmodified.
+
+```sentinel
+strings.trim_left("aaaaaaaafoo", "a") // "foo"
+strings.trim_left("!!!bar!!!", "!") // "bar!!!"
+```
+
+### strings.trim_right(s, cutset)
+
+Trim the trailing characters contained in the `cutset` from the
+string `s`. If the string doesn't have the cutset, then the string
+is returned unmodified.
+
+```sentinel
+strings.trim_right("foo_bar...", ".") // "foo_bar"
+strings.trim_right("billing---","-") // "billing"
+```
+
+### strings.trim_space(s)
+
+Trim leading and trailing white space from the string `s`. If the
+string doesn't have any surrounding white space, then the string is
+returned unmodified.
+
+```sentinel
+strings.trim_space(" foo ") // "foo"
+strings.trim_space(" bar foo ") // "bar foo"
+```
+
+### strings.trim_prefix(s, prefix)
+
+Trim the prefix from the string `s`. If the string doesn't have the
+prefix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_prefix("billing-id", "billing-") // "id"
+strings.trim_prefix("bill-id", "billing-") // "bill-id"
+```
+
+### strings.trim_suffix(s, suffix)
+
+Trim the suffix from the string `s`. If the string doesn't have the
+suffix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_suffix("billing-id", "-id") // "billing"
+strings.trim_suffix("bill-id", "-foo") // "bill-id"
+```
+
+### strings.to_lower(s)
+
+Lowercase the string s.
+
+```sentinel
+strings.to_lower("FoO") // "foo"
+```
+
+### strings.to_upper(s)
+
+Uppercase the string s.
+
+```sentinel
+strings.to_upper("FoO") // "FOO"
+```
+
+### strings.split(s, sep)
+
+Split the string 's' via a substring 'sep'. If 'sep' is not contained in
+'s', the return value will be a single-element list containing only 's'.
+As a special-case, if the input is already a list, it will be returned
+as-is. This allows calling the split function when not knowing if the input
+is already a list or not.
+
+```sentinel
+strings.split("foo,bar,baz", ",") // ["foo", "bar", "baz"]
+strings.split(["foo", "bar", "baz"], ",") // ["foo", "bar", "baz"]
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/imports/time.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/imports/time.mdx
new file mode 100644
index 0000000000..3a675e2bbd
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/imports/time.mdx
@@ -0,0 +1,257 @@
+---
+page_title: 'Import: time'
+sidebar_current: docs-imports-time
+description: The time import provides access to the execution time and time functions.
+layout: docs
+---
+
+# Import: time
+
+The time import provides access to the execution time and time functions.
+
+During the execution of a policy the time is fixed to the moment of
+execution. This gives the illusion that a policy instantly executes at a
+single moment of time.
+
+The import uses Coordinated Universal Time (UTC).
+
+As an example of this, if a policy accessed `time.now.second` multiple times,
+for each access the same value would be returned even if they are several seconds apart.
+This is the execution `timespace`, which is mapped to `time.now`.
+
+It is also possible to run functions on a custom time by using the
+`time.load()` function to create a new timespace from the specified value.
+See the documentation for available actions on timespaces.
+
+Times specified as the reference time or via the `time.load()` function can
+be specified in a `timeish` fashion. This is either one of:
+
+- The number of seconds since the Unix epoch (Thursday, 1 January 1970,
+ 00:00:00 UTC),
+- A timestamp matching the format outlined in RFC3339, either in the
+ format `2006-01-02T15:04:05+07:00`, which includes a timezone offset, or
+ `2006-01-02T15:04:05Z`, which indicates UTC.
+
+### time.now
+
+Create a `timespace` locked either to execution time or, if set, the
+reference time. In a given execution, this will always produce the same
+value.
+
+```sentinel
+time.now // {"day": 17, "hour": 23, "minute": 19, "month": 11 ... }
+```
+
+### time.load(timeish)
+
+Create a timespace using the given value. Please see the import
+documentation for valid values for "timeish".
+
+```sentinel
+time.load("2019-11-16T01:20:30Z") // {"day": 16, "hour": 1, "minute": 20, "month": 11 ... }
+```
+
+### time.nanosecond
+
+A nanosecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.nanosecond * 1) // 2019-11-16T01:20:30.000000001Z
+```
+
+### time.microsecond
+
+A microsecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.microsecond * 1) // 2019-11-16T01:20:30.000001Z
+```
+
+### time.millisecond
+
+A millisecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.millisecond * 1) // 2019-11-16T01:20:30.001Z
+```
+
+### time.second
+
+A second duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.second * 1) // 2019-11-16T01:20:31Z
+```
+
+### time.minute
+
+A minute for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.minute * 1) // 2019-11-16T01:21:30Z
+```
+
+### time.hour
+
+An hour duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+## Type: timespace
+
+### timespace.hour
+
+The hour from 0 to 23.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").hour // 1
+```
+
+### timespace.minute
+
+The minute from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").minute // 20
+```
+
+### timespace.second
+
+The second from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").second // 30
+```
+
+### timespace.day
+
+The day of the month, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").day // 16
+```
+
+### timespace.month
+
+The month of the year as an integer, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month // 11
+```
+
+### timespace.month_name
+
+The name of the month of the year, in English. Example: `January`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month_name // November
+```
+
+### timespace.weekday
+
+The weekday as an integer, where 0 is Sunday and 6 is Saturday.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday // 6
+```
+
+### timespace.weekday_name
+
+The name of the day of the week, in English. Example: `Sunday`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday_name // Saturday
+```
+
+### timespace.year
+
+The year as an integer.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").year // 2019
+```
+
+### timespace.unix
+
+The number of seconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix // 1573867230
+```
+
+### timespace.unix_nano
+
+The number of nanoseconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix_nano // 1573867230000000000
+```
+
+### timespace.zone
+
+The timezone offset, in seconds. This can be a negative number to indicate
+time west of UTC.
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone // -28800
+```
+
+### timespace.zone_string
+
+The timezone offset, in the format `+HH:MM` (example: `-08:00` for PST).
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone_string // -08:00
+```
+
+### timespace.before(timeish_or_timespace)
+
+Check if the time in the timespace is before the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").before("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.after(timeish_or_timespace)
+
+Check if the time in the timespace is after the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").after("2019-11-15T01:20:30Z") // true
+```
+
+### timespace.equal(timeish_or_timespace)
+
+Check if two times are equivalent
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").equal("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.add(duration)
+
+Add a duration in nanoseconds to the time in the timespace and return a new timespace. The
+duration can be negative. The duration should be specified as an integer
+multiplied with the correct unit.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+### timespace.sub(timeish_or_timespace)
+
+Subtract a time from the time in the timespace and return a duration in nanoseconds.
+
+```sentinel
+// Gives a duration of 3 hours.
+duration = time.load(
+ "2019-11-16T01:20:30Z",
+).sub("2019-11-16T04:20:30Z")
+
+// Returns 2019-11-16T01:20:30Z
+time.load(
+ "2019-11-16T04:20:30Z",
+).add(duration)
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/imports/types.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/imports/types.mdx
new file mode 100644
index 0000000000..fcfdbd2ae5
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/imports/types.mdx
@@ -0,0 +1,35 @@
+---
+page_title: 'Import: types'
+sidebar_current: docs-imports-types
+description: The types import enables a Sentinel policy to parse an object's type.
+layout: docs
+---
+
+# Import: types
+
+The types import enables a Sentinel policy to parse an object's type.
+
+### types.type_of(obj)
+
+Returns the object's type as a string
+
+```sentinel playground
+import "types"
+
+// Typically the inputs would come from an external source.
+is_boolean = rule { types.type_of(true) is "bool" }
+is_string = rule { types.type_of("Hello!") is "string" }
+is_integer = rule { types.type_of(42) is "int" }
+is_float = rule { types.type_of(42.123) is "float" }
+is_null = rule { types.type_of(null) is "null" }
+is_undefined = rule { types.type_of(undefined) is "undefined" }
+
+main = rule {
+ is_boolean and
+ is_string and
+ is_integer and
+ is_float and
+ is_null and
+ is_undefined
+}
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/imports/units.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/imports/units.mdx
new file mode 100644
index 0000000000..e2ad3442f9
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/imports/units.mdx
@@ -0,0 +1,39 @@
+---
+page_title: 'Import: units'
+sidebar_current: docs-imports-units
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: units
+
+The units import provides access to quick calculations for various byte
+units.
+
+Note that this import uses base-2 terminology:
+
+- One kilobyte is 1024 bytes
+- One megabyte is 1024^2 = 1048576 bytes
+- One gigabyte is 1024^3 = 1073741824 bytes
+- One terabyte is 1024^4 = 1099511627776 bytes
+- One petabyte is 1024^5 = 1125899906842624 bytes
+
+### units.kilobyte
+
+The base-2 representation of a kilobyte (1024 bytes).
+
+### units.megabyte
+
+The base-2 representation of a megabyte (1048576 bytes).
+
+### units.gigabyte
+
+The base-2 representation of a gigabyte (1073741824 bytes).
+
+### units.terabyte
+
+The base-2 representation of a terabyte (1099511627776 bytes).
+
+### units.petabyte
+
+The base-2 representation of a petabyte (1125899906842624 bytes).
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/imports/version.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/imports/version.mdx
new file mode 100644
index 0000000000..59366c1087
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/imports/version.mdx
@@ -0,0 +1,151 @@
+---
+page_title: 'Import: version'
+sidebar_current: docs-imports-version
+description: |-
+ The version import provides functions for parsing versions and version constraints,
+ and verifying versions against a set of constraints.
+layout: docs
+---
+
+# Import: version
+
+The version import provides functions for parsing versions and version constraints,
+and verifying versions against a set of constraints.
+
+Versions are created through the `new` function, which accepts a string type in dotted-tri format (i.e. 1.0.0-alpha.1+001) that can represent a version.
+
+### version.new(v)
+
+Constructs a version from string value.
+
+```sentinel
+version.new("1.0.0-alpha.1+001")
+```
+
+### version.major
+
+An integer representation of the Major number for a given version .
+
+```sentinel
+version.new("1.0.0-alpha.1+001").major // 1
+```
+
+### version.minor
+
+An integer representation of the Minor number for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").minor // 0
+```
+
+### version.patch
+
+An integer representation of the Patch number for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").patch // 0
+```
+
+### version.prerelease
+
+A string representation of the Prerelease for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").prerelease // "alpha.1"
+```
+
+### version.metadata
+
+A string representation of the Metadata for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").metadata // "001"
+```
+
+### version.version
+
+A string representation of the version including pre-release and metadata information.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").version // "1.0.0-alpha.1+001"
+```
+
+### version.greater_than(v)
+
+Tests that the version is greater than a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").greater_than("0.12.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").gt("1.0.1-alpha.1+001") // False
+```
+
+### version.greater_than_or_equals(v)
+
+Tests that the version is greater than or equal to a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").greater_than_or_equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").gte("1.0.1-alpha.1+001") // False
+```
+
+### version.less_than(v)
+
+Tests that the version is less than a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").less_than("1.0.1-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").lt("0.12.0-alpha.1+001") // False
+```
+
+### version.less_than_or_equals(v)
+
+Tests that the version is less than or equal to a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").less_than_or_equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").lte("0.12.0-alpha.1+001") // False
+```
+
+### version.less_than_or_equals(v)
+
+Tests that the version equals a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").eq("0.12.0-alpha.1+001") // False
+```
+
+### version.compare(v)
+
+Compares one version with a given version. Compare returns -1, 0, or 1 if the version is less, equal, or greater than the other version, respectively.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").compare("0.12.0-alpha.1+001") // -1
+version.new("1.0.0-alpha.1+001").compare("1.0.0-alpha.1+001") // 0
+version.new("0.12.0-alpha.1+001").cmp("1.0.0-alpha.1+001") // 1
+```
+
+### version.satisfies(v, x)
+
+Tests that a version adheres to one or more version constraints. Satisfies supports the following restriction operators:
+
+- =
+- !=
+- \>
+- <
+- \>=
+- <=
+- ~>
+
+Satisfies supports the following usage scenarios:
+
+- A constraint with a pre-release can only match a pre-release version that contains the same major, minor and patch identifiers.
+- A constraint without a pre-release can only match a version without a pre-release.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").satisfies("> 1.0.0-alpha.1+001") // false
+version.new("1.0.0-alpha.1+001").satisfies(">= 1.0.0-alpha.1+001") // true
+version.new("1.0.0-alpha.1+001").satisfies(">= 1.0.0-alpha.1+001, < 1.0.0-beta+001") // true
+version.new("1.0.0").sat("= 1.0.0") // true
+version.new("0.12.7").sat("~> 0.12.0") // true
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/index.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/index.mdx
new file mode 100644
index 0000000000..4497b40b83
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/index.mdx
@@ -0,0 +1,31 @@
+---
+layout: 'docs'
+page_title: 'Documentation'
+sidebar_current: 'docs-home'
+description: |-
+ Sentinel is a language and framework for policy built to be embedded in existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+[introduction](/sentinel/intro) to understand what Sentinel is, how it
+compares to other software, and more.
+
+
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/language/arithmetic.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/language/arithmetic.mdx
new file mode 100644
index 0000000000..c49f2fd0ce
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/language/arithmetic.mdx
@@ -0,0 +1,67 @@
+---
+page_title: Sentinel Language - Arithmetic
+sidebar_current: docs-language-arith
+description: >-
+ Sentinel supports arithmetic operators for integers and floats. Sentinel
+ supports sum, difference, product, quotient, and remainder.
+layout: docs
+---
+
+# Language: Arithmetic
+
+Sentinel supports arithmetic operators for integers and floats. Sentinel
+supports sum, difference, product, quotient, and remainder.
+
+```plaintext
++ sum
+- difference
+* product
+/ quotient
+% remainder
+```
+
+These operators work in a typical infix style:
+
+```sentinel
+4 + 8 // 12
+8 * 2 // 16
+8 / 4 // 2
+8 / 5 // 1
+8 % 5 // 3
+```
+
+## Order of Operations
+
+Arithmetic follows a standard mathematical order of operations.
+Grouping with parentheses can be used to affect ordering.
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+(4 + 5) * 2 // 18
+```
+
+A full table of operator precendence can be found on the
+[boolean expressions page](/sentinel/language/boolexpr). This
+shows how arithmetic operators relate to other operators.
+
+## Integer Division
+
+Integer division with a remainder rounds down to the nearest integer.
+Example: `8 / 3 is 2`.
+
+If the divisor is zero, an error occurs.
+
+## Mixed Numeric Operations
+
+Mixed numeric operations between integer and floating-point values are
+permitted. The result is a floating-point operation with the integer converted
+to a floating-point value for purposes of calculation.
+
+```sentinel
+import "types"
+
+a = 1.1 + 1 // 2.1
+types.type_of(a) // "float"
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/language/boolexpr.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/language/boolexpr.mdx
new file mode 100644
index 0000000000..f1c518c3bf
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/language/boolexpr.mdx
@@ -0,0 +1,225 @@
+---
+page_title: Sentinel Language - Boolean Expressions
+sidebar_current: docs-language-boolexpr
+description: >-
+ Boolean expressions are expressions that evaluate to a boolean value from the
+ result of a combination of one or more comparisons and logical operators.
+layout: docs
+---
+
+# Language: Boolean Expressions
+
+Boolean expressions are expressions that evaluate to a boolean value
+from the result of a combination of one or more comparisons and logical
+operators. Boolean expressions form the basis of policy since policy can be broken
+down to a set of logical decisions that turn into true or false.
+
+A single boolean expression is the body of a [rule](/sentinel/language/rules).
+Additionally, boolean expressions are used with [conditionals](/sentinel/language/conditionals),
+and more.
+
+## Order of Operations
+
+The precedence of operators is shown below. Operators with higher
+precedence values (larger numbers) are evaluated first. Operators with
+the same precedence value are evaluated left-to-right.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Examples of this precedence are shown below:
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+```
+
+## Logical Operators
+
+Logical operators are used to change or combine logical expressions.
+There are three binary operators `and`, `or`, and `xor`. There is
+a single unary operator `not` (which can also be specified as `!`).
+
+The binary operators require two boolean operands and the unary
+operator requires a single boolean operand. In both case, the operands
+are values or expressions that are boolean types.
+
+Below is a basic explanation of the logical operators directly from
+the specification:
+
+```sentinel
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+Using parentheses to group boolean expressions, you can combine
+boolean expressions to become more complex or affect ordering:
+
+```sentinel
+(p and q) or r
+p and (q or r)
+```
+
+## Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+For any comparison, the two operands must be equivalent types. The one
+exception are integers and floats. When an integer is compared with
+a float, the integer is promoted to a floating point value.
+
+The available comparison operators are:
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+The behavior of `is` with `==` and `is not` with `!=` is identical.
+
+Example comparisons:
+
+```sentinel
+name is "Mitchell"
+idnumber > 42
+idnumber <= 12
+```
+
+Using the logical operators, these can be combined:
+
+```sentinel
+name is "Mitchell" and idnumber > 42
+```
+
+The language specification provides more detail on exactly
+[how comparison behaves](/sentinel/language/spec#comparison-operators).
+
+## Set Operators
+
+The set operators `contains` and `in` test for inclusion in a collection
+(a list or map).
+
+Set operators may 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.
+
+`contains` tests if the left-hand collection contains the right-hand value.
+`in` tests if the right-hand collection contains the left-hand value. For
+maps, "contains" looks at the keys, not the values.
+
+Examples:
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+```
+
+## Matches Operator
+
+The `matches` operator tests if a string matches a regular expression.
+The `matches` operator can be negated by prefixing the operator with
+`not`, such as `not matches`.
+
+The left-hand value is the string to test. The right-hand value is
+a string value representing a regular expression. The syntax of the regular
+expression is the same general syntax used by Python, Ruby, and other languages.
+The precise syntax accepted is the syntax accepted by RE2.
+
+Regular expressions are not anchored by default; any anchoring must be
+explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+## Any, All Expressions
+
+`any` and `all` expressions allow testing that any or all elements of a
+collection match some boolean expression. The result of an `any` and `all`
+expression is a boolean.
+
+`any` returns the boolean value `true` if any value in the collection expression
+results in the body expression evaluating to `true`. If the body expression
+evalutes to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression
+result in the body expression evaluating to `true`. If any value in the
+collection expression result in the body expression evaluating to `false`,
+the `all` expression returns `false`.
+
+For empty collections, `any` returns `false` and `all` returns `true`.
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" }
+any group.tasks as t { t.driver is "vmware" }
+```
+
+Since `any` and `all` expressions are themselves boolean expressions, they
+can be combined with other operators:
+
+```sentinel
+any ["a", "b"] as char { char is "a" } or
+other_value is "another"
+```
+
+## Emptiness Comparison
+
+The expressions `is empty` and `is not empty` provide a convenience
+method for determining the emptiness of a value. It supports the same types
+as the [built-in length](/sentinel/functions/length), collections and strings.
+
+```sentinel
+[] is empty // true
+[] is not empty // false
+["foo"] is empty // false
+["foo"] is not empty // true
+undefined is empty // undefined
+undefined is not empty // undefined
+```
+
+## Defined Comparison
+
+The expressions `is defined` and `is not defined` provide a convenience
+method for determining if a value has been defined. In other words, any value
+other than `undefined` can be considered as `defined`.
+
+```sentinel
+[] is defined // true
+4 is defined // true
+true is defined // true
+{} is defined // true
+undefined is defined // false
+[] is not defined // false
+4 is not defined // false
+true is not defined // false
+undefined is not defined // true
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/language/collection-operations.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/language/collection-operations.mdx
new file mode 100644
index 0000000000..41da8f3584
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/language/collection-operations.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Sentinel Language - Collection Operations
+sidebar_current: docs-language-collection-operations
+description: |-
+ Operations for interacting with collections (list, map).
+layout: docs
+---
+
+# Language: Collection Operations
+
+Collection operations are expressions that are performed on a list or map to
+return a variation of the initial data.
+
+At the moment, `filter` is the only collection operation available.
+
+## Filter Expression
+
+`filter` is a [quantifier
+expression](/sentinel/language/spec#quantifier-expressions-any-all-filter-map) that
+returns a subset of the provided collection. Only elements whose filter body
+returns true will be returned. If any of the elements filter body returns
+undefined, the final result will be undefined.
+
+Filter uses the same syntax as the `any` and `all` [boolean
+expressions](/sentinel/language/boolexpr#any-all-expressions):
+
+```sentinel
+filter list as value { condition } // Single-iterator, list
+filter list as idx, value { condition } // Double-iterator, list
+
+filter map as key { condition } // Single-iterator, map
+filter map as key, value { condition } // Double-iterator, map
+```
+
+Examples:
+
+```sentinel
+l = [1, 1, 2, 3, 5, 8]
+evens = filter l as v { v % 2 is 0 } // [2, 8]
+
+m = { "a": "foo", "b": "bar" }
+matched_foo = filter m as _, v { v is "foo" } // { "a": "foo" }
+```
+
+## Map Expression
+
+`map` is a [quantifier
+expression](/sentinel/language/spec#quantifier-expressions-any-all-filter-map) that
+returns a list, regardless of the input collection type. Each element within the
+input collection is evaluted according to the map expression body and appended
+to the result.
+
+```sentinel
+l = [1, 2]
+r = map l as v { v % 2 } // [false, true]
+
+m = { "a": "foo", "b": "bar" }
+r = map m as k, v { v } // ["foo", "bar"]
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/language/conditionals.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/language/conditionals.mdx
new file mode 100644
index 0000000000..1736795dbb
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/language/conditionals.mdx
@@ -0,0 +1,161 @@
+---
+page_title: Sentinel Language - Conditionals
+sidebar_current: docs-language-conditional
+description: |-
+ Conditional statements allow your policy to behave differently depending on a condition.
+layout: docs
+---
+
+# Language: Conditionals
+
+Conditional statements allow your policy to behave differently depending
+on a condition.
+
+Conditional statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## If Statements
+
+`if` statements only execute their bodies if a condition is met.
+The syntax of an `if` statement is:
+
+```sentinel
+if condition {
+ // ... this is executed if condition is true
+}
+```
+
+The `condition` must result in a boolean, such as by calling a function
+or evaluating a [boolean expression](/sentinel/language/boolexpr). If
+the `condition` is `true`, the body (within the `{}`) is executed. Otherwise,
+the body is skipped.
+
+Examples:
+
+```sentinel
+// This would execute the body
+value = 12
+if value is 18 {
+ print("condition met")
+}
+
+// Direct boolean values can be used
+value = true
+if value {
+ print("condition met")
+}
+
+// This would not execute the body since the boolean expression will
+// result in undefined.
+value = {}
+if value["key"] > 12 {
+ print("condition met")
+}
+```
+
+### Else, Else If
+
+An `else` clause can be given to an `if` statement to execute a body
+in the case the condition is _not met_. By putting another `if` statement
+directly after the `else`, multiple conditions can be tested for.
+The syntax is:
+
+```sentinel
+if condition {
+ // ...
+} else {
+ // ...
+}
+
+if condition {
+ // ...
+} else if other_condition {
+ // ...
+} else {
+ // ...
+}
+```
+
+### Scoping
+
+The body of an `if` statement does not create a new
+[scope](/sentinel/language/scope). Any variables assigned within the body
+of an if statement will modify the scope that the `if` statement itself
+is in.
+
+Example:
+
+```sentinel
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+```sentinel
+a = 18
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+## Case Statements
+
+`case` statements are a selection control mechanism that execute a clause based
+on matching expressions. It is worth noting that the expression for `case` is
+optional. When no expression is provided, it defaults the expression to `true`.
+Additionally, the order of clauses is important, as they are evaluated from top
+to bottom, executing the first match.
+The syntax of a case statement is:
+
+```sentinel
+case expression {
+ when clause_expression:
+ // executed when clause_expression and expression are equal
+ else:
+ // executed if no clause matches expression
+}
+```
+
+### When Clause
+
+Any clause that has an expression for comparison must use the `when` keyword.
+It accepts a list of expressions, seperated by a `,`.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+}
+```
+
+```sentinel
+case {
+ when x > 40:
+ return true
+}
+```
+
+### Else Clause
+
+The `else` keyword allows for capturing any expressions that have no matching
+`when` clause.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+ else:
+ return false
+}
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/language/functions.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/language/functions.mdx
new file mode 100644
index 0000000000..8214078b6b
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/language/functions.mdx
@@ -0,0 +1,229 @@
+---
+page_title: Sentinel Language - Functions
+sidebar_current: docs-language-functions
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Functions
+
+Functions allow you to create reusable code to perform computations.
+
+Below is a trivial example function that doubles a number and shows
+the main rule using the function:
+
+```sentinel playground
+double = func(x) {
+ return x * 2
+}
+
+main = rule { double(12) is 24 }
+```
+
+Functions may contain any expressions,
+[conditionals](/sentinel/language/conditionals),
+and [loops](/sentinel/language/loops). They must return a value
+at the end of their execution. If no reasonable return value exists,
+the function should return [undefined](/sentinel/language/undefined).
+
+## Function Types
+
+### Named Functions
+
+-> **NOTE:** Named functions must be created within the [package scope](/sentinel/language/scope#package-scope).
+
+Named functions are declared using the `func` keyword as its own statement.
+They provide a safe method of creating functions and have additional
+restrictions that do not apply to anonymous functions.
+
+Firstly, named functions cannot be re-assigned, and also cannot use a name
+that is already used elsewhere. For instance, the below example will error due
+to the attempt to reassign the named function identifier to a new value:
+
+```sentinel
+func sum(a, b) {
+ return a + b
+}
+
+sum = 4
+```
+
+Additionally, the following will error due to the named function attempting
+to make use of an already assigned identifier:
+
+```sentinel
+sum = 4
+
+func sum(a, b) {
+ return a + b
+}
+```
+
+Named functions are helpful for policy authors to declare critical functions
+whose value or implementation should not be changed.
+
+### Anonymous Functions
+
+An anonymous function is created by assigning a variable to a `func`. The
+variable can be reassigned at any time including to different value types.
+Anonymous functions are helpful for use cases like closures, where a function
+can return another function.
+
+```sentinel
+func makeAdder(a) {
+ return func(b) {
+ return a + b
+ }
+}
+```
+
+## Creating a Function
+
+A function can have zero or more parameters. These parameters are specified
+within parentheses `()` separated by commas. If a function has no parameters,
+the parentheses must still be present.
+
+A function must terminate with a `return` statement to return a value back to
+the caller. Only a single value can be returned. The type of a return can
+vary between return statements.
+
+Anonymous function example:
+
+```sentinel
+add1 = func(x) { return x + 1 }
+```
+
+Named function example:
+
+```sentinel
+func add1(x) {
+ return x + 1
+}
+```
+
+Both examples create a function that adds 1 to the parameter `x`.
+
+## Calling a Function
+
+Functions are called by accessing their name followed by parentheses with
+a list of comma-separated values for the parameters. If the function takes no
+parameters, an empty set of parentheses must still be used to denote a
+function call.
+
+To call the function in the example above:
+
+```sentinel
+x = 1
+y = add1(x)
+```
+
+## Scoping
+
+The body of a `func` creates a new [scope](/sentinel/language/scope).
+
+The parent scope is the scope in which the function is created. This allows
+for the creation of functions within functions that can access their outer
+scopes.
+
+Example:
+
+```sentinel
+f = func() {
+ a = 42
+ print(a) // 42
+ return undefined
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+f = func() {
+ a = 42
+ return undefined
+}
+
+print(a) // 18
+```
+
+And below is an example of creating a function in a function which uses
+outer values:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double()
+}
+
+print(f()) // 84
+```
+
+A more complex example below shows how scoping works when passing
+functions around as arguments or results:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double
+}
+
+double = f()
+print(double()) // 84
+```
+
+## Pass By Value
+
+The parameters to a function are passed by value, but not deep copied.
+This means that elements of collections can still be modified but the
+root value cannot be changed.
+
+Example:
+
+```sentinel
+f = func(x) {
+ x = "value"
+ return x
+}
+
+x = "outside"
+f(x)
+print(x) // "outside"
+```
+
+```sentinel
+f = func(x) {
+ append(x, "value")
+ return x
+}
+
+x = []
+f(x)
+print(x) // ["value"]
+```
+
+## Recursion
+
+A function is allowed to call other functions, including itself.
+This can be used to implement recursion. For example, the fibonacci sequence
+is implemented below:
+
+```sentinel
+fib = func(x) {
+ if x <= 0 {
+ return undefined
+ }
+
+ if x == 1 {
+ return 1
+ } else {
+ return x + fib(x - 1)
+ }
+}
+```
+
+Note that this example also shows using
+[undefined](/sentinel/language/undefined)
+as a return value in cases where a function has undefined behavior.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/language/imports.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/language/imports.mdx
new file mode 100644
index 0000000000..0ed1541b85
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/language/imports.mdx
@@ -0,0 +1,116 @@
+---
+page_title: Sentinel Language - Imports
+sidebar_current: docs-language-imports
+description: >-
+ Imports enable a Sentinel policy to access reusable libraries and external
+ data and functions.
+layout: docs
+---
+
+# Language: Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external
+data and functions. The exact list of available imports is dependent on the
+host system. Host systems may also make the available imports configurable
+via plugins.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that
+the import `calendar` allows access to engineer calendars. This import
+only exists for the purpose of this example and at the time of writing is
+not a real available import.
+
+```sentinel
+import "calendar"
+
+// Get the calendar for Bob for today
+bob_calendar = calendar.for("bob").today
+
+// Allow this policy to pass if Bob is not on vacation.
+main = rule { not bob_calendar.has_event("vacation") }
+```
+
+This example shows an import being used to deny a behavior if Bob is on
+vacation. Hopefully real policies do not depend on a single person being
+in the office, but the example shows the power of imports.
+
+In addition to consuming imports, you can also
+[extend Sentinel by writing custom imports](/sentinel/extending)
+This allows you to add any arbitrary behavior to your Sentinel-protected
+systems.
+
+## Importing
+
+Whether accessing a built-in library or an external plugin, the syntax
+for an import is the same:
+
+```sentinel
+import "name"
+```
+
+This adds the import with the name `name`. It can be referenced using
+the identifier `name`. For example, if the import above exposed first
+and last name data, you could access it via `name.first` or `name.last`.
+
+You can shadow imports by assigning a variable. In the example below,
+the import `name` becomes unavailable within the function because it is
+shadowed by a variable of the same name.
+
+Shadowing an import is not the same as overwriting a variable. Imports
+are not part of the [scope](/sentinel/language/scope), meaning that
+the shadow only exists for the scope it is created within. For everything
+else, the import works even after it is shadowed. The example below shows that
+as well.
+
+```sentinel
+import "name"
+
+f = func() {
+ name = "jane"
+ return name
+}
+
+print(f()) // "jane"
+print(name.first) // "bob"
+```
+
+## Aliases
+
+An import can be aliased to another name using the syntax below:
+
+```sentinel
+import "name" as other
+```
+
+This would make the import "name" available as `other`.
+
+An import may only be imported once, regardless of aliases. The following
+would be **invalid** and would result in an error:
+
+```sentinel
+import "name" as one
+import "name" as two
+```
+
+## Accessing Import Data
+
+Imports are accessed with dot-separated identifiers, such as `name.first` or
+`name.stage.first`. These are called _selector expressions_. An import may
+return nested data that further can be accessed via selector expressions.
+
+In the first example on this page with the "calendar" import, we see this:
+
+```sentinel
+import "calendar"
+
+a = calendar.for("bob") // selector expression calendar.for
+b = a.today // selector expression on the result
+c = b.vacation // accessing further nested data
+```
+
+The exact structure of an import is dependent on the import. Please reference
+the documentation for an import to learn more.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/language/index.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/language/index.mdx
new file mode 100644
index 0000000000..7f588c94cd
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/language/index.mdx
@@ -0,0 +1,114 @@
+---
+page_title: Sentinel Language
+sidebar_current: docs-language-basics
+description: |-
+ Sentinel policies are written using the Sentinel language. This language
+ is easy to learn and easy to write. You can learn the Sentinel language
+ and be productive within an hour. Learning Sentinel doesn't require any
+ formal programming experience.
+layout: docs
+---
+
+# Sentinel Language
+
+Sentinel policies are written using the Sentinel language. This language
+is easy to learn and easy to write. You can learn the Sentinel language
+and be productive within an hour. Learning Sentinel doesn't require any
+formal programming experience.
+
+This language guide will contain many details that are not necessary to
+be productive with Sentinel or are targeted to those who are looking for
+exact information about the language (such as what types of line endings are
+allowed). You can safely ignore these sentences.
+
+You may also view the
+[official language specification](/sentinel/language/spec)
+This is a specific and detailed document on the syntax and behavior of
+the language primarily intended for implementation creators and to disambiguate
+the language.
+
+## Simplest Example
+
+The example below is about the simplest practical example of Sentinel.
+It is reasonable to imagine this as a realistic policy. This shows that
+in most cases, Sentinel will be extremely simple:
+
+```sentinel
+main = rule { request.method is "GET" and request.headers contains "X-Key" }
+```
+
+## Files
+
+Sentinel policies are single files that end in the `.sentinel` file extension.
+There is currently no built-in mechanism to Sentinel for merging multiple
+files. This is purposefully done to make Sentinel policies easy to submit
+to systems that support Sentinel policies.
+
+Sentinel policy files must be UTF-8 encoded and can end in both
+Unix (LF) or Windows (CRLF) line breaks. When running the
+[auto-formatter](#),
+line endings will always use Unix line breaks.
+
+## Ordering
+
+Sentinel policies are executed top-down. For example:
+
+```sentinel
+a = 1 // a = 1 here
+b = a + 1 // b = 2 here
+a = 3 // a = 3, b = 2
+```
+
+In this example, the value of `a` and `b` is shown at each line. Since Sentinel
+executes values top-down, the final value of `a` is 3 and `b` is 2. `b` _does
+not_ become 4.
+
+## Main
+
+Sentinel expects there to be a `main` [rule](/sentinel/language/rules).
+The value of this rule is the result of the entire policy.
+
+The result of the policy depends on the evaluated contents of the `main` rule.
+For booleans, a policy passes on a `true` value, and fails on a `false` value.
+Other types generally follow a zero or zero-length pattern for determining
+success.
+
+| Type | Passing Condition | Failing Condition |
+| ------- | ----------------- | -------------------------- |
+| Boolean | `true` | `false` |
+| String | `""` | Any non-zero length string |
+| Integer | `0` | Any non-zero value |
+| Float | `0.0` | Any non-zero value |
+| List | `[]` | Any non-zero length list |
+| Map | `{}` | Any non-zero length map |
+
+A value of main that falls outside of the above types will result in a policy
+error. As a special case, if `main` evaluates to an undefined value, the error
+message will indicate as such, with a reference to where the undefined value was
+encountered.
+
+## More Complex Example
+
+The simple example above is a full working example. In our experience with
+Sentinel, many policies can be representing using this simple form. However,
+to show more features of the language, a more complex example is shown below.
+This example is also a realistic example of what Sentinel may be used for.
+
+```sentinel
+import "units"
+
+memory = func(job) {
+ result = 0
+ for job.groups as g {
+ for g.tasks as t {
+ result += t.resources.memory else 0
+ }
+ }
+
+ return result
+}
+
+main = rule {
+ memory(job) < 1 * units.gigabyte
+}
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/language/lists.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/language/lists.mdx
new file mode 100644
index 0000000000..bc5269d8eb
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/language/lists.mdx
@@ -0,0 +1,135 @@
+---
+page_title: Sentinel Language - Lists
+sidebar_current: docs-language-lists
+description: Lists are a collection of zero or more values.
+layout: docs
+---
+
+# Language: Lists
+
+Lists are a collection of zero or more values.
+
+Lists can be created using by wrapping values in `[]` and separating them
+by commas. An optional trailing comma is allowed. List elements can be
+differing types. Examples:
+
+```sentinel
+[] // An empty list
+["foo"] // Single element list
+["foo", 1, 2, true] // Multi element list with different types
+["foo", [1, 2]] // List containing another list
+```
+
+A list can be [sliced](/sentinel/language/slices) to create sublists.
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for value inclusion in a list.
+
+## Accessing Elements
+
+List elements can be accessed with the syntax `name[index]` where
+`index` is zero-indexed.
+
+A negative index accesses the list in reverse. It is the same as
+reversing a list and then using a positive index. Similar to a positive
+index, it is bounded by the length of the list as a negative value.
+
+Accessing beyond the length of the list results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+a = ["foo", 1, true, [1, 2]]
+
+a[0] // "foo"
+a[2] // true
+a[4] // undefined
+a[-2] // true
+a[-4] // "foo"
+a[-5] // undefined
+a[3][1] // 2
+```
+
+## List Append
+
+Values can be appended to a list using the built-in
+[append](/sentinel/functions/append) function.
+
+This modifies the list in-place and returns
+[undefined](/sentinel/language/undefined). For more information on
+why `append` behaves this way, please read the full documentation for the
+[append](/sentinel/functions/append) function.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append([1,2], "foo") // [1, 2, "foo"]
+append([1,2], [3]) // [1, 2, [3]]
+append(1, 3) // error()
+```
+
+## List Concatenation
+
+Two lists can be concatenated using the `+` operator or the shorthand
+`+=` assignment operator. For the `+` operator, a new list is returned.
+For `+=`, the left-hand list is modified in place.
+
+Examples:
+
+```sentinel
+[1] + [2] // [1, 2]
+[1] + [[1]] // [1, [1]]
+[1] + 1 // error
+
+a = [1]
+a += [2] // a = [1, 2]
+a += 3 // error
+```
+
+## List Length
+
+The length of a list can be retrieved using the
+[length](/sentinel/functions/length) function.
+
+Examples:
+
+```sentinel
+length([]) // 0
+length(["foo"]) // 1
+```
+
+## Removing Items From a List
+
+You can use a combination of [list concatenation](#list-concatenation) and
+[slicing](/sentinel/language/slices) to remove elements from a list.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+a = a[:2] + a[3:] // [1, 2, 4, 5]
+```
+
+The shorthand shown here is effectively the same as `a[0:2] + a[3:length(a)]`,
+which creates a new list out of the concatenation two sub-lists composed of the
+first two elements, and the rest of the list starting at index 3. This
+effectively removes the 3rd element from the list (index 2).
+
+## List Comparison
+
+Lists may be [compared](/sentinel/language/boolexpr#comparison-operators)
+for equality. Lists are equal if they are of equal length and their
+corresponding elements are comparable and equal. Lists with the same elements
+but in a different order are not equal.
+
+```sentinel
+[1, 2] is [1, 2] // true
+[1, 2] is [2, 1] // false
+["a"] is ["a", "b"] // false
+["a", ["b", "c"]] is ["a", ["b", "c"]] // true
+```
+
+List comparison speed is O(N), meaning that the speed of the comparison is
+_linearly_ proportional to the number of elements in the list. The more
+elements, the more iterations that are necessary to verify equality.
+
+-> The N-value quoted above should account for the sum of the elements of _all_
+lists in the subjects of comparison, as list comparison will recurse into these
+lists to check for equality.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/language/logging-errors.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/language/logging-errors.mdx
new file mode 100644
index 0000000000..705a72cbd4
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/language/logging-errors.mdx
@@ -0,0 +1,52 @@
+---
+page_title: Sentinel Language - Logging and Errors
+sidebar_current: docs-language-log
+description: The built-in functions `print` and `error` can be used for logging and errors.
+layout: docs
+---
+
+# Language: Logging and Errors
+
+The built-in functions `print` and `error` can be used for logging
+and errors. Logging is helpful to understand how a policy is executing
+in development. And errors are useful to halting execution immediately in
+certain scenarios.
+
+## Print
+
+The `print` function is used to output debug information. The exact location
+where this print statements go is dependent on the host application and
+the Sentinel runtime itself.
+
+The `print` function takes zero or more Sentinel values as arguments.
+Each value is formatted for human-friendly output and space-separated.
+Example:
+
+```sentinel
+value = 42
+print("the value is", value) // the value is 42
+
+map = { "foo": false }
+print(map) // { "foo": false }
+
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // false
+```
+
+## Errors
+
+Certain actions in Sentinel, such as accessing an undefined variable,
+attempting to add two incompatible types, etc. result in _runtime errors_.
+These errors halt the execution of a policy immediately. The pass/fail result
+of a policy is considered fail.
+
+Runtime errors can be manually triggered using the `error` function.
+The `error` function behaves exactly like the `print` function except that
+execution is halted immediately.
+
+This should only be used in scenarios where the policy _must fail_ due to
+some unexpected or invalid condition. The line between `error` and
+[undefined](/sentinel/language/undefined) can sometimes be unclear. Undefined
+is recommended when a policy can conceivably recover or safely ignore the
+undefined behavior. An error should be used when the policy should fail and
+notify someone regardless.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/language/loops.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/language/loops.mdx
new file mode 100644
index 0000000000..622732bd35
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/language/loops.mdx
@@ -0,0 +1,92 @@
+---
+page_title: Sentinel Language - Loops
+sidebar_current: docs-language-loops
+description: >-
+ Loop statements allow you to execute a body of code for each element in a
+ collection or for some fixed number of times.
+layout: docs
+---
+
+# Language: Loops
+
+Loop statements allow you to execute a body of code for each element in
+a collection or for some fixed number of times.
+
+Loop statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## For Statements
+
+`for` statements allow repeated execution of a block for each
+element in a collection.
+
+Example:
+
+```sentinel
+// A basic sum
+count = 0
+for [1, 2, 3] as num {
+ count += num
+}
+```
+
+The syntax is `for COLLECTION as value`. This will iterate over the
+collection, assigning each element to `value`. In the example above,
+each element is assigned to `num`. The body is executed for each element.
+In the example above, the body adds `num` to the `count` variable. This
+creates a basic sum of all values in the collection.
+
+For a map, the assigned element is the key in the map. In the example
+below, `name` would be assigned map keys.
+
+```sentinel
+list = []
+for { "a": 1, "b": 2 } as name {
+ append(list, name)
+}
+
+print(list) // ["a" "b"]
+```
+
+An alternate syntax is `for COLLECTION as key, value`. This will assign
+both the key and value to a variable. For a list, the key is the element
+index. For a map, it is the key and value is assigned the element value.
+Example:
+
+```sentinel
+count = 0
+for { "a": 1, "b": 2 } as name, num {
+ count += num
+}
+
+print(count) // 3
+```
+
+## Scoping
+
+The body of a `for` statement creates a new
+[scope](/sentinel/language/scope). If a variable is assigned within
+the body of a for statement that isn't assigned in a parent scope,
+that variable will only exist for the duration of the body execution.
+
+Example:
+
+```sentinel
+for list as value {
+ a = 42
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+for list as value {
+ a = 42
+}
+
+print(a) // 18
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/language/maps.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/language/maps.mdx
new file mode 100644
index 0000000000..298bf9a567
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/language/maps.mdx
@@ -0,0 +1,121 @@
+---
+page_title: Sentinel Language - Maps
+sidebar_current: docs-language-maps
+description: >-
+ Maps are a collection of zero or more key/value pairs. These are useful for
+ storing values that are looked up by a unique key.
+layout: docs
+---
+
+# Language: Maps
+
+Maps are a collection of zero or more key/value pairs. These are useful
+for storing values that are looked up by a unique key.
+
+Maps can be created using `{}` and specifying key/value pairs. Keys and
+values can be differing types. An optional trailing comma is allowed.
+Examples:
+
+```sentinel
+// Empty map
+{}
+
+// Map with a single value on one line
+{ "key": "value" }
+
+// Map with multiple values with differing types on multiple lines
+{
+ "key": "value",
+ 42: true,
+}
+```
+
+Maps are **unordered**. When
+[looping](/sentinel/language/loops)
+over a map, the key/value pairs can be returned in any order.
+
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for key inclusion in a map.
+
+## Accessing Elements
+
+Map elements can be accessed with the syntax `name[key]`. This looks up
+a value by a key.
+
+Accessing a key that doesn't exist results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+map = { "key": "value", 42: true, }
+
+map["key"] // "value"
+map[42] // true
+map[0] // undefined
+```
+
+## Modifying or Adding Elements
+
+Elements can be added or modified in a map by assigning to `name[key]`.
+If the key doesn't exist, the value is added. If the key already exists,
+the value is overridden.
+
+```sentinel
+map = { "key": "value" }
+
+map[42] = true // Add a new key/value
+map["key"] = 12 // Modify the value of "key"
+```
+
+## Deleting Elements
+
+An element can be deleted from a map using the
+[delete](/sentinel/functions/delete) function.
+
+Examples:
+
+```sentinel
+map = { "key": "value" }
+delete(map, "key") // map is now empty
+delete(map, "other") // no effect for non-existent key
+```
+
+## Keys and Values
+
+The keys and values of a map can be retrieved as
+[lists](/sentinel/language/lists) using the
+[keys](/sentinel/functions/keys) and
+[values](/sentinel/functions/values) functions.
+
+Because maps are unordered, the keys and values are returned in an
+unspecified order. It should not be assumed that keys and values will be
+returned in a consistent order.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+## Map Comparison
+
+Maps may be [compared](/sentinel/language/boolexpr#comparison-operators) for
+equality. Maps are equal if they are of equal length and both their
+corresponding keys and values are comparable and equal.
+
+```sentinel
+{"foo": "bar"} is {"foo": "bar"} // true
+{"foo": "bar"} is {"baz": "bar"} // false
+{"foo": "bar"} is {"foo": "baz"} // false
+{"foo": "bar"} is {"foo": "bar", "baz": "qux"} // false
+{1: "a"} is {1.0: "a"} // true (int/float comparable)
+
+// also true (maps are not ordered):
+{"m": {"a": "b"}, "l": ["a"]} is {"l": ["a"], "m": {"a": " b"}}
+```
+
+-> The current worst-case comparison speed for maps is O(N²), or _quadratic
+time_. This is the square of the cumulative elements or the parent and all child
+maps contained within the map. Consider this when making comparisons on larger,
+more complex maps. This may be optimized in the future.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/language/parameters.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/language/parameters.mdx
new file mode 100644
index 0000000000..2e008d469b
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/language/parameters.mdx
@@ -0,0 +1,146 @@
+---
+page_title: Sentinel Language - Parameters
+sidebar_current: docs-language-parameters
+description: >-
+ Parameteres allow a Sentinel policy to describe variables that are expected
+ to be supplied by the calling application, in addition to any default value.
+layout: docs
+---
+
+# Language: Parameters
+
+Sentinel allows a policy author to supply parameters to help facilitate policy
+reuse and ensure sensitive values do not need to be hard-coded in a policy.
+
+Parameters are supplied by using the `param` keyword, followed by an identifier.
+A default value can also be supplied by using the `default` keyword.
+
+```sentinel
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+```
+
+Once declared, parameters can be used like any other variable, including being
+re-assigned.
+
+```sentinel
+param foo default 1 // 1 (default)
+
+foo = foo + 1 // 2
+```
+
+## Variable Descriptions
+
+You can supply a description to a parameter by adding a comment at the top of
+it. This value can be communicated to a specific implementation of Sentinel to
+provide information about what the parameter is for during configuration.
+
+```sentinel
+// An example parameter. Must be supplied or the policy will fail.
+param foo
+```
+
+## Supplying Parameter Values Using the Sentinel CLI
+
+In a production implementation, supplying parameters to a policy is an
+implementation-specific detail - see the documentation for your particular
+implementation for details.
+
+Using the [Sentinel CLI](/sentinel/commands), you can supply parameters one of
+four ways.
+
+### Supplying Parameter Values Using the Configuration File
+
+You can supply parameters using the
+[`param`](/sentinel/configuration#parameters) section of the
+[configuration file](/sentinel/configuration).
+
+```hcl
+param "foo" {
+ value = "bar"
+}
+```
+
+This method works for both `sentinel apply` and `sentinel test`.
+
+In addition to the above, you can supply targeted parameters to each
+[policy block](/sentinel/configuration#policies) in the configuration file.
+
+```hcl
+policy "foo" {
+ source = "foo.sentinel"
+ enforcement_level = "hard-mandatory"
+ params = {
+ "name" = "Sample"
+ }
+}
+```
+
+### Supplying Parameter Values Using the Environment
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can supply a value using environment variables - prefix the parameter with
+`SENTINEL_PARAM_`, using the name of the parameter to supply.
+
+```plaintext
+SENTINEL_PARAM_foo=bar sentinel apply policy.sentinel
+```
+
+### Supplying Parameter Values Using CLI Arguments
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can also use the `-param` CLI argument to supply parameter in a `key=value`
+pair.
+
+```plaintext
+sentinel apply -param foo=bar policy.sentinel
+```
+
+### Interactive CLI Prompting
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+If a required value has not been supplied when a policy is run with `sentinel apply`, it will be prompted for, along with its description:
+
+
+
+ $ sentinel apply policy.sentinel
+
+ policy.sentinel:2:7: requires value for parameter foo
+
+ An example parameter. Must be supplied or the policy will fail.
+
+
+ Values can be strings, floats, or JSON array or object values. To
+ force
+
+ strings, use quotes.
+
+
+ Enter a value: bar
+
+
+
+ Pass
+
+
+
+
+### CLI Value Format
+
+-> **NOTE:** This section contains details for the parameter features supported by `sentinel apply`.
+
+The CLI takes either strings, or JSON numbers, arrays, or maps. If you need a
+literal string value, quote the value.
+
+```sentinel
+foo // string
+42 // number (float)
+"42" // string ("42", without quotes)
+[1, 2] // array (list)
+{"a": "b"} // object (map)
+```
+
+-> **NOTE:** Boolean values are not supported by this method.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/language/rules.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/language/rules.mdx
new file mode 100644
index 0000000000..e31924d414
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/language/rules.mdx
@@ -0,0 +1,204 @@
+---
+page_title: Sentinel Language - Rules
+sidebar_current: docs-language-rules
+description: >-
+ Rules form the basis of a policy by representing behavior that is either
+ passing or failing (true or false).
+layout: docs
+---
+
+# Language: Rules
+
+Rules form the basis of a policy by representing behavior that is either passing
+or failing. A policy can be broken down into a set of rules. Breaking down a
+policy into a set of rules can make it more understandable and aids with
+testing.
+
+An example is shown below:
+
+```sentinel playground
+weather = "sunny"
+day = "wednesday"
+is_sunny = rule { weather is "sunny" }
+is_wednesday = rule { day is "wednesday" }
+
+main = rule {
+ is_sunny and
+ is_wednesday
+}
+```
+
+A rule contains a single expression. This can be a simple [boolean
+expression](/sentinel/language/boolexpr), or an expression representing the
+discovery of a set of violations using more in-depth expressions like [`filter`
+and `map`](/sentinel/language/collection-operations). You can split expressions
+into multiple lines for readability, as you would with any other expression
+within Sentinel.
+
+A rule is also _lazy_ and _memoized_. _Lazy_ means that the rule is only
+evaluated when it is actually required, not at the point that it is
+created. This is covered in more detail in the [lazy](#lazy-and-memoized) section.
+_Memoized_ means that the value is only computed once and then saved. Once
+a rule is evaluated, the result of it is reused anytime the rule is referenced
+again.
+
+## Making rules easier to understand
+
+Rules are an abstraction that make policies much more understandable
+by making it easier for humans to see what the policy is trying to do.
+
+For example, consider the policy before which doesn't abstract into rules:
+
+```sentinel
+main = rule {
+ ((day is "saturday" or day is "sunday") and homework is "") or
+ (day in ["monday", "tuesday", "wednesday", "thursday", "friday"] and
+ not school_today and homework is "")
+}
+```
+
+Assume that `day`, `homework`, and `school_day` are available.
+
+In plain English, this policy is trying to say: you can play with your friends
+only on the weekend as long as there is no homework, or during the week if
+there is no school and no homework.
+
+Despite the relatively simple nature of this policy (a few lines, about 5
+logical expressions), it is difficult to read and understand, especially if
+you've not seen it before.
+
+The same policy using rules as an abstraction:
+
+```sentinel
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+is_valid_weekend = rule { is_weekend and homework is "" }
+is_valid_weekday = rule { not is_weekend and not school_today and homework is "" }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+By reading the names of the rules, its much clearer to see what each individual
+part of the policy is trying to achieve. The readability is improved even
+further when adding comments to the rules to explain them further, which is
+a recommended practice:
+
+```sentinel
+// A weekend is Sat or Sun
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+// A valid weekend is a weekend without homework
+is_valid_weekend = rule { is_weekend and homework is "" }
+
+// A valid weekday is a weekday without school
+is_valid_weekday = rule { not is_weekend and not school_today and homework is "" }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+## "When" Predicates
+
+A rule may have an optional `when` predicate attached to it. When this is
+present, the rule is only evaluated when the predicate results in `true`.
+Otherwise, the rule is not evaluated and the result of the rule is always
+`true`.
+
+"When" predicates should be used to define the context in which the rule
+is semantically meaningful. It is common when translating human language
+policy into Sentinel to have scenarios such as "when the key has a prefix
+of `/account/`, the remainder must be numeric." In that example, when the
+key _does not_ have the specified prefix, the policy doesn't apply.
+
+Assume you have rules `is_prefix` and `is_numeric` to check both the
+conditions in the example above. An example is shown with and without
+the "when" predicate:
+
+```sentinel
+example_no_when = rule { (is_prefix and is_numeric) or not is_prefix }
+
+example_when = rule when is_prefix { is_numeric }
+```
+
+The rules are equivalent in behavior for all values of `is_prefix` and
+`is_numeric`, but the second rule is more succint and easier to understand.
+
+## Testing
+
+To verify a policy works correct, the
+[built-in Sentinel test framework](/sentinel/writing/testing)
+uses rules as the point where you can assert behavior. You say that
+you expect certain rules to be true or false. By doing so, you ensure that
+the policy results in the value you expect using the rule flow that you
+expect.
+
+Therefore, in addition to readability, we recommend splitting policies into
+rules to aid testability.
+
+## Non-Boolean Values
+
+Sentinel supports non-boolean values in rules. This can be useful for passing
+more detail along to a calling integration when more detail is required than a
+boolean value would be able to communicate. This data shows up in the [policy
+trace](/sentinel/writing/tracing) and can be utilized in different ways
+depending on the integration you are using Sentinel with.
+
+Consider a different take on our policy above:
+
+```sentinel
+// A policy to check a set of scheduled days to determine what days you can't
+// come out and play, based on the day not being a weekend day and there being
+// homework to do.
+
+param days
+
+main = rule {
+ filter days as d {
+ d.day not in ["saturday", "sunday"] and
+ d.homework is not ""
+ }
+}
+```
+
+When given a value in the set of `days` that has a `day` that is a weekday, and
+`homework` present, the policy will fail. Additional, when tracing was enabled,
+the days that were detected as violating the policy would be given in the trace
+for the `main` rule.
+
+Generally, for non-boolean values, a policy fails on non-zero data. See the
+details on [the `main` rule](/sentinel/language#main) for more details.
+
+The result of a rule must be either a boolean, string, integer, float, list, or
+map. All other types will result in a runtime error.
+
+## Lazy and Memoized
+
+A rule is _lazy_ and _memoized_.
+
+_Lazy_ means that the rule is only evaluated when it is actually required,
+not at the point that it is created. And _memoized_ means that the value is
+only computed once and then saved. Once a rule is evaluated, the result of it
+is reused anytime the rule is referenced again.
+
+Both of these properties have important implications for Sentinel
+policies:
+
+**Performance:** Rules are a way to improve the performance of a Sentinel
+policy. If you have a boolean expression that is reused a lot, a rule will
+only be evaluated once. In the example shown above, `is_weekend` is used
+multiple times, but will only have to be evaluated once.
+
+**Behavior:** Rules accessing variables see the value of those variables
+at _the time they are evaluated_. This can lead to surprising behavior
+in some cases. For example:
+
+```sentinel playground
+a = 1
+b = rule { a == 1 }
+a = 2
+main = b
+```
+
+In this example, `main` will actually result to `false`. It is evaluated
+when it is needed, which is when the policy executes `main`. At this point,
+`a` is now 2 and `b` has not been evaluated yet. Therefore, `b` becomes `false`.
+All future references to `b` will return `false` since a rule is memoized.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/language/scope.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/language/scope.mdx
new file mode 100644
index 0000000000..f498e26dc2
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/language/scope.mdx
@@ -0,0 +1,47 @@
+---
+page_title: Sentinel Language - Scope
+sidebar_current: docs-language-scope
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Scope
+
+Scope is the context in which variables are created and accessed. If a
+variable exists in a parent scope, it is accessed and can be modified.
+Otherwise, the variable is created in your current scope.
+
+Scopes are created with _blocks_. A block is a possibly empty sequence
+of statements within matching brace brackets `{}`. You may nest blocks
+to create nested scopes.
+
+## Package Scope
+
+The package scope is the top level scope within a policy file and encapsulates
+the entire file contents. Imports, parameters and named functions must be
+declared within the package scope.
+
+## Implicit Scopes
+
+Each `any`, `all`, and `for` statement is considered to be in its own block.
+Note that `if` statements _do not_ create their own block.
+
+## Examples
+
+The example policy below shows various effects of scopes:
+
+```sentinel
+a = 1
+print(a) // 1
+
+f = func() {
+ print(a) // 1
+ a = 12
+ b = 1
+ return undefined
+}
+f()
+
+print(a) // 12
+print(b) // undefined
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/language/slices.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/language/slices.mdx
new file mode 100644
index 0000000000..82dc7f4c18
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/language/slices.mdx
@@ -0,0 +1,45 @@
+---
+page_title: Sentinel Language - Slices
+sidebar_current: docs-language-slices
+description: >-
+ Slices are an efficient way to create a substring or sublist from an existing
+ string or list, respectively.
+layout: docs
+---
+
+# Language: Slices
+
+Slices are an efficient way to create a substring or sublist from an
+existing string or list, respectively.
+
+The syntax for creating a slice is:
+
+```sentinel
+a[low : high]
+```
+
+`low` is the lowest index to slice from. The resulting slice includes
+the value at `low`. `high` is the index to slice to. The resulting slice
+will not include the value at `high`. The length of the resulting list
+or string is always `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+
+a = "hello"
+b = a[1:4] // "ell"
+```
+
+## Convenience Shorthands
+
+Some convenience shorthands are available by omitting the value for either the
+low or high index in the slice expression: omitting `low` implies a low index of
+0, and omitting `high` implies a high index of the length of the list.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+
+b = a[:2] // [1, 2] (same as a[0:2])
+b = a[2:] // [3, 4, 5] (same as a[2:length(a)])
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/language/spec.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/language/spec.mdx
new file mode 100644
index 0000000000..d8efb378cd
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/language/spec.mdx
@@ -0,0 +1,1333 @@
+---
+page_title: Sentinel Language Specification
+sidebar_current: docs-language-spec
+description: This is the specification for the Sentinel policy language.
+layout: docs
+---
+
+# Sentinel Language Specification
+
+This is the specification for the Sentinel policy language.
+
+The Sentinel language is designed with policy enforcement in mind. It is dynamically typed and garbage collected and has explicit support for _rule_ construction representing boolean logic.
+
+The language is designed to be easy to learn and use by non-programmers. It is expected to be embedded within applications.
+
+## Table of Contents
+
+
+
+
+- [Source code representation](#source-code-representation)
+- [Declarations and Scope](#declarations-and-scope)
+- [Blocks](#blocks)
+- [Lexical Elements](#lexical-elements)
+ - [Comments](#comments)
+ - [Identifiers](#identifiers)
+ - [Keywords](#keywords)
+ - [Operators and Delimiters](#operators-and-delimiters)
+ - [Integer Literals](#integer-literals)
+ - [Floating-point Literals](#floating-point-literals)
+ - [String Literals](#string-literals)
+ - [Implicit Line Joining](#implicit-line-joining)
+ - [Whitespace](#whitespace)
+ - [Semicolons](#semicolons)
+- [Variables](#variables)
+- [Undefined](#undefined)
+- [Expressions](#expressions)
+ - [Operand](#operand)
+ - [Primary Expressions](#primary-expressions)
+ - [Null](#null)
+ - [Booleans](#booleans)
+ - [Boolean Literals](#boolean-literals)
+ - [Boolean Expressions](#boolean-expressions)
+ - [List Literals](#list-literals)
+ - [Map Literals](#map-literals)
+ - [Function Literals](#function-literals)
+ - [Rule Expressions](#rule-expressions)
+ - [Index Expressions](#index-expressions)
+ - [Selectors](#selectors)
+ - [Slice Expressions](#slice-expressions)
+ - [Calls](#calls)
+ - [Operators](#operators)
+ - [Operator Precedence](#operator-precedence)
+ - [Arithmetic Operators](#arithmetic-operators)
+ - [Integer operators](#integer-operators)
+ - [Integer overflow](#integer-overflow)
+ - [Floating-point operators](#floating-point-operators)
+ - [String Concatenation](#string-concatenation)
+ - [List Concatenation](#list-concatenation)
+ - [Comparison Operators](#comparison-operators)
+ - [Logical Operators](#logical-operators)
+ - [Set Operators](#set-operators)
+ - [Matches Operator](#matches-operator)
+ - [Else Operator](#else-operator)
+ - [Quantifier Expressions (any, all, filter, map)](#quantifier-expressions-any-all-filter-map)
+- [Statements](#statements)
+ - [Expression Statements](#expression-statements)
+ - [Assignments](#assignments)
+ - [If Statements](#if-statements)
+ - [Case Statements](#case-statements)
+ - [For Statements](#for-statements)
+ - [Break Statements](#break-statements)
+ - [Continue Statements](#continue-statements)
+ - [Return Statements](#return-statements)
+- [Imports](#imports)
+- [Parameters](#parameters)
+- [Built-in Functions](#built-in-functions)
+ - [Length](#length)
+ - [Collections](#collections)
+ - [List Append](#list-append)
+ - [Map Delete](#map-delete)
+ - [Keys and Values](#keys-and-values)
+ - [Range](#range)
+ - [Type Conversion](#type-conversion)
+ - [Printing](#printing)
+ - [Errors](#errors)
+- [Program Execution](#program-execution)
+
+
+
+## Source code representation
+
+Source code is Unicode text encoded in UTF-8. A "character" referenced in this document refers to a Unicode code point. Each code point is distinct: upper and lower case letters are different characters.
+
+The underscore character \_ (U+005F) is considered a "letter".
+
+```ebnf
+letter = unicode_letter | "_" .
+decimal_digit = "0" … "9" .
+octal_digit = "0" … "7" .
+hex_digit = "0" … "9" | "A" … "F" | "a" … "f" .
+```
+
+## Declarations and Scope
+
+A declaration binds an identifier to a value. An identifier is declared at the point it is first assigned. An assignment is considered a first assignment if the identifier hasn't already been previously declared in the current scope or any parent scopes.
+
+The scope of an identifier is the extent of source text in which the identifier denotes the specified value. Sentinel is lexically scoped using blocks.
+
+An identifier declared in a block may be redeclared in an inner block. While the identifier of the inner declaration is in scope, it denotes the value assigned in the inner declaration.
+
+## Blocks
+
+A block is a possibly empty sequence of statements within matching brace brackets.
+
+```ebnf
+Block = "{" StatementList "}" .
+StatementList = { Statement ";" } .
+```
+
+Blocks nest and affect scoping.
+
+In addition to explicit blocks in the source code, there are implicit blocks:
+
+1. The universe block encompasses all source text.
+2. Each program has a program block containing all source text for that program.
+3. Each "any", "all", and "for" statements is considered to be in its own implicit block. "if" does not create an implicit block.
+
+## Lexical Elements
+
+### Comments
+
+Comments are sections of source text used for documentation.
+
+```plaintext
+# Single line comment
+// Single line comment
+/* multi
+line
+ comment */
+```
+
+Three forms of comments are supported: two single-line forms and one multi-line form. A single-line comment begins with the token `//` or `#`. Everything between the starting token and the end of the line is ignored.
+
+A multi-line comment begins with the token `/*` and ends with the token `*/`. Everything between `/*` and `*/` is ignored.
+
+A comment may not start inside a string literal or inside another comment.
+
+A multi-line comment containing no newlines acts like a space.
+
+### Identifiers
+
+Identifiers name program entities such as rules, variables, and functions. An identifier is a sequence of one or more letters and digits. The first character in an identifier must be a letter.
+
+```ebnf
+identifier = letter { letter | unicode_digit } .
+```
+
+```sentinel
+a
+_a
+_A
+αβ
+```
+
+#### Pre-Declared Identifiers
+
+The following identifiers are implicitly declared in the [universe block](#blocks):
+
+```plaintext
+Constants:
+ false null true undefined
+
+Functions:
+ append bool delete error float int keys length print range string values
+```
+
+### Keywords
+
+The following keywords are reserved and may not be used as identifiers:
+
+```plaintext
+all
+any
+as
+break
+case
+continue
+default
+else
+empty
+filter
+for
+func
+if
+import
+map
+param
+return
+rule
+when
+```
+
+### Operators and Delimiters
+
+The following character sequences represent operators, delimiters, and other special tokens:
+
+```plaintext
++
+-
+*
+/
+%
++=
+-=
+*=
+/=
+%=
+==
+<
+>
+=
+!
+!=
+<=
+>=
+( )
+[ ]
+{ }
+,
+.
+:
+;
+and
+contains
+else
+in
+is
+matches
+not
+or
+xor
+```
+
+As a special case, `else` is both a keyword and operator, depending on the context. See [Else Operator](#else-operator) for more details.
+
+### Integer Literals
+
+An integer literal is a sequence of digits representing an integer constant. An optional prefix sets a non-decimal base: `0` for octal, `0x` or `0X` for hexadecimal. In hexadecimal literals, letters `a-f` and `A-F` represents values 10 through 15.
+
+Integers are signed 64-bit values (-9223372036854775808 to 9223372036854775807).
+
+```ebnf
+int_lit = decimal_lit | octal_lit | hex_lit .
+decimal_lit = ( "1" … "9" ) { decimal_digit } .
+octal_lit = "0" { octal_digit } .
+hex_lit = "0" ( "x" | "X" ) hex_digit { hex_digit } .
+```
+
+```
+42
+0600
+0xBadFace
+170141183460469231731687303715884105727
+```
+
+### Floating-point Literals
+
+A floating-point literal is a decimal representation of a floating-point constant. It has an integer part, a decimal point, a fractional part, and an exponent part. The integer and fractional part comprise decimal digits; the exponent part is an e or E followed by an optionally signed decimal exponent. One of the integer part or the fractional part may be elided; one of the decimal point or the exponent may be elided.
+
+Floating-point numbers are IEEE-754 64-bit floating numbers.
+
+```ebnf
+float_lit = decimals "." [ decimals ] [ exponent ] |
+ decimals exponent |
+ "." decimals [ exponent ] .
+decimals = decimal_digit { decimal_digit } .
+exponent = ( "e" | "E" ) [ "+" | "-" ] decimals .
+```
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+### String Literals
+
+String literals are character sequences between double quotes, as in "bar". Within the quotes, any character may appear except newline and unescaped double quote. The text between the quotes forms the value of the literal.
+
+A multi-character sequence beginning with a backslash encode values in various formats.
+
+Backslash escapes allow arbitrary values to be encoded as ASCII text. There are four ways to represent the integer value as a numeric constant: `\x` followed by exactly two hexadecimal digits; `\u` followed by exactly four hexadecimal digits; `\U` followed by exactly eight hexadecimal digits, and a plain backslash `\` followed by exactly three octal digits. In each case the value of the literal is the value represented by the digits in the corresponding base.
+
+After a backslash, certain single-character escapes represent special values:
+
+```plaintext
+\a U+0007 alert or bell
+\b U+0008 backspace
+\f U+000C form feed
+\n U+000A line feed or newline
+\r U+000D carriage return
+\t U+0009 horizontal tab
+\v U+000b vertical tab
+\\ U+005c backslash
+\" U+0022 double quote
+```
+
+The three-digit octal (\nnn) and two-digit hexadecimal (\xnn) escapes represent individual bytes of the resulting string; all other escapes represent the (possibly multi-byte) UTF-8 encoding of individual characters. Thus inside a string literal \377 and \xFF represent a single byte of value 0xFF=255, while ÿ, \u00FF, \U000000FF and \xc3\xbf represent the two bytes 0xc3 0xbf of the UTF-8 encoding of character U+00FF.
+
+A string value is a (possibly empty) sequence of bytes. Strings are immutable: once created, it is impossible to change the contents of a string.
+
+The length of a string s (its size in bytes) can be discovered using the built-in function `length`. An individual character (of type string) can be accessed by integer indices 0 through `length(s)-1`.
+
+```ebnf
+string_lit = `"` { unicode_value | byte_value } `"` .
+unicode_value = unicode_char | little_u_value | big_u_value | escaped_char .
+byte_value = octal_byte_value | hex_byte_value .
+octal_byte_value = `\` octal_digit octal_digit octal_digit .
+hex_byte_value = `\` "x" hex_digit hex_digit .
+little_u_value = `\` "u" hex_digit hex_digit hex_digit hex_digit .
+big_u_value = `\` "U" hex_digit hex_digit hex_digit hex_digit
+ hex_digit hex_digit hex_digit hex_digit .
+escaped_char = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | `"` ) .
+```
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+### Implicit Line Joining
+
+Expressions can be split over more than one line. For example:
+
+```sentinel
+a or
+b or # This is a comment
+c
+```
+
+Implicitly continued lines can have trailing comments. Blank continued lines are allowed.
+
+### Whitespace
+
+Whitespace is needed to separate tokens, but no distinction is made between the number and combination of whitespace characters. Whitespace characters can be space (U+0020), horizontal tabs (U+0009), carriage returns (U+000D), and newlines (U+000A).
+
+```sentinel
+a or b
+
+a or b
+
+a or
+ b
+
+rule { a or b }
+
+rule {
+ a or b }
+
+rule {
+ a or
+ b
+}
+```
+
+### Semicolons
+
+The formal grammar uses semicolons ";" as terminators in a number of productions.
+It is idiomatic Sentinel source to omit semicolons in most cases.
+
+A semicolon is automatically inserted into the token stream immediately after
+a line's final token if that token is:
+
+- An identifier
+- An integer, float, or string literal
+- The keyword `break`, `continue`, or `return`
+- The delimiter `)`, `]`, or `}`
+
+## Variables
+
+A variable is a storage location for holding a value.
+
+A variable has a dynamic type, which is the concrete type of the value assigned to the variable at run time. The dynamic type may vary during execution and change as a result of further assignments.
+
+A variable's value is retrieved by referring to the variable in an expression; it is the most recent value assigned to the variable. If a variable has not yet been declared (initially assigned), it is an error.
+
+```sentinel
+x = 7 // x is type int
+x = "foo" // x is now type string
+
+x = y // error if y is not previously assigned
+```
+
+## Undefined
+
+The value denoted by the keyword `undefined` represents undefined behavior or values. It can be created directly using the keyword `undefined`. It is also returned as a result of expressions in specified cases.
+
+`undefined` is a valid operand for any operations. Only `undefined or true` will result in true. All other operations result in `undefined`. An exception is if `undefined` is not reached as a result of short-circuit operations.
+
+```sentinel
+undefined OR true = true
+undefined OR false = undefined
+undefined OR undefined = undefined
+undefined AND true = undefined
+undefined AND false = undefined
+undefined AND undefined = undefined
+undefined XOR true = undefined
+undefined XOR false = undefined
+undefined XOR undefined = undefined
+
+// Short-circuit examples
+false OR true OR undefined = true
+false OR undefined OR true = true
+true AND false AND undefined = false
+true AND undefined AND false = undefined
+
+// Non-logical operators
+undefined + 5 = undefined
+-undefined = undefined
+!undefined = undefined
+```
+
+If the result of the main rule is `undefined`, it is treated as `false` but is indicative of erroneous logic.
+
+## Expressions
+
+### Operand
+
+Operands denote the elementary values in an expression. An operand may be a literal, an identifier denoting a variable, rule, or function, or a parenthesized expression.
+
+```ebnf
+Operand = Literal | OperandName | MethodExpr | "(" Expression ")" .
+Literal = BasicLit | MapLit | ListLit | FunctionLit | RuleLit .
+BasicLit = int_lit | float_lit | string_lit .
+OperandName = identifier .
+```
+
+### Primary Expressions
+
+Primary expressions are the operands for unary and binary expressions.
+
+```ebnf
+PrimaryExpr =
+ Operand |
+ PrimaryExpr Selector |
+ PrimaryExpr Index |
+ PrimaryExpr Slice |
+ PrimaryExpr Arguments .
+
+Selector = "." identifier .
+Index = "[" Expression "]" .
+Slice = "[" [ Expression ] ":" [ Expression ] "]" .
+Arguments = "(" [ Expression { "," Expression } ] ")" .
+```
+
+```sentinel
+x
+2
+(s + ".txt")
+f(3.1415, true)
+m["foo"]
+s[i : j + 1]
+obj.color
+f.p[i].x()
+x is empty
+x is not empty
+```
+
+### Null
+
+The reserved word `null` denote the singleton value `null`. Null represents the explicit absence of a value. Behavior of `null` within expressions is specified explicitly for each expression.
+
+### Booleans
+
+### Boolean Literals
+
+The reserved words `true` and `false` denote objects that represent the boolean values `true` and `false`, respectively. These are boolean literals.
+
+```ebnf
+BoolLit = "true" | "false" .
+```
+
+#### Boolean Expressions
+
+Boolean expressions are expressions that must result in a boolean value. Any other value type becomes the `undefined` value.
+
+```ebnf
+BoolExpr = Expression .
+```
+
+### List Literals
+
+A list literal denotes a list, which is an integer indexed collection of values.
+
+```ebnf
+ListLit = "[" [ ElementList [ "," ] ] "]" .
+ElementList = Element { "," Element } .
+Element = Expression | LiteralValue .
+```
+
+A list may contain zero or more values. The number of values in a list is its length.
+
+A list has a set of indices. An empty list has an empty set of indices. A non-empty list has the index set `{0...n - 1}` where `n` is the length of the list. Attempting to access a list using an index that is not a member of its set of indices results in the `undefined` value.
+
+### Map Literals
+
+A map literal denotes a map, which is an unordered group of elements indexed by a set of unique keys.
+
+```ebnf
+MapLit = "{" [ KeyedElementList [ "," ] ] "}" .
+KeyedElementList = KeyedElement { "," KeyedElement } .
+KeyedElement = Element ":" Element .
+```
+
+Keys can only be a boolean, numeric, or string type.
+
+The value of a non-existent key is the `undefined` value.
+
+### Function Literals
+
+A function literal represents a function.
+
+```ebnf
+FunctionLit = "func" Function .
+Function = Parameters FunctionBody .
+FunctionBody = Block .
+Parameters = "(" [ IdentifierList [ "," ] ] ")" .
+IdentifierList = identifier { "," identifier } .
+```
+
+```sentinel
+func(a, b) { return b }
+```
+
+Function literals are only allowed in the file scope. They may refer to variables defined in surrounding blocks.
+
+A function must terminate with a return statement. If a function has no meaningful return value, it should return `undefined`.
+
+### Rule Expressions
+
+A rule is an expression that is evaluated lazily and the result is memoized.
+
+If the optional "when" predicate is present, the rule is evaluated only when
+the "when" boolean expression results in true. If the predicate is false,
+the rule is not evaluated and returns true. The predicate is evaluated when
+the rule would be evaluated; it is also lazy and memoized in the same way.
+
+```ebnf
+RuleExpr = "rule" [ "when" BoolExpr ] "{" Expr "}" .
+```
+
+```sentinel
+rule { x is y }
+
+rule {
+ x == "value" or
+ y == "other"
+}
+
+rule when x is y { y > 42 }
+
+rule { map ["a", "b", "c"] as _, id {
+ { "id": id }
+}}
+```
+
+### Index Expressions
+
+A primary expression of the form `a[x]` denotes the element of a list or map indexed by x. The value x is called the index or key.
+
+For `a` of type map:
+
+- If `a` contains a key `x`, `a[x]` is the map value with key `x`.
+- If `a` does not contain key `x`, `a[x]` is the `undefined` value.
+
+For `a` of type list:
+
+- `x` must be an integer
+- `x` must be in the range `[-1 * length(a), length(a)-1]`
+- If `x` is contained in the set of indices of `a`, `a[x]` is the list element at index `x`. If `x` is negative, `a[x]` is equivalent to `a[length(a)+x]`.
+- If `x` is not contained in the set of indices of `a`, `a[x]` is the `undefined` value.
+
+For `a` of value `null`:
+
+- `a[x]` is the `undefined` value.
+
+Otherwise `a[x]` is an error.
+
+### Selectors
+
+For a primary expression x, the selector expression `x.f` denotes the field `f` of the value `x`. The identifier `f` is called the selector. The type of the selector expression is the type of the selector.
+
+As a special case, selectors can be [reserved words](#keywords) and keyword [operators](#operators-and-delimiters), but cannot be any other non-identifier element.
+
+Selectors are used to access data from [imports](#imports). The first primary expression `x` in `x.f` denotes the import name. The field `f` is the selector to access data from the import.
+
+Selectors may also be used to access map data with static keys. They are syntactic sugar over index expressions. `math.pi` is equivalent to `math["pi"]` and exhibit the same limitations and behavior as an index expression. Selectors cannot, however, be used to assign values to map data.
+
+Selectors on undefined result in undefined.
+
+```sentinel
+math.pi
+time.pst.hour
+```
+
+### Slice Expressions
+
+Slice expressions construct a substring or list from a string or list.
+
+The primary expression
+
+```sentinel
+a[low : high]
+```
+
+constructs a substring or list. The indices `low` and `high` select which elements of operand `a` appear in the result. The result has indices starting at 0 and length equal to `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+```
+
+For convenience, any of the indices may be omitted. A missing `low` index defaults to zero; a missing `high` index defaults to the length of the sliced operand:
+
+```sentinel
+a[2:] // same as a[2 : length(a)]
+a[:3] // same as a[0 : 3]
+a[:] // same as a[0 : length(a)]
+```
+
+The indices are in range if `0 <= low <= high <= length(a)`, otherwise they are out of range. If the indices are out of range at run time, the result is the `undefined` value.
+
+If `a` is the value `null`, the result is the `undefined` value.
+
+If `a` is any other value type, it is an error.
+
+### Calls
+
+```ebnf
+CallExpr = identifier Arguments .
+```
+
+Given an expression `f` where `f` is a function value:
+
+```sentinel
+f(a1, a2, … an)
+```
+
+calls `f` with arguments `a1, a2, ... an`. The type of the expression is the result of `f`. The arguments are evaluated left to right. Arguments are passed by value.
+
+### Operators
+
+```ebnf
+Expression = UnaryExpr | Expression binary_op Expression .
+UnaryExpr = PrimaryExpr | unary_op UnaryExpr .
+
+binary_op = logical_op | set_op | rel_op | add_op | mul_op | else_op .
+logical_op = "and" | "or" | "xor" .
+set_op = ["not"] ( "contains" | "in" ).
+rel_op = "==" | "!=" | "<" | "<=" | ">" | ">=" |
+ "is" | "is not" | "matches" | "not matches" .
+add_op = "+" | "-" .
+mul_op = "*" | "/" | "%" .
+else_op = "else" .
+unary_op = "+" | "-" | "!" | "not" | empty_op | defined_op .
+empty_op = "is empty" | "is not empty" .
+defined_op = "is defined" | "is not defined" .
+```
+
+#### Operator Precedence
+
+Unary operators have the highest precedence.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Binary operators of the same precedence associate from left to right. For instance, x / y _ z is the same as (x / y) _ z.
+
+### Arithmetic Operators
+
+Arithmetic operators apply to numeric values and yield a result of the same type when both operands are the same.
+
+Arithmetic operations between integer and floating-point types result in a floating-point value with the integer operand treated as a floating-point value for purposes of calculation.
+
+Arithmetic operations between numeric values (integer, floating-point) and non-numeric values (strings, lists) are not permitted.
+
+All five supported arithmetic operators (+, -, \*, /, %) apply to both integer and floating-point types; + also applies to strings.
+
+```plaintext
++ sum integers, floats, strings, lists
+- difference integers, floats
+* product integers, floats
+/ quotient integers, floats
+% remainder integers, floats
+```
+
+#### Integer operators
+
+For two integer values x and y, the integer quotient `q = x / y` and remainder `r = x % y` satisfy the following relationships:
+
+```sentinel
+x = q*y + r and |r| < |y|
+```
+
+with x / y truncated towards zero.
+
+```plaintext
+ x y x / y x % y
+ 5 3 1 2
+-5 3 -1 -2
+ 5 -3 -1 2
+-5 -3 1 -2
+```
+
+As an exception to this rule, if the dividend x is the most negative value for the int type of x (-9223372036854775808), the quotient `q = x / -1` is equal to x (and r = 0).
+
+If the divisor is a constant, it must not be zero. If the divisor is zero at run time, an error occurs.
+
+#### Integer overflow
+
+For signed integers, the operations `+`, `-`, and `*` may legally overflow and the resulting value exists and is deterministically defined by the signed integer representation, the operation, and its operands. No exception is raised as a result of overflow.
+
+#### Floating-point operators
+
+For floating-point numbers, +x is the same as x, while -x is the negation of x. The result of a floating-point division by zero is not specified beyond the IEEE-754 standard.
+
+#### String Concatenation
+
+Strings can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = "hi"
+y = "hello"
+x = x + ", " + y // "hi, hello"
+x += " and good bye" // "hi, hello and good bye"
+```
+
+String addition creates a new string by concatenating the operands.
+
+#### List Concatenation
+
+Lists can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = [1, 2]
+x = x + [2, 3] // [1, 2, 2, 3]
+x += [4] // [1, 2, 2, 3, 4]
+```
+
+List addition creates a new list by concatenating the operands.
+
+### Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+In any comparison, the two operands must be equivalent types. The only exception are integers and floats, which are considered numeric and may be compared. Any comparison between other non-matching types results in the `undefined` value.
+
+The equality operators `==`, `!=`, `is`, and `is not` apply to operands that are comparable. The ordering operators `<`, `<=`, `>`, and `>=` apply to operands that are ordered. The behavior of `is` with `==` and `is not` with `!=` is identical. These terms and the result of the comparisons are defined as follows:
+
+- Boolean values are comparable. Two boolean values are equal if they are either both true or both false.
+- Integer values are comparable and ordered, in the usual way.
+- Floating point values are comparable and ordered, as defined by the IEEE-754 standard.
+- An integer compared with floating point value treats the integer as the converted floating point value.
+- String values are comparable and ordered, lexically byte-wise.
+- Lists are comparable. Lists are equal if they are of equal length and their
+ corresponding elements are comparable and equal.
+- Maps are comparable. Maps are equal if they are of equal length and both their
+ corresponding keys and values are comparable and equal.
+
+If either operand is the `undefined` value, the result of the expression is the `undefined` value.
+
+### Emptiness Comparisons
+
+Checking the emptiness of an object can be achieved by using one of the emptiness expressions, `is empty` or `is not empty`. Although similar to [Comparison Operators](#comparison-operators) in that they yield a boolean value and read as though a comparison is taking place, both `is empty` and `is not empty` are evaluated as a multi-word expression.
+
+An emptiness comparison can only be performed against collections, strings or `undefined`, with the same rules as the [built-in length](/sentinel/functions/length) function.
+
+```sentinel
+"" is empty // true
+"foo" is empty // false
+[] is empty // true
+[1] is empty // false
+{} is empty // true
+{"a": "b"} is empty // false
+
+"" is not empty // false
+"foo" is not empty // true
+[] is not empty // false
+[1] is not empty // true
+{} is not empty // false
+{"a": "b"} is not empty // true
+
+undefined is empty // undefined
+undefined is not empty // undefined
+```
+
+### Logical Operators
+
+Logical operators apply to boolean values and yield a boolean result.
+
+Logical operators are evaluated left-to-right and perform short-circuit logic. The right-hand side is not guaranteed to be evaluated if a short-circuit can be performed.
+
+```plaintext
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+### Set Operators
+
+The set operators `contains` and `in` test for set inclusion for lists
+and maps, and substring inclusion for strings.
+
+Set operators may be negated by prefixing the operator with `not`: `not contains` and `not in`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+`contains` tests if the left-hand collection contains the right-hand value. For lists, this tests equality of any value. For maps, this tests equality of any key. For strings, this tests for substring existence.
+
+`in` tests if the right-hand collection contains the left-hand value. The behavior is equivalent to `contains`.
+
+The collection must be a list, map, or string. If it is the `undefined` value, the result is the `undefined` value. For any other value, the result is an error.
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+
+"test" contains "est" // true
+"test" contains "best" // false
+"test" in "testing" // true
+"best" in "testing" // false
+```
+
+### Matches Operator
+
+The matches operator tests if a string matches a regular expression.
+
+The matches operators may be negated by prefixing the operator with `not`: `not matches`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+The left-hand value is the string to test. The right-hand value is a string value representing a regular expression.
+
+The syntax of the regular expression is the same general syntax used by Python, Ruby, and other languages. More precisely, it is the syntax accepted by RE2. The regular expression is not anchored by default; any anchoring must be explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+If either operand is `undefined`, the result is the `undefined` value. For any other non-string value, the result is an error.
+
+### Else Operator
+
+Else operators can be used to provide a default value for an expression that may evaluate to the `undefined` value. Else operators return their left-hand value unless it is `undefined`, otherwise they return their right-hand value.
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else null
+```
+
+### Quantifier Expressions (any, all, filter, map)
+
+Quantifiers are expressions that apply a predicate to a collection (list or map). The purpose of the quantifier is to produce a result based on its particular type.
+
+`any` and `all` expressions are existential and universal quantifiers, respectively. `any` expressions are equivalent to a chain of `or` and `all` expressions are equivalent to a chain of `and`. Both expressions implement short-circuiting equivalent to logical operators.
+
+The `map` expression is an apply-to-all quantifier and returns a new list for all values in the input collection. The output list will be the same length as the input collection.
+
+The `filter` expression is a subset quantifier and returns the subset of all values in the input collection for which the supplied predicate applies. This will be zero or more elements, up to the length of the input.
+
+The body of a quantifier expression is a boolean expression.
+
+`any` returns the boolean value `true` if any value in the collection expression results in the body expression evaluating to `true`. If the body expression evaluates to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression result in the body expression evaluating to `true`. If any value in the collection expression result in the body expression evaluating to `false`, the all expression returns `false`.
+
+For empty collections, `any` returns false and `all` returns `true`.
+
+`map` always returns a list, regardless of if the input collection is a list itself; maps (as in the data type) also return lists when processed through `map`. Each element is the result of the supplied expression body.
+
+`filter` returns a list or map, the same type as its input collection. Only the elements for which the expression body evaluated to `true` will be returned. If an iteration of the expression body results in `undefined`, the entire result set is `undefined`.
+
+When the collection is a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When the collection is a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+QuantExpr = QuantOp Expression "as" [ identifier "," ] identifier "{" BoolExpr "}" .
+QuantOp = "all" | "any" | "filter" | "map" .
+```
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" } // true if every iterations is true
+any group.tasks as t { t.driver is "vmware" } // true if any iteration is true
+map group.tasks as t { { "driver": t.driver } } // [{"driver": "vmware"}, ...]
+filter group.tasks as t { t.driver is "vmware" } // subset of tasks that is true
+```
+
+## Statements
+
+### Expression Statements
+
+Function call expressions may exist in the statement context.
+
+```ebnf
+ExpressionStmt = Expression .
+```
+
+### Assignments
+
+Assignments set the value of an expression to the value of another expression.
+
+```ebnf
+Assignment = AssignExpr assign_op Expression .
+AssignExpr = identifier | IndexExpr .
+assign_op = [ add_op | mul_op ] "=" .
+```
+
+The assignment `x op= y` is equivalent to `x = x op (y)` for supported values of `op`.
+
+```sentinel
+a = 1
+b[12] = 42
+c["key"] = "value"
+
+a *= 12
+b[12] += 8
+```
+
+Assignments to lists and maps can be carried out using [index expressions](#index-expressions), with the operands of the index expression being evaluated alongside the right hand side expression in a right-to-left (RHS, LHS) order.
+
+In addition to the rules detailed in the section describing their use, the following rules apply when assigning to maps or lists using index expressions:
+
+- The [variable](#variables) must exist and be a list or map. Attempts to use an index assignment on an unknown variable, or a variable that not a list or map, results in a runtime error.
+- Assigning to a list or map index that already exists overwrites that index's data with evaluated right hand side's value.
+- Assigning to an unknown key in a map creates a key for that map with the evaluated right hand side's value assigned to that key.
+- Attempting to assign a value to a list index that is out of range results in a runtime error.
+
+### If Statements
+
+If statements specify the conditional execution of two branches according to the value of a boolean expression. If the expression evaluates to true, the "if" branch is executed, otherwise, if present, the "else" branch is executed.
+
+```ebnf
+IfStmt = "if" BoolExpr Block [ "else" ( IfStmt | Block ) ] .
+```
+
+```sentinel
+if x < y {
+ return x
+} else if x > z {
+ return z
+} else {
+ return y
+}
+```
+
+### Case Statements
+
+Case statements specify a selection control mechanism to conditionally execute a branch based on expression equality.
+
+Each clause that wishes to provide an expression must use the "when" keyword. Multiple expressions can be provided, separated with a comma (",").
+
+There can be one, optional, "else" clause within a `case` statement. The "else" clause is executed if all other clauses failed to run.
+
+The clauses are evaluated in the order they are listed, with the first successful evaluation being executed.
+
+A `case` statement can optionally provide an expression. If no expression is provided, it is the equivalent of writing `case true {`.
+
+```ebnf
+CaseStmt = "case" [ Expression ] "{" { CaseWhenClause } "}" .
+CaseWhenClause = CaseWhenCase ":" StatementList .
+CaseWhenCase = "when" Expression { "," Expression } | "else" .
+```
+
+```sentinel
+case x {
+when y, z:
+ return true
+else:
+ return false
+}
+
+case {
+when x > 42:
+ return true
+else:
+ return false
+}
+```
+
+### For Statements
+
+A `for` statement specifies repeated execution of a block.
+
+The expression must evaluate to a list or a map.
+
+When iterating over a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When iterating over a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+ForStmt = "for" Expressions "as" [ identifier "," ] identifier Block .
+```
+
+```sentinel
+for [1, 2, 3] as v { count += v } // basic sum
+
+for [1, 2, 3] as idx, v {
+ if idx > 1 { count += v }
+}
+
+data = { "a": 12, "b": 32 }
+for data as k { count += data[k] } // sum map values
+for data as k, v { count += v }
+```
+
+### Break Statements
+
+A `break` statement terminates execution of the innermost `for` statement
+within the same function.
+
+```ebnf
+BreakStmt = "break" .
+```
+
+```sentinel
+// Will only print "1"
+for [1, 2, 3] as v {
+ print(v)
+ break
+}
+```
+
+### Continue Statements
+
+A `continue` statement begins the next iteration of the innermost `for` loop.
+The `for` loop must be within the same function.
+
+```ebnf
+ContinueStmt = "continue" .
+```
+
+```sentinel
+// Will print 1, 3
+for [1, 2, 3] as v {
+ if v == 2 {
+ continue
+ }
+
+ print(v)
+ break
+}
+```
+
+### Return Statements
+
+A return statement in a function F terminates the execution of F and provides the result value.
+
+```ebnf
+ReturnStmt = "return" Expression .
+```
+
+A function must terminate with a return statement.
+
+The return statement can be invoked at any time during a function to terminate execution.
+
+## Imports
+
+An import adds an assignment to the global scope to external definitions.
+
+The import declarations must only appear at the top of the source file, before any other statements. Comments may appear before imports.
+
+```ebnf
+ImportDecl = "import" Name ["as" identifier] .
+Name = string_lit .
+```
+
+An import name and identifier must be distinct. Two names may not repeated even if they're declared with different identifiers.
+
+If an identifier is not specified, the identifier is the name of the import itself.
+
+An import is not a valid value. The identifier representing the import may not be used as an expression, such as in assignment statements or call expressions.
+
+Values in imports are not assignable directly via their selectors. Function calls may be used to alter the state of the external data represented by the import. Whether or not this is supported is specific to the import implementation. The exception to this assignment restriction is the memoized data returned by a call to an import (see below).
+
+Data returned by imports can be memoized, meaning that data is returned by the import in the form of a map which is then used as much as possible without having to return to the import for more data. For example, `t = time.now` returns a map so that `t.hour` will be able to be looked up without having to go back to the import for that data.
+
+Data returned by imports may be callable, meaning that methods may be called on the memoized data returned by an import. For example, given `t = time.now`, `t.after(some_previous_time)` is valid, with the receiver data being set to the local memoized data stored in `t`. It is a valid case to accept modifications to the receiver data (example: assignment to the memoized data via index expressions), as long as all data in the receiver continues to be string-keyed. It is an invalid case to accept receiver data with non-string-typed keys. Methods may also alter the contents of receiver data so that it is different after the call is finished.
+
+As with methods, imports may also have callable keys where the data may not be memoized. Example: in the event of `v = foo.bar`, if `v.baz` is not included in the memoized data, a call will be made to the import to fetch it. The same rules and restrictions to receiver data apply to callable keys as do to method calls.
+
+The support for callable methods and non-memoized keys on data returned by imports is specific to the import implementation.
+
+The handling of import loading is specific to the runtime implementation.
+
+Implicit imports may be added by the runtime, for example for standard library definitions. An explicit import of the same name should override a matching implicit import. For example, if "time" is an implicit import and the program specifies `import "time" as cal`, then only `cal` is defined.
+
+```sentinel
+import "time"
+import "math" as m
+```
+
+## Parameters
+
+```ebnf
+ParamDecl = "param" identifier [ "default" Expression ]
+```
+
+A parameter is a way for a policy to describe variables that are expected to be supplied by the calling application, in addition to any default value.
+
+Parameter declarations must appear at the top of the source file, following imports.
+
+Like imports, comments may appear before parameters; this is mainly pertinent when the policy is not importing anything, which would make parameters the first declarations that appear in a policy.
+
+A parameter declaration describes a valid variable that is added to the scope of a policy at runtime. This variable has the same properties and rules as other variables assigned during the course of policy evaluation as defined by the specification. This includes having a dynamic type and being able to be re-assigned during execution.
+
+Parameters may only be strings, integers, floating point numbers, booleans, lists, or maps. More complex types such as rules or functions are not allowed.
+
+A parameter can specify a default value by adding one in the declaration via use of the "default" keyword, and supplying a literal for the default value. The literal for a default is a very limited expression, comprising of:
+
+- For strings, a simple string literal;
+- For integers and floating-point numbers, either a literal denoting the value, or a unary expression with the literal, and the operators - or +;
+- For booleans, the pre-declared identifiers true or false;
+- For lists and maps, their respective literals composed of values and keys (for maps) of the above values only.
+
+Any other type of expression or identifier is not allowed.
+
+A parameter that does not have a default value defined is required by the policy. Evaluating a policy with a required parameter that has not been supplied at execution results in a runtime error.
+
+Parameters must not conflict with imports or any other identifiers currently defined in the global scope, including any reserved or pre-declared identifiers. Attempting to assign a parameter to an existing value results in a runtime error.
+
+```sentinel
+import "time"
+
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+param time default "11:00" // error, conflicts with "time" import
+param undefined // error, reserved identifier
+```
+
+## Built-in Functions
+
+Built-in functions are predeclared. They are called like any other function.
+
+### Length
+
+The built-in function `length` returns the length of a collection of string.
+
+The length of a string is the number of bytes in the string. It is not necessarilly the number of characters.
+
+The length of a collection is the number of elements in that collection.
+
+The length of `undefined` is `undefined`.
+
+### Collections
+
+#### List Append
+
+The built-in function `append` appends a value to the end of a list in-place.
+
+Appending to a non-list value results in an immediate `fail`.
+
+The return value of `append` is always the `undefined` value.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+#### Map Delete
+
+The built-in function `delete` deletes elements from a map by key. The map is modified in-place.
+
+Deleting a key that does not exist does nothing.
+
+Calling delete for a non-map value results in an immediate `fail`.
+
+The return value of `delete` is always the `undefined` value.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
+
+#### Keys and Values
+
+The built-in function `keys` and `values` return the keys and values of a map, respectively. The return value is a list. Both functions return values in an unspecified order.
+
+The `keys` or `values` of `undefined` is `undefined`.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+### Range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it defaults to `1`.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4]
+range(1, 5, 2) // [1,3]
+range(0, -3, -1) // [0,-1,-2]
+```
+
+### Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a value of that type according to the rules below.
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
+
+### Printing
+
+The built-in function `print` can be used to output formatted debug information. The runtime may omit print function calls depending on its execution mode. The runtime may choose where the output of a print function goes.
+
+`print` is a variadic function, accepting one or more values to be printed; values are separated by a single whitespace character (`" "`).
+
+The return value of `print` is always `true` to allow it to be used in boolean expressions.
+
+```sentinel
+print("hello") // "hello"
+print("hello", "world") // "hello world"
+print("The", "number", "is", 42) // "The number is 42"
+print([1, 2, 3]) // "[1, 2, 3]"
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // "false"
+```
+
+### Errors
+
+The built-in function `error` is equivalent to `print` but immediately causes execution to halt and the main rule to evaluate to `false`. The program execution is considered to have failed.
+
+## Program Execution
+
+Program execution begins by evaluating the source top to bottom. If evaluation is successful, the `main` rule is evaluated and its result is returned. Execution can be interrupted at any time by an error, which can be called explicitly or implicitly through illegal or undefined behavior.
+
+---
+
+> The content of this page is licensed under the [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
+>
+> Based on [The Go Programming Language Specification](https://golang.org/ref/spec) licensed under [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/language/undefined.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/language/undefined.mdx
new file mode 100644
index 0000000000..dc51531783
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/language/undefined.mdx
@@ -0,0 +1,97 @@
+---
+page_title: Sentinel Language - Undefined
+sidebar_current: docs-language-undefined
+description: >-
+ The value `undefined` is a special value representing undefined behavior or an
+ unknown value. Any operation on an `undefined` value results in `undefined`.
+layout: docs
+---
+
+# Language: Undefined
+
+The value `undefined` is a special value representing undefined behavior
+or an unknown value.
+
+Undefined is not an error because there are situations where the undefined
+value can be safely ignored and the language also provides construts for
+recovering from an undefined value.
+
+The undefined value can be created manually with `undefined`. In most cases,
+undefined is created by accessing a non-existent list element or value.
+The undefined value is created in a number of other circumstances. The documentation
+will note when an undefined value may be created.
+
+Almost any operation with `undefined` results in `undefined`. For example,
+performing math on undefined, attempting to call a function that is undefined,
+etc.
+
+## Main and Undefined
+
+If the value `undefined` is returned from the top-level `main` rule, then
+the policy is considered `false`. However, the runtime provides mechanisms
+for the host application to determine the policy failure was caused by
+an undefined value and report that to the user.
+
+The `main` rule returning the undefined value should be considered a logical
+error in most cases and should probably be corrected by the policy writer.
+
+## Debugging Undefined
+
+When an `undefined` value is first created (either explicitly or implicitly),
+the runtime will record the position where the undefined was created. This
+location can be used to find logic that could be erroneous.
+
+## Boolean Logic and Undefined
+
+Boolean logic is one way to safely recover from `undefined`. If boolean
+logic can safely determine a result despite an undefined value, that result
+will be returned.
+
+For example: `undefined or true` will result in `true`, because no matter
+what actual value `undefined` could've been if the policy behaved properly,
+the boolean expression would still be true.
+
+Another scenario where `undefined` can be safely ignored is short-circuit
+logic with `and`. `false and undefined` will result in `false`.
+
+The full table of logical operations on `undefined` is below:
+
+```sentinel
+undefined or true = true
+undefined or false = undefined
+undefined or undefined = undefined
+undefined and true = undefined
+undefined and false = undefined
+undefined and undefined = undefined
+undefined xor true = undefined
+undefined xor false = undefined
+undefined xor undefined = undefined
+
+// Short-circuit examples
+false or true or undefined = true
+false or undefined or true = true
+true and false and undefined = false
+true and undefined and false = undefined
+```
+
+## Else Expressions
+
+The `else` expression allows explicit recovery from undefined and is useful
+for setting default values.
+
+`else` is an operator where the left and right hand side are values. If the
+left-hand value is `undefined`, the right-hand value is returned. Otherwise,
+the left-hand value is returned.
+
+Examples:
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else "default"
+
+// In more complex scenarios
+
+foo() else 42 == 12
+a = config.value else "default"
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/language/values.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/language/values.mdx
new file mode 100644
index 0000000000..f932480340
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/language/values.mdx
@@ -0,0 +1,210 @@
+---
+page_title: Sentinel Language - Values
+sidebar_current: docs-language-values
+description: Values are the data that Sentinel policies operate on.
+layout: docs
+---
+
+# Language: Values
+
+Values are the _data_ that Sentinel policies operate on. You can create this
+data yourself, for example by just typing the number `42`, or you may access
+this data from an external source to make policy decisions.
+
+Values have a _type_, which determines what kind of operations can be performed
+on the data. Example types are booleans (true and false), numbers,
+and strings (text).
+
+This page documents all the available value types. You can also
+[convert between types](#type-conversion).
+
+## Boolean
+
+A boolean is a value that is either true or false.
+
+A boolean value is created literally with `true` or `false`.
+
+As a policy language, booleans are central to the behavior of Sentinel.
+Booleans are used as conditions in [if statements](/sentinel/language/conditionals),
+are the result of [rules](/sentinel/language/rules), and more. The ultimate
+result of a Sentinel policy is true or false.
+
+## Integer
+
+An integer is a whole number.
+
+An integer is a 64-bit value. This means it can represent numbers from
+-9,223,372,036,854,775,808 to 9,223,372,036,854,775,808.
+
+Integers are created by typing them out literally with no
+separators (such as a comma). An optional prefix can set a non-decimal base:
+`0` for octal, and `0x` for hexadecimal. In hexadecimal literals, letters
+`a-f` and `A-F` represent values 10 to 15.
+
+Example integers are shown below:
+
+```sentinel
+42
+0600
+0xBadFace
+170141183460469
+```
+
+Integers are used for math and numerical comparison.
+
+## Float
+
+A float is a number with a decimal point. It has an integer part, a decimal
+point, a fractional part, and optionally an exponent part. Example
+floats are shown below:
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+Floats are IEEE-754 64-bit floating point numbers.
+
+## String
+
+Strings are text values.
+
+Strings are created by wrapping text in double quotes, such as `"hello"`.
+Within the quotes, any character may appear except newline and an unescaped
+double quote. The text between the quotes is the value.
+
+Because Sentinel policies must be UTF-8 encoded text, strings themselves are
+UTF-8 encoded text. This means you can put any value UTF-8 character into
+a string, such as `"日本語"`.
+
+You can have a string with a literal double-quote by _escaping_ it with
+a backslash. For example: `"they said \"hello\""` turns into the value
+`they said "hello"`.
+
+Backslash escapes can be used for much more than only escaping a double quote.
+Newlines are `\n`, a literal backslash is `\\`, and many more. The full list
+of available backslash escapes can be found
+[in the language specification](/sentinel/language/spec#string-literals).
+
+Example strings:
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+Strings do not support indexing, however it is possible to achieve a similar
+result through the combination of the [strings](/sentinel/imports/strings)
+standard import and list indexing.
+
+```sentinel playground
+import "strings"
+
+asciistr = "Hello, world!"
+utf8str = "日本語"
+
+utf8split = rule {
+ strings.split(utf8str, "")[2] == "語"
+}
+
+asciisplit = rule {
+ strings.split(asciistr, "")[5] == ","
+}
+
+main = rule { utf8split and asciisplit }
+```
+
+Strings support [slicing](/sentinel/language/slices) to efficiently create
+substrings.
+
+## List
+
+See [Lists](/sentinel/language/lists).
+
+## Map
+
+See [Maps](/sentinel/language/maps).
+
+## Rule
+
+See [Rules](/sentinel/language/rules).
+
+## Function
+
+See [Functions](/sentinel/language/functions).
+
+## Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a
+value of that type. Some examples are shown below followed by a list of exact
+rules for type conversion.
+
+```sentinel
+int(42) // 42
+int("42") // 42
+int(42.8) // 42
+int(true) // 1
+
+float(1.2) // 1.2
+float(1) // 1.0
+float("4.2") // 4.2
+float(true) // 1.0
+
+string("foo") // "foo"
+string(88) // "88"
+string(0xF) // "15"
+string(true) // "true"
+
+bool("true") // true
+bool(1) // true
+bool(-1) // true
+bool(0.1) // true
+bool("false") // false
+bool(0) // false
+```
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/language/variables.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/language/variables.mdx
new file mode 100644
index 0000000000..7516890212
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/language/variables.mdx
@@ -0,0 +1,99 @@
+---
+page_title: Sentinel Language - Variables
+sidebar_current: docs-language-vars
+description: Variables store values that can be used later.
+layout: docs
+---
+
+# Language: Variables
+
+Variables store values that can be used later.
+
+Most notably, a variable assignment of `main` is required for all
+Sentinel policies. This is the main [rule](/sentinel/language/rules) that
+is executed to determine the result of a policy. The `main` rule may
+use other variables that are assigned in the policy.
+
+Example:
+
+```sentinel
+a = 1
+b = a + 1
+```
+
+In this example, two variables are assigned: `a` and `b`. `a` is given
+the value of "1" and `b` uses the `a` to calculate its own value.
+
+## Syntax
+
+The syntax for assigning a variable is:
+
+```sentinel
+IDENTIFIER = VALUE
+```
+
+On the left of the equal sign is an _identifier_. This is a name for your
+variable and will be how you reference it later. An identifier is any
+combination of letters and digits, but must start with a letter. An
+underscore `_` is also a valid letter.
+
+On the right is any valid Sentinel value or expression. A value is a
+literal value such as a number or string. An expression is some computed
+value such as doing math, calling a function, etc.
+
+## Assignment and Reassignment
+
+A variable is _assigned_ when it is given a value. You can also _reassign_
+variables at any time by setting it to a new value. The new value takes
+effect for any subsequent use of that variable. A variable can be reassigned
+to a different type.
+
+For example:
+
+```sentinel
+a = 1 // a = 1
+b = a // b = 1
+a = "value" // a = "value", b = 1
+c = a // c = "value", b = 1
+```
+
+In the above example you can see that the variables are set and reassigned.
+Notice that the value of a variable is the _current_ value, and that reassigning
+a variable only affects _future_ uses of that variable. You can see this with
+`c` and `b` being different values.
+
+## Unassigned Variables
+
+Using a variable that is _unassigned_ is an error.
+
+In the example below, the first line would result in the policy erroring.
+Sentinel is executed top-down and the value of `c` is not available yet
+on the first line.
+
+```sentinel
+a = c // Error!
+c = 1
+```
+
+## Type Conversion
+
+While a topic more related to [values][lang-values], variables can be assigned
+(or-reassigned) a different value type via type conversion.
+
+[lang-values]: /sentinel/language/values
+
+```sentinel
+s = "1.1"
+a = int(s) // a = 1
+a = float(s) // a = 1.1
+
+a = 1
+s = string(a) // s = "1"
+```
+
+For more details, see the type conversion sections in the
+[values][lang-values-type-conversion] page, or the [Sentinel Language
+Specification][lang-spec-type-conversion].
+
+[lang-values-type-conversion]: /sentinel/language/values#type-conversion
+[lang-spec-type-conversion]: /sentinel/language/spec#type-conversion
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/nomad.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/nomad.mdx
new file mode 100644
index 0000000000..a105b0337d
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/nomad.mdx
@@ -0,0 +1,51 @@
+---
+page_title: Nomad and Sentinel
+sidebar_current: docs-app-nomad
+description: >-
+ Nomad Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies can currently execute on job
+ submission (creation, update).
+layout: docs
+---
+
+# Nomad
+
+Nomad is a simple, flexible scheduler and workload orchestrator.
+[Nomad Enterprise](https://www.hashicorp.com/products/nomad/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies can currently execute on job submission (creation, update).
+
+Sentinel policies have full access to the job structure. This allows the
+Sentinel policy to control behavior based on any attribute within a job,
+such as the driver, resource requests, network configuration, volume
+configuration, and more. The information that Sentinel policies have access
+to will expand over time.
+
+Nomad fully supports all [enforcement levels](/sentinel/concepts/enforcement-levels).
+For soft mandatory policies, the `sentinel-override` capability must be
+available on the user's ACL policy to allow override. Overrides are always
+logged.
+
+The Nomad integration with Sentinel is documented in depth in the
+[Nomad Enterprise documentation](https://www.nomadproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Only allow Docker-based jobs.**
+
+```sentinel
+# Test policy only allows Docker based tasks
+main = rule { all_drivers_docker }
+
+# all_drivers_docker checks that all the drivers in use are Docker
+
+all_drivers_docker = rule {
+ all job.task_groups as tg {
+ all tg.tasks as task {
+ task.driver is "docker"
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/terraform.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/terraform.mdx
new file mode 100644
index 0000000000..9e4c430ddb
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/terraform.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Terraform and Sentinel
+sidebar_current: docs-app-terraform
+description: >-
+ Use Sentinel with HCP Terraform and Terraform Enterprise to enforce policy
+ on Terraform configurations, states, and plans.
+layout: docs
+---
+
+# Terraform
+
+[HCP Terraform and Terraform Enterprise](https://www.hashicorp.com/products/terraform/) use Sentinel to enforce
+policy on [Terraform](https://www.terraform.io) configurations, states, and plans.
+
+The Sentinel integration with Terraform runs within
+HCP Terraform and Terraform Enterprise
+after a `terraform plan` and before a `terraform apply`. The policies
+have access to the created plan, the state at the time of the plan,
+and the configuration at the time of the plan.
+
+The Terraform integration with Sentinel is documented in depth in the [HCP Terraform and Terraform Enterprise documentation](/terraform/cloud-docs/policy-enforcement/sentinel).
+Please read that page for full documentation. This page will only show basic examples.
+
+### Examples
+
+**Example: All AWS instances must have a tag**
+
+```sentinel
+import "tfplan"
+
+main = rule {
+ all tfplan.resources.aws*instance as *, instances {
+ all instances as \_, r {
+ (length(r.applied.tags) else 0) > 0
+ }
+ }
+}
+```
+
+**Example: Only allow GCP instance sizes smaller than `n1-standard-16`**
+
+```sentinel
+import "tfplan"
+
+allowed_machine_types = [
+ "n1-standard-1",
+ "n1-standard-2",
+ "n1-standard-4",
+ "n1-standard-8",
+]
+
+main = rule {
+ all tfplan.resources.google_compute_instance as _, instances {
+ all instances as _, r {
+ r.applied.machine_type in allowed_machine_types
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/vault.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/vault.mdx
new file mode 100644
index 0000000000..6db2f8af22
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/vault.mdx
@@ -0,0 +1,64 @@
+---
+page_title: Vault and Sentinel
+sidebar_current: docs-app-vault
+description: >-
+ Vault Enterprise uses Sentinel to augment the built-in policy system to
+ provide Role Governing Policies (RGPs) and Endpoint Governing Policies (EGPs)
+ to enable complex, flexible policies across identities and endpoints.
+layout: docs
+---
+
+# Vault
+
+[Vault Enterprise](https://www.hashicorp.com/products/vault/) uses Sentinel
+to augment the built-in policy system to provide Role Governing Policies (RGPs)
+and Endpoint Governing Policies (EGPs) to enable complex, flexible policies
+across identities and endpoints.
+
+**Role Governing Policies (RGPs)** are Sentinel policies that are tied to
+particular tokens, Identity entities, or Identity groups. They have access to
+a rich set of controls across various aspects of Vault. These are evaluated
+whenever a token they're attached to is used.
+
+**Endpoint Governing Policies (EGPs)** are Sentinel policies that are tied to
+particular paths instead of tokens. They have access to as much request
+information as possible, but they can take effect even on unauthenticated
+paths, such as login paths.
+
+The Vault integration with Sentinel is documented in depth in the
+[Vault Enterprise documentation](https://www.vaultproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Endpoint policy that requires MFA authentication from a corporate network.**
+
+```sentinel
+import "sockaddr"
+
+# We expect logins to come only from our private IP range
+cidrcheck = rule {
+ sockaddr.is_contained(request.connection.remote_addr, "10.20.0.0/16")
+}
+
+# Require Ping MFA validation to succeed
+ping_valid = rule {
+ mfa.methods.ping.valid
+}
+
+main = rule when request.path is "auth/ldap/login" {
+ ping_valid and cidrcheck
+}
+```
+
+**Example: Endpoint policy that disallows tokens created before a certain time.**
+
+```sentinel
+import "time"
+import "strings"
+
+main = rule when not strings.has_prefix(request.path, "auth/ldap/login") {
+ time.load(token.creation_time).unix > time.load("2017-09-17T13:25:29Z").unix
+}
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/writing/basic.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/writing/basic.mdx
new file mode 100644
index 0000000000..b62533466e
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/writing/basic.mdx
@@ -0,0 +1,113 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-basic
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Policy Basics
+
+Sentinel policies are easy to write while still supporting advanced constructs
+for creating complex policies. This page will explain the basics of writing
+Sentinel policies to get started. You don't need any prior Sentinel knowledge,
+but we do recommend reading the
+[getting started guide](/sentinel/intro/getting-started/first-policy) and
+[language guide](/sentinel/language) after this.
+
+Sentinel policies are text files written using the [Sentinel language](/sentinel/language).
+The policies are evaluated top-to-bottom. The value of `main` after execution
+determines whether a policy passes or fails.
+
+## The Simplest Policy
+
+Sentinel only requires that a policy have a `main` variable that evaluates to
+a boolean value.
+
+A valid example is shown below:
+
+```sentinel playground
+main = 10 > 5
+```
+
+This type of minimal policy is not purely academic. In practice, simple
+policies can often be reduced to a single line logical statement resulting
+in `true` or `false`. However, the expression is usually wrapped in a
+[rule](/sentinel/language/rules) for testing reasons.
+
+You can verify Sentinel will execute this minimal policy using the CLI:
+
+```
+$ sentinel apply minimal.sentinel
+Pass
+```
+
+## Logical Expressions
+
+Policy is at its core a set of logic: you can or can not perform some action
+under a certain set of circumstances. Those circumstances are logical
+expressions. Therefore, Sentinel policies primarily translate into logical
+expressions.
+
+Detailed documentation on boolean expressions is
+[available in the language guide](/sentinel/language/boolexpr).
+
+A simple numerical comparison was seen in the first example on this page.
+Sentinel also provides inclusion operators such as `contains`, `any`, `all`, and
+more. Sentinel allows some operators to have aliases to promote readability
+while remaining programmer-familiar, such as `==` which can equivalently be `is`.
+
+The example below verifies that all numbers in a list are even:
+
+```sentinel playground
+numbers = [6, 22, 8, 4, 12]
+main = all numbers as n { n % 2 is 0 }
+```
+
+## Variables
+
+A policy will very often use variables. Applications such as
+[Nomad](https://www.nomadproject.io) inject variables into the global
+scope of a policy for making policy decisions. For example, Nomad injects
+the job that is being run into the policy scope. Knowing how to use
+variables is critical to effectively using Sentinel.
+
+Detailed documentation on how to define and access variables is
+[available in the language guide](/sentinel/language/variables).
+
+Variables can be defined and used explicitly. For example:
+
+```sentinel playground
+value = 10
+main = value > 5
+```
+
+But they may also be introduced implictly by the host system. Nomad
+injects `job` into policies to describe the job that is being run. The
+policy below is a valid policy that requires a job have two task groups.
+Notice that `job` is not defined anywhere. It is implicitly inserted by
+the host application (in this case Nomad). Refer to the application you're
+writing policy for to determine if it implicitly inserts values.
+
+```json sentinel
+{
+ "policy": "main = length(job.task_groups) is 2",
+ "globals": {
+ "job": {
+ "task_groups": ["foo", "bar"]
+ }
+ }
+}
+```
+
+## And more!
+
+The Sentinel language supports many more features such as functions,
+loops, and more. You can learn about all of this in the
+[complete language guide](/sentinel/language).
+
+The other pages in the [writing policy](/sentinel/writing)
+will cover other information you need to know about writing Sentinel
+policies that isn't simply a language reference.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/writing/debugging.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/writing/debugging.mdx
new file mode 100644
index 0000000000..7dc284d222
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/writing/debugging.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-debugging
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Debugging
+
+As policies become more complex, it becomes harder to understand exactly why
+a policy may be behaving differently than you expect.
+
+Prior to actively debugging, there are multiple best practices we recommend
+following. We recommend breaking your policy down into a set of
+[rules](/sentinel/writing/rules). This will make it easier to understand
+[traces](/sentinel/writing/tracing) and make it easier to
+[test](/sentinel/writing/testing) your policies.
+This should help to debug policies up to a significant complexity.
+
+## Print Logging
+
+The built-in [print function](/sentinel/language/logging-errors#print)
+can be used to log data. The print output is only shown during failures
+or when [tracing is explicitly enabled](/sentinel/writing/tracing).
+
+The `print` function outputs each argument, which can be of any type, converted
+to a human-friendly string format. Arguments are separated with a single space.
+
+Example:
+
+```sentinel playground
+value = 42
+print("the number is", value) // the number is 42
+
+object = { "foo": false }
+print(object) // { "foo": false }
+
+main = rule { true }
+```
+
+The `print` function returns the value `true` so that it can also be
+used within rule expressions. Note that the `print` function should be
+used at the beginning of statements otherwise they may never be
+reached. This is due to internal performance techniques within Sentinel
+like [short-circuiting](https://en.wikipedia.org/wiki/Short-circuit_evaluation).
+
+Example:
+
+```sentinel playground
+// rule_a and rule_b will evaluate to false
+rule_a = rule { print("rule_a is printed") and false } // "rule_a is printed"
+rule_b = rule { false and print("rule_b is printed") } // Nothing is printed
+
+// rule_c and rule_d will evaluate to true
+rule_c = rule { print("rule_c is printed") or true } // "rule_c is printed"
+rule_d = rule { true or print("rule_d is printed") } // Nothing is printed
+
+main = rule { rule_a or rule_b or rule_c and rule_d }
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/writing/imports.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/writing/imports.mdx
new file mode 100644
index 0000000000..74ac751ee4
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/writing/imports.mdx
@@ -0,0 +1,117 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom import](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only local
+context for making policy decisions.
+
+Sentinel also comes with a set of [standard imports](/sentinel/imports).
+Standard imports are available to every Sentinel policy to help policy writers
+with common tasks such as working with the time, network addresses, and more.
+
+-> **This page is about writing policies that _use_ imports.** If you're
+interested in _creating_ a new import, please see the section on [extending
+Sentinel](/sentinel/extending) for information on how to write modules and
+import plugins.
+
+## Using Imports
+
+To use an import, you use the `import` keyword at the top of your policy. This
+specifies the name of the import you want to use. The application you're writing
+the policy for must already be
+[configured](/sentinel/configuration#imports) to provide that
+import.
+
+Details on imports can be found in the
+[import section in the language reference](/sentinel/language/imports).
+
+In the example below, we use the [time](/sentinel/imports/time) import:
+
+```sentinel playground
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+There are two options to develop a policy locally when using imports:
+configure Sentinel to launch the import on `apply`, or mock the import
+using `mock`. The former requires access to the import while the
+latter is faster (doesn't have to launch a process for plugins) and
+doesn't require the import.
+
+## Mocking Imports
+
+The first option to developing policies locally is to mock the import values.
+When mocking an import, you don't need the import to be available. This can be
+useful since some imports may not be available as a plugin and may only be
+available to the application the policy runs in.
+
+Mocks are specified via the [configuration
+file](/sentinel/configuration). Mocks can also be used for
+[testing](/sentinel/writing/testing).
+
+You can supply mock configuration one of two ways, depending on your use case:
+
+- **[Using static data](/sentinel/configuration#mocking-static-data):**
+ Use this method when you can accurately represent your mock data in JSON and
+ do not need to mock complex Sentinel features such as functions.
+- **[Using Sentinel code](/sentinel/configuration#mocking-with-sentinel-code):**
+ Use this method when using a static JSON object is insufficient, such as when
+ you need to mock functions or other complex Sentinel features.
+
+Our example does not require complex data to be mocked, so a static object
+is sufficient:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ hour = 12
+ weekday_name = "Tuesday"
+ }
+ }
+}
+```
+
+This can be used via the CLI:
+
+```
+$ sentinel apply -config=config.hcl policy.sentinel
+Pass
+```
+
+## Launching Import Plugins
+
+If you have access to the plugin binary, you can launch the import. The
+benefit of this is that it is really using the import to test your
+policy. If the import changes, your policies may start failing. If you
+only use mock data and the import changes, your policies will still
+appear to work.
+
+Imports are configured in the configuration file:
+
+```hcl
+import "plugin" "custom_time" {
+ source = "/path/to/sentinel-time-import"
+}
+```
+
+This would require the `sentinel-time-import` binary. For this example
+this doesn't currently exist. We plan on writing one to provide for this
+section of the documentation.
+
+## Custom Imports
+
+You can also [create your own imports](/sentinel/extending).
+
+If your policy decisions could benefit from accessing external information,
+then you can use custom imports as a way to do this.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/writing/index.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/writing/index.mdx
new file mode 100644
index 0000000000..064069d389
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/writing/index.mdx
@@ -0,0 +1,35 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing
+description: >-
+ Sentinel provides a language and workflow for building policy across any
+ system that embeds Sentinel. By learning Sentinel once, you are able to
+ effectively control access to many systems using Sentinel's powerful features.
+ Basic concepts that are important to understand for Vault usage.
+layout: docs
+---
+
+# Writing Sentinel Policy
+
+This section covers how to write Sentinel policies.
+
+It is recommended that you complete the
+[getting started guide](/sentinel/intro/getting-started/first-policy) prior to
+reading this section of the documentation. The getting started guide will
+explain all the basics of writing and testing policies. This section of the
+documentation will serve as more of a reference guide to all available
+features of Sentinel.
+
+Sentinel provides a language and workflow for building policy across any system
+that embeds Sentinel. By learning Sentinel once, you are able to effectively
+control access to many systems using Sentinel's powerful features.
+
+Sentinel also provides a [local CLI](/sentinel/commands) for developing and testing
+Sentinel policies. This CLI can be integrated into a daily developer's workflow
+along with CI to treat [policy as code](/sentinel/concepts/policy-as-code).
+
+Sentinel uses its own [language](/sentinel/language) for writing
+policies. You can view a [language reference](/sentinel/language)
+as well as the [specification](/sentinel/language/spec) for details. You
+don't have to read those documents immediately, since the language should
+be easy enough to pick up throughout this section.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/writing/rules.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/writing/rules.mdx
new file mode 100644
index 0000000000..ab4fc69d08
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/writing/rules.mdx
@@ -0,0 +1,65 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-rules
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Rules
+
+Rules form the basis of a policy by representing behavior that is either passing
+or failing. Rules are a first class language construct in Sentinel. A policy can
+and should be broken down into rules to aid with readability, testability, and
+performance.
+
+Rules provide:
+
+- **Readability:** A policy that is broken down into rules can be read
+ more easily. The logic becomes clearer to see for policy writers when
+ they come back to it.
+
+- **Reportability:** When [tracing](/sentinel/writing/tracing) is enabled,
+ rules form the foundation of the data returned. All evaluated rules are added
+ to the trace with the data the rule evaluated to, and their position within
+ policy or module source files.
+
+- **Testability:** [Policy testing](/sentinel/writing/testing) is
+ built on asserting the values of rules. By breaking logic down into
+ rules, you're able to more effectively test your policies.
+
+- **Performance:** Rules are only evaluated on demand, and are also only
+ evaluated once. This means that a rule referenced multiple times only has a
+ one-time performance cost. For complex logic, this could result in improved
+ performance.
+
+An example usage of rules is shown below:
+
+```json sentinel
+{
+ "policy": "is_sunny = rule { weather is \"sunny\" }\nis_wednesday = rule { day is \"wednesday\" }\nmain = rule { is_sunny and is_wednesday }",
+ "globals": {
+ "weather": "sunny",
+ "day": "wednesday"
+ }
+}
+```
+
+Rules can also contain non-boolean data. This can be useful for passing more
+detail to the caller than an opaque boolean value would be able to communicate.
+
+```sentinel playground
+days = [{"weather": "sunny"}]
+main = rule {
+ filter days as d {
+ d.weather is not "sunny"
+ }
+}
+```
+
+Details about rules and their behavior can be found in
+[the language reference](/sentinel/language/rules). Rules have important
+behavior so we recommend reading the language reference on rules.
+Rules will be used throughout the remainder of the documentation.
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/writing/testing.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/writing/testing.mdx
new file mode 100644
index 0000000000..5d8a522c28
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/writing/testing.mdx
@@ -0,0 +1,495 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-testing
+description: >-
+ Sentinel has a built-in test framework to validate a policy behaves as
+ expected.
+layout: docs
+---
+
+# Testing
+
+Sentinel has a built-in test framework to validate a policy behaves as expected.
+
+With an ever-increasing amount of automation surrounding technology,
+the guardrails provided by policies are a critical piece towards ensuring
+expected behavior. As a reliance on correct policy increases, it is important
+to test and verify policies.
+
+Testing is a necessary step to fully realize
+[policy as code](/sentinel/concepts/policy-as-code). Just as good software
+is well tested, a good set of policies should be equally well tested.
+
+## Test Folder Structure
+
+Policies are tested by asserting that [rules](/sentinel/writing/rules)
+are the expected values given a pre-configured input. Tests are run
+by executing the [test command](/sentinel/commands/test).
+
+Sentinel is opinionated about the folder structure required for tests.
+This opinionated structure allows testing to be as simple as running
+`sentinel test` with no arguments. Additionally, it becomes simple to test
+in a CI or add new policies.
+
+The structure Sentinel expects is `test//*.[hcl|json]` where ``
+is the name of your policy file without the file extension. Within that folder
+is a list of HCL or JSON files. Each file represents a single test case.
+Therefore, each policy can have multiple tests associated with it.
+
+-> Note that the primary configuration file format for Sentinel is HCL. While
+you can write configuration files in a HCL-equivalent JSON, we only discuss the
+use of HCL on this page.
+
+## Test Case Format
+
+Each HCL file within the test folder for a policy is a single test case.
+
+The file is the same configuration format as the [CLI configuration
+file](/sentinel/configuration). The format lets you define mock data, imports to
+use, and more. This mock data is the key piece in being able to test policies:
+you craft a specific scenario and assert your policy behaves as you expect.
+
+Test cases also use the `test` block within the configuration file to assert the
+value of [rules](/sentinel/writing/rules). If the `test` key is omitted, the
+policy is expected to pass. If the test key is specified, only the rules
+specified in the map will be asserted. This means if you omit `main`, then the
+final policy result is not asserted.
+
+Example with assertions:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+
+test {
+ rules = {
+ main = false
+ is_open_hours = false
+ is_weekday = true
+ }
+}
+```
+
+The configuration above specifies some parameter data, and asserts the result of
+some rules. This is the same configuration used in the example section below.
+
+## Example
+
+Lets use the following file as an example. Save this file to a directory
+and name it `policy.sentinel`. It can be named anything with the `sentinel`
+extension, but by naming it `policy.sentinel` your output should match
+the example output on this page.
+
+```sentinel
+// The day of the week.
+param day
+
+// The hour of the day.
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+### A Passing Test
+
+Next, let's define a single test case. Relative to where you saved
+the policy, create a file at the path `test/policy/good.hcl`.
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 14
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/good.hcl
+```
+
+This passed because the policy passed. We didn't assert any specific
+rules. By not specifying any assertions, `test` expects the policy itself
+to fully pass.
+
+### A Failing Test
+
+Define another test case to fail. We want to verify our policy fails when expected, too.
+
+Save the following as `test/policy/7-am.hcl`:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+FAIL - policy.sentinel
+ FAIL - test/policy/7-am.hcl
+ expected "main" to be true, got: false
+
+ trace:
+ policy.sentinel:9:1 - Rule "main"
+ bool: false
+
+ policy.sentinel:8:1 - Rule "is_open_hours"
+ bool: false
+
+ PASS - test/policy/good.hcl
+```
+
+As you can see, the test fails because "main" is false. This is good
+because the policy _should_ have failed since we specified an invalid
+hour. But, we expect main to be false and don't want our test to fail!
+Update `7-am.hcl` to add test assertions:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+
+test {
+ rules = {
+ main = false
+ is_open_hours = false
+ }
+}
+```
+
+And when we run the tests:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/7-am.hcl
+ PASS - test/policy/good.hcl
+```
+
+The test passes. We asserted that we expect the `main` rule to be false,
+the `is_open_hours` rule to be false, and the `is_weekday` rule to be
+true. By asserting some rules are true, we can verify that our policy
+is failing for reasons we expect.
+
+## Mocking
+
+The above example demonstrates how to test by supplying different
+[parameters](/sentinel/language/parameters). Parameters in a policy can be
+specifically useful when you want to control user-defined input values to a
+policy.
+
+However, generally, when testing, you will need mimic the conditions you will
+see in production. Production implementations of Sentinel will supply data using
+one of two methods:
+
+- **Global data:** Data is injected directly into the policy's scope and is
+ accessible using normal identifiers, similar to variables.
+- **Imports:** Data is stored behind an [import](/sentinel/language/imports)
+ and loaded on demand as needed by the policy author.
+
+Proper testing of a policy requires that these values be able to be _mocked_ -
+or, in other words, simulated in a way that allows the accurate testing of the
+scenarios that a policy could reasonably pass or fail under.
+
+Mocking both globals and imports can be done by setting various parts of the
+configuration file.
+
+### Mocking Globals
+
+Demonstrating the mocking of globals can be seen by making a few modifications to
+our example policy, removing the `param` declarations:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Then, change the `param` section in the configuration file to
+[`global`](/sentinel/configuration#global).
+
+```json
+global "day" {
+ value = "monday"
+}
+
+global "hour" {
+ value = 14
+}
+```
+
+This test should still pass, as if nothing had happened, although what we've
+done is shifted our parameters to globals, simulating an environment where `day`
+and `hour` are already defined for us.
+
+### Mocking Imports
+
+To mock imports, we need to use the
+[`mock`](/sentinel/configuration#mock-imports) section of the
+configuration file.
+
+Let's say the above example is behind an import named `time`.
+
+-> **NOTE:** [`time`](/sentinel/imports/time) is a valid standard import.
+This example may not be accurate to the
+import's syntax.
+
+The code now looks like this:
+
+```sentinel
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To mock this import, we can [mock it as static
+data](/sentinel/configuration#mocking-static-data). The configuration
+file now looks like, without assertions:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ weekday_name = "Monday"
+ hour = 14
+ }
+ }
+}
+```
+
+The policy will now pass, with the `time` import mocked.
+
+Data can also be [mocked as Sentinel
+code](/sentinel/configuration#mocking-with-sentinel-code). In this case,
+the above configuration file would look like:
+
+```hcl
+mock "time" {
+ module {
+ source = "mock-time.sentinel"
+ }
+}
+```
+
+And a file named `mock-time.sentinel` would now hold your mock values:
+
+```sentinel
+day = "monday"
+hour = 14
+```
+
+Mocking as Sentinel code allows more complex details to be mocked as well, such
+as functions. Say we wanted to mock the `time.load()` function. To mock this,
+just add it to the `mock-time.sentinel` file:
+
+```sentinel
+load = func(_) {
+ return {
+ "weekday_name": "Monday",
+ "hour": 14,
+ }
+}
+```
+
+Your code can now be written as:
+
+```sentinel
+import "time"
+
+t = time.load("a_mock_timestamp")
+
+is_weekday = rule { t.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { t.hour > 8 and t.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To see more details, see the [Mock
+Imports](/sentinel/configuration#mock-imports) section in the
+configuration file.
+
+## Asserting Non-Boolean Rules
+
+Non-boolean rules can also be asserted by `sentinel test`.
+
+To assert non-boolean values, simply enter the expected value into the rule
+contents:
+
+```hcl
+param "maintenance_days" {
+ value = [
+ {
+ day = "wednesday"
+ hour = 9
+ },
+ {
+ day = "friday"
+ hour = 1
+ },
+ {
+ day = "sunday"
+ hour = 1
+ },
+ ]
+}
+
+test {
+ rules = {
+ main = [
+ {
+ day = "wednesday"
+ hour = 9
+ },
+ ]
+ }
+}
+```
+
+This would assert a policy checking for violations of a maintenance policy,
+saying that maintenance hours should happen before 6AM on any given day:
+
+```sentinel
+param maintenance_days
+
+main = rule {
+ filter maintenance_days as d {
+ d.hour >= 6
+ }
+}
+```
+
+## Test JSON Output
+
+Running `sentinel test` with the `-json` flag will give you the test results in
+a JSON output format, suitable for parsing by reporting software.
+
+-> **Note:** The JSON output format is currently under development and the
+format may change at a later time. Additionally, support for other well-known
+formats, such as JUnit, may become available in the future.
+
+The current format is an object with the top-level keys being `policies` and
+`duration`, with each test grouped up by policy being run. `duration` represents
+time taken in milliseconds for all policies to run.
+
+The policy result fields are:
+
+- `path`: The path of the policy and the index of the test result in the
+ `policies` field in the root object.
+- `status`: A string representation of the policy's test status as a whole. Can
+ be one of `PASS`, `FAIL`, `ERROR`, or `?`. The final status, `?`, represents a
+ policy that has no tests to process, and acts like a passing test.
+- `errors`: An array of any error messages encountered during processing the
+ policy for testing. Usually reserved for policy file or parser-related errors.
+ For case-specific errors, see the error field for the particular case.
+- `cases`: A map of case results, indexed by case path.
+- `duration`: Time taken in milliseconds for the policy to run.
+
+The case result fields are:
+
+- `path`: The path of the test case and the result's index in the `cases` field
+ in the policy object.
+- `status`: A string representation of the case's test status. Can be one of
+ `PASS`, `FAIL` or `ERROR`.
+- `errors`: An array of any error messages encountered during running this test
+ case.
+- `trace`: The trace for this policy in JSON format. See the
+ [tracing](/sentinel/writing/tracing) page for more details.
+- `rule_detail`: When the `status` of this test case is `FAIL`, contains an
+ object of assertion failure detail, indexed by rule. Only failures are counted
+ here; any rules not found here can be assumed to have passed or not asserted.
+- `config_warnings`: An array of strings denoting any configuration warnings
+ found while processing the configuration for this test case.
+- `config_legacy`: Denotes whether or not the configuration is a legacy JSON
+ configuration and needs to be modernized. This field may be removed in future
+ releases.
+
+A passing example is shown below:
+
+```json
+{
+ "policies": {
+ "policy.sentinel": {
+ "path": "policy.sentinel",
+ "status": "PASS",
+ "errors": null,
+ "duration": 5,
+ "cases": {
+ "test/policy/pass.hcl": {
+ "path": "test/policy/pass.hcl",
+ "status": "PASS",
+ "errors": null,
+ "trace": {
+ "description": "A very basic policy to determine if a run is within working hours.",
+ "error": null,
+ "print": "",
+ "result": true,
+ "rules": {
+ "is_open_hours": {
+ "desc": "Passes if run during business hours.",
+ "ident": "is_open_hours",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 290,
+ "line": 13,
+ "column": 1
+ },
+ "value": true
+ },
+ "is_weekday": {
+ "desc": "Passes if the day does not fall on the weekend.",
+ "ident": "is_weekday",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 193,
+ "line": 10,
+ "column": 1
+ },
+ "value": true
+ },
+ "main": {
+ "desc": "",
+ "ident": "main",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 338,
+ "line": 14,
+ "column": 1
+ },
+ "value": true
+ }
+ }
+ },
+ "rule_detail": {},
+ "config_warnings": null,
+ "config_legacy": false
+ }
+ }
+ }
+ },
+ "duration": 10
+}
+```
diff --git a/content/sentinel/v0.27.x/content/sentinel/docs/writing/tracing.mdx b/content/sentinel/v0.27.x/content/sentinel/docs/writing/tracing.mdx
new file mode 100644
index 0000000000..e98767da00
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/docs/writing/tracing.mdx
@@ -0,0 +1,403 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-tracing
+description: >-
+ Sentinel provides execution traces to better understand the execution of a
+ policy.
+layout: docs
+---
+
+# Traces
+
+Sentinel provides execution traces to better understand the execution of a policy.
+
+When using the Sentinel [CLI](/sentinel/commands), traces are shown
+on policy failure during `apply` or `test`, or when the `-trace` flag is
+explicitly specified. For Sentinel-enabled applications, traces are optional
+and may require special configuration to enable.
+
+The availability of the trace may vary from application to application. While
+some applications may only log traces on failure, others, such as [Terraform
+Cloud](https://www.terraform.io/cloud), log the trace on every execution. For
+more details, consult your implementation's documentation.
+
+-> **Note:** We're actively improving Sentinel tracing in each release
+to provide better data and improve readability. The exact format of a
+trace may not match the Sentinel version embedded in the application you're
+using, but the data should be similar. The canonical format for a trace should
+get more stable as we approach a 1.0 release.
+
+## Analyzing a Trace
+
+To show traces, let's use the example policy below with the CLI:
+
+```sentinel
+param day
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+
+main = rule { is_open_hours and is_weekday }
+```
+
+Let's cause the policy to fail so the trace is more interesting.
+If you're on a terminal that supports it, the trace output will be colored
+so you can more clearly see passes, failures, and rules.
+
+
+
+ $ sentinel apply main.sentinel{'\n'}
+
+ main.sentinel:1:7: requires value for parameter day
+
+ {'\n'}
+ {'\n'}
+ {'\n'}
+ {' '}Values can be strings, floats, or JSON array or object values. To force
+ {'\n'}
+ {' '}strings, use quotes.{'\n'}
+ {'\n'}
+ {' '}Enter a value: sunday{'\n'}
+ {'\n'}
+
+ main.sentinel:2:7: requires value for parameter hour
+
+ {'\n'}
+ {'\n'}
+ {'\n'}
+ {' '}Values can be strings, floats, or JSON array or object values. To force
+ {'\n'}
+ {' '}strings, use quotes.{'\n'}
+ {'\n'}
+ {' '}Enter a value: 14{'\n'}
+ {'\n'}
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+
+ Fail - main.sentinel
+
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:7:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}
+ false
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:5:1 - Rule "is_open_hours"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}true{'\n'}
+ {'\n'}
+
+ main.sentinel:4:1 - Rule "is_weekday"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}false{'\n'}
+
+
+
+We can see all of our rules neatly and clearly displayed along with the result
+of the policy. If you are getting the trace due to a failed policy and not
+because you explicitly used `-trace`, an informational message is displayed.
+
+As we can see from our basic example, the `main` rule has failed and its result
+is highlighted in red. All peripheral rules that were also evaluated as part of
+the policy are also displayed below the main rule. Source positions are also
+displayed so that you can find the references to the rules in your code.
+
+Via the trace, we can clearly see that while `is_open_hours` returned a true
+result, `is_weekday` did not, and per the logic in our code, the policy is
+rejected.
+
+## Non-Boolean Rules and the Trace
+
+The trace is particularly important when working with rules that return
+non-boolean data, such as rules where you want to see violations instead of a
+simple opaque boolean value. Let's take our example from the [rules](./rules)
+section, and write a small static policy for it:
+
+```sentinel playground
+days = [
+ {"weekday": "Sunday", "weather": "clouds"},
+ {"weekday": "Monday", "weather": "rain"},
+ {"weekday": "Tuesday", "weather": "sunny"},
+ {"weekday": "Wednesday", "weather": "sunny"},
+ {"weekday": "Thursday", "weather": "clouds"},
+ {"weekday": "Friday", "weather": "rain"},
+ {"weekday": "Saturday", "weather": "sunny"},
+]
+
+main = rule {
+ filter days as d {
+ d.weather is not "sunny"
+ }
+}
+```
+
+Here is the output of our policy:
+
+
+
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+ {'\n'}
+
+ Fail - weather.sentinel
+
+ {'\n'}
+ {'\n'}
+
+ weather.sentinel:11:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+
+ {' [\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Sunday"'}
+ {'\n'}
+ {' "weather": "clouds"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Monday"'}
+ {'\n'}
+ {' "weather": "rain"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Thursday"'}
+ {'\n'}
+ {' "weather": "clouds"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Friday"'}
+ {'\n'}
+ {' "weather": "rain"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' ]'}
+
+
+
+
+We can now see all of the weekdays with non-sunny weather.
+
+Note that to prevent formatting issues and excessive output, the human-readable
+trace is limited in the volume of output it will display for collections.
+
+## JSON Output
+
+The trace can also be displayed in JSON by adding `-json` to `sentinel apply`
+and `sentinel test`, and will display the trace in its entirety, unlike the
+standard CLI output.
+
+Here is the result of running `sentinel apply -json` against our weather policy:
+
+```json
+{
+ "can_override": false,
+ "error": null,
+ "duration": 12,
+ "policies": [
+ {
+ "allowed_failure": false,
+ "error": null,
+ "duration": 12,
+ "policy": "weather.sentinel",
+ "result": false,
+ "trace": {
+ "description": "",
+ "error": null,
+ "print": "",
+ "result": false,
+ "rules": {
+ "main": {
+ "desc": "",
+ "ident": "main",
+ "position": {
+ "filename": "weather.sentinel",
+ "offset": 328,
+ "line": 11,
+ "column": 1
+ },
+ "value": [
+ {
+ "weather": "clouds",
+ "weekday": "Sunday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Monday"
+ },
+ {
+ "weather": "clouds",
+ "weekday": "Thursday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Friday"
+ }
+ ]
+ }
+ }
+ }
+ }
+ ],
+ "result": false
+}
+```
+
+Note that the trace is always included with this output, regardless of whether
+or not the policy passes or fails; using `-trace` is unnecessary.
+
+Additionally, with `sentinel apply`, you can display the value of a single rule
+in JSON, instead of the entire trace. This is done with the `-json-rule` flag.
+
+-> **Note:** `sentinel apply -json-rule` needs to be run either against a single
+on-disk policy, or against a single policy within a policy set. It is ignored
+in full policy set executions and functions exactly like `-json` in these
+scenarios.
+
+Here is the result of executing `sentinel apply -json-rule main weather.sentinel`:
+
+```json
+[
+ {
+ "weather": "clouds",
+ "weekday": "Sunday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Monday"
+ },
+ {
+ "weather": "clouds",
+ "weekday": "Thursday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Friday"
+ }
+]
+```
+
+## Other Trace Details
+
+There are some other details that are good to know when working with the trace:
+
+- Only rules that get executed are added to the trace. Considering our first
+ example where the `main` rule is the expression `is_open_hours and is_weekday`,
+ if our expression was using an `or` operator instead of `and`, in addition to
+ the policy passing, `is_open_hours` would be present in the trace, but
+ `is_weekday` would not be, as the policy would have never evaluated that rule.
+- Descriptions for both rules and policies will show up in the trace as well if
+ present. Here is the output to our first policy with the appropriate
+ annotations:
+
+
+
+ ...{'\n\n'}
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+
+ Fail - main.sentinel
+
+ {'\n'}
+ {'\n'}
+ Description:{'\n'}
+ {' A very basic policy to determine if a run is within working\n'}
+ {' hours.\n'}
+ {'\n'}
+
+ main.sentinel:7:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}
+ false
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:5:1 - Rule "is_open_hours"
+
+ {'\n'}
+ {' '}
+ Description:
+ {'\n'}
+ {' Passes if run during business hours.\n'}
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}true{'\n'}
+ {'\n'}
+
+ main.sentinel:4:1 - Rule "is_weekday"
+
+ {'\n'}
+ {' '}
+ Description:
+ {'\n'}
+ {' Passes if the day does not fall on the weekend.\n'}
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}false{'\n'}
+
+
diff --git a/content/sentinel/v0.27.x/content/sentinel/intro/getting-started/first-policy.mdx b/content/sentinel/v0.27.x/content/sentinel/intro/getting-started/first-policy.mdx
new file mode 100644
index 0000000000..38d7b22c9a
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/intro/getting-started/first-policy.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Your First Sentinel Policy
+sidebar_current: intro-getting-started-first
+description: Let's begin by writing a working Sentinel policy.
+layout: intro
+---
+
+# Your First Sentinel Policy
+
+Sentinel is a system to enforce complex policies on an integrated application.
+
+Writing Sentinel policy requires minimal programming experience. The Sentinel
+language is designed to be approachable and learned quickly and easily. Whether
+you're a professional programmer or someone who uses SQL and Excel, you can
+learn to write Sentinel policies.
+
+Let's begin by writing a simple, working Sentinel policy:
+
+```sentinel playground
+hour = 4
+main = rule { hour >= 0 and hour < 12 }
+```
+
+This is a valid Sentinel policy. It will pass since we hardcoded the
+`hour` to be 4. In a real system, `hour` may be something that is provided
+to us and actually set to the current hour. We'll learn more about that later.
+
+For now, try running this policy locally. Save the above example to a file
+named `policy.sentinel` and execute it. Then, modify the policy to make it
+fail. Play around more if you'd like.
+
+```
+$ sentinel apply policy.sentinel
+Pass
+```
+
+## Main
+
+Every Sentinel policy must have a `main` rule. This is the rule that
+is evaluated to determine the result of a policy.
+
+A rule describes an expression that generally means one of two things:
+
+- Does a policy _pass a condition_ that would authorize an operation? In our
+ above example, describe a policy that checks the supplied hour (4) is within an
+ authorized time window (between 0 - midnight, and 12 noon).
+- Conversely, can a policy find any _violations_ that would block authorization
+ of the operation? Building on the above, consider a policy that takes a
+ schedule, and finds all time blocks that fall outside of the example time window
+ supplied in the above policy.
+
+It is easy to imagine that such a rule might be used in a system such
+as [Nomad](https://www.nomadproject.io) to restrict the times when a
+deploy can occur. The power of arbitrary logical statements within Sentinel
+allows Sentinel policies to restrict almost any behavior.
+
+Next, we'll
+[introduce and explain rules](/sentinel/intro/getting-started/rules)
+so we can use them in our policies.
diff --git a/content/sentinel/v0.27.x/content/sentinel/intro/getting-started/imports.mdx b/content/sentinel/v0.27.x/content/sentinel/intro/getting-started/imports.mdx
new file mode 100644
index 0000000000..dbbb146ecf
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/intro/getting-started/imports.mdx
@@ -0,0 +1,58 @@
+---
+page_title: Imports
+sidebar_current: intro-getting-started-imports
+description: Imports enable Sentinel to access external data and functions.
+layout: intro
+---
+
+# Imports
+
+Imports enable Sentinel to access external data and functions.
+
+Sentinel ships with a set of [standard imports](/sentinel/imports).
+Standard imports are available by default to every Sentinel policy. Note that
+the application embedding Sentinel may allow or deny the available
+set of imports.
+
+To use an import, use the `import` statement. Then, access data and
+functions on the import using the import name followed by a period and
+the field you want to access. The example below checks whether the request
+path starts with a prefix. Assume that `request_path` is available.
+
+```sentinel
+import "strings"
+
+main = rule { strings.has_prefix(request_path, "/admin") }
+```
+
+## External Data
+
+The true power in imports is their ability to reference external data.
+Imports can be added to a Sentinel-enabled application through
+[plugins](/sentinel/extending). This enables any Sentinel-enabled
+application to make policy decision based on any source of data.
+
+This ability allows the policy system to enforce almost any necessary
+organizational policy, since the ability of a policy isn't restricted
+purely to the embedding application's data model.
+
+For example, policies in [Nomad](https://www.nomadproject.io) can
+access data in [Consul](https://www.consul.io) to determine attributes
+of a policy. In the example below, we use a hypothetical Consul import:
+
+```sentinel
+import "consul"
+
+main = rule {
+ job.tasks[0].resources.memory <= int(consul.get("policy/nomad/max-memory"))
+}
+```
+
+In this example, a Nomad job's memory usage is limited to the value of
+a Consul key/value item. By simply changing a Consul KV entry, policy
+can be changed. Imports can do anything, you can [write your own import plugins](/sentinel/extending)
+to extend Sentinel.
+
+Next, we'll learn how to
+[test policies](/sentinel/intro/getting-started/testing)
+to ensure our policy logic is correct.
diff --git a/content/sentinel/v0.27.x/content/sentinel/intro/getting-started/index.mdx b/content/sentinel/v0.27.x/content/sentinel/intro/getting-started/index.mdx
new file mode 100644
index 0000000000..5c9b6b43c5
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/intro/getting-started/index.mdx
@@ -0,0 +1,16 @@
+---
+page_title: Install Sentinel CLI - Getting Started
+sidebar_current: intro-getting-started-overview
+description: The first step to using Sentinel is to get the CLI installed.
+layout: intro
+---
+
+# Getting Started With Sentinel
+
+This section covers getting started with Sentinel. Here, we will take you
+through the first steps that you will need to do to get started with installing
+the Sentinel CLI, writing your first policy, and getting familiar with rules,
+boolean logic, imports, and testing.
+
+You can select a topic on the menu to get started. The first step is to [install
+the Sentinel CLI](/sentinel/intro/getting-started/install).
diff --git a/content/sentinel/v0.27.x/content/sentinel/intro/getting-started/install.mdx b/content/sentinel/v0.27.x/content/sentinel/intro/getting-started/install.mdx
new file mode 100644
index 0000000000..a23a5b7239
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/intro/getting-started/install.mdx
@@ -0,0 +1,56 @@
+---
+page_title: Install Sentinel CLI - Getting Started
+sidebar_current: intro-getting-started-install
+description: The first step to using Sentinel is to get the CLI installed.
+layout: intro
+---
+
+# Install Sentinel CLI
+
+Sentinel is a policy framework that is embedded in the enterprise versions of
+HashiCorp tools. The policies you write are deployed to these applications and
+enforced there.
+
+The Sentinel CLI is a command-line interface for local development and testing.
+For the getting started guide, we'll use the CLI to learn how to write policies
+for Sentinel-enabled applications. The Sentinel CLI is distributed as a [binary
+package](/sentinel/install) for all supported platforms and architectures.
+
+## Installation Instructions
+
+To install the Sentinel CLI, find the [appropriate package](/sentinel/install) for your
+system and download it. The CLI is packaged as a zip archive.
+
+After downloading Sentinel, unzip the package. The CLI runs as a single binary
+named `sentinel`. Any other files in the package can be safely removed and
+Sentinel will still function.
+
+The final step is to make sure that the `sentinel` binary is available on the `PATH`.
+See [this page](https://stackoverflow.com/questions/14637979/how-to-permanently-set-path-on-linux)
+for instructions on setting the PATH on Linux and Mac.
+[This page](https://stackoverflow.com/questions/1618280/where-can-i-set-path-to-make-exe-on-windows)
+contains instructions for setting the PATH on Windows.
+
+## Verifying the Installation
+
+After installing the Sentinel CLI, verify the installation worked by opening a
+new terminal session and checking that the `sentinel` binary is available. By
+executing `sentinel`, you should see help output similar to the following:
+
+```
+$ sentinel
+Usage: sentinel [--version] [--help] []
+
+Available commands are:
+ apply Execute a policy and output the result
+ fmt Format Sentinel policy to a canonical format
+ test Test policies
+ version Prints the Sentinel version
+```
+
+If you get an error that the binary could not be found, then your `PATH`
+environment variable was not setup properly. Please go back and ensure that your
+`PATH` variable contains the directory where Sentinel was installed.
+
+Otherwise, the CLI is installed and ready to go. Let's celebrate by [writing our
+first policy!](/sentinel/intro/getting-started/first-policy)
diff --git a/content/sentinel/v0.27.x/content/sentinel/intro/getting-started/logic.mdx b/content/sentinel/v0.27.x/content/sentinel/intro/getting-started/logic.mdx
new file mode 100644
index 0000000000..d3716a64ea
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/intro/getting-started/logic.mdx
@@ -0,0 +1,54 @@
+---
+page_title: Logic
+sidebar_current: intro-getting-started-logic
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Logic
+
+The core of a policy is a set of logical expressions to determine whether
+a behavior is allowed or not.
+Sentinel makes it easy to write readable logical expressions to express
+the intended policy.
+
+The logical expression syntax will be familiar to anyone who has programmed
+before. Sentinel also supports a couple more unique constructs to assist
+with common policy requirements. Detailed documentation on how to write
+logical expressions and the supported operators is available in
+[the boolean expression reference](/sentinel/language/boolexpr).
+
+Example logic:
+
+```sentinel
+a is b // Equality, you can also use ==
+a is not b // Inequality, you can also use !=
+
+a > b // Relational operators, comparison
+a < b
+a >= b
+a <= b
+
+a contains b // Inclusion check, substrings, etc.
+a not contains b // Negated version of above
+```
+
+The full list of available operators can be found in
+[the boolean expression reference](/sentinel/language/boolexpr).
+
+Logic can be combined with `and` or `or`. When combined with `and`, both
+sides must result in `true`. When combined with `or`, only one side needs
+to be true to result in `true`.
+
+With these building blocks, basic rules can be built up:
+
+```sentinel
+main = rule {
+ hour >= 8 and
+ hour < 17
+}
+```
+
+Next, we'll introduce
+[imports](/sentinel/intro/getting-started/imports)
+so that we can write rules that interact with external data.
diff --git a/content/sentinel/v0.27.x/content/sentinel/intro/getting-started/next-steps.mdx b/content/sentinel/v0.27.x/content/sentinel/intro/getting-started/next-steps.mdx
new file mode 100644
index 0000000000..23b470be59
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/intro/getting-started/next-steps.mdx
@@ -0,0 +1,21 @@
+---
+page_title: Next Steps
+sidebar_current: intro-getting-started-nextsteps
+description: That concludes the getting started guide for Sentinel.
+layout: intro
+---
+
+# Next Steps
+
+That concludes the getting started guide for Sentinel. Hopefully you're excited
+about the possibilities of Sentinel and ready to put this knowledge to use to
+improve the safety of your environments.
+
+We've covered the basics of the core features of Sentinel in this guide.
+We recommend the following as next steps:
+
+- [Documentation](/sentinel) - The documentation is an in-depth
+ reference guide of all the features of Sentinel.
+
+- [Language Reference](/sentinel/language) - The language reference
+ is a complete reference to the features of the Sentine policy language.
diff --git a/content/sentinel/v0.27.x/content/sentinel/intro/getting-started/rules.mdx b/content/sentinel/v0.27.x/content/sentinel/intro/getting-started/rules.mdx
new file mode 100644
index 0000000000..f12a61fe75
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/intro/getting-started/rules.mdx
@@ -0,0 +1,86 @@
+---
+page_title: Rules
+sidebar_current: intro-getting-started-rules
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Rules
+
+Rules in Sentinel are a first-class concept. Within a policy, rules serve a few
+purposes:
+
+- They make complex logic more understandable by allowing said logic to be
+ broken down.
+- They allow assertion of this logic through
+ [testing](/sentinel/intro/getting-started/testing) of the rule's contents.
+- They facilitate reporting of data as rules get published as part of the [policy
+ trace](/sentinel/writing/tracing).
+
+A rule functions in ways similar to both a variable and a function: they hold a
+value, but are lazily evaluated; a rule's value is not assigned until the first
+time it's referenced in a policy. Additionally, while the value of evaluated
+rules will be available within a policy's trace after it's evaluated, values of
+variables - and the return value of functions - are not. Finally, a rule value
+is memoized - further references to the rule will not change its result.
+
+Rules can hold more than just boolean data. For more advanced rule patterns, see
+[the language reference](/sentinel/language/rules).
+
+## Writing Rules
+
+Let's look at the Sentinel policy we wrote in the previous section:
+
+```sentinel playground
+hour = 4
+main = rule { hour >= 0 and hour < 12 }
+```
+
+The logic in this policy is straightforward and easy to understand. However,
+it is common for policy logic to become much more complicated. Rules are the
+key abstraction for making complex logic understandable. We can turn the
+policy above into a set of rules:
+
+```sentinel playground
+hour = 4
+
+past_midnight = rule { hour >= 0 }
+before_noon = rule { hour < 12 }
+
+main = rule {
+ past_midnight and
+ before_noon
+}
+```
+
+This policy is easier to read. Our original policy was already quite
+simple, but it is easy to imagine a more complex policy becoming much
+more readable by extracting logical expressions into a set of rules.
+
+Rules don't just exist to make a policy more readable. They're also a
+testing and debugging point. For testing, you can assert that certain rules
+are certain values given a specific input to verify that your policy works
+as you expect. Testing and debugging are covered in later sections.
+
+## Conditional Rules
+
+In many cases, a rule is only valid when something else is also true.
+
+Consider the human language statement "before you eat, you must wash your hands."
+The rule "you must wash your hands" is only valid "before you eat". In any
+other scenario, the rule is meaningless.
+This is also true in a technical context. Imagine the rule "if the driver
+is Docker, you must not expose any ports." For this example, assume
+rules `is_docker` and `has_no_exposed_ports` exist. You can write this rule
+like this:
+
+```sentinel
+main = rule when is_docker { has_no_exposed_ports }
+```
+
+When `is_docker` is true, the rule is evaluated as normal. However,
+when `is_docker` is false, the rule always returns `true`, because the
+logic of the rule is meaningless.
+
+Let's learn how to create more complex rules with
+[logical expressions](/sentinel/intro/getting-started/logic).
diff --git a/content/sentinel/v0.27.x/content/sentinel/intro/getting-started/testing.mdx b/content/sentinel/v0.27.x/content/sentinel/intro/getting-started/testing.mdx
new file mode 100644
index 0000000000..22a720667b
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/intro/getting-started/testing.mdx
@@ -0,0 +1,72 @@
+---
+page_title: Testing
+sidebar_current: intro-getting-started-testing
+description: A rule describes a set of conditions that result in either true or false.
+layout: intro
+---
+
+# Testing
+
+It is important to ensure the policies you write are correct. Sentinel
+includes a built-in test framework that can be run locally and in CI
+environments to test that policies behave as expected.
+
+A common pitfall with many simple ACL systems is that they provide no easy
+way to verify their correctness. You basically have to set the ACL and try
+the behaviors against a real system to verify it is working as expected.
+This requires a lot of setup and is unique to each system.
+
+[Sentinel's built-in test framework](/sentinel/writing/testing) has zero
+dependencies. Contained within the [Sentinel CLI](/sentinel/commands), it can
+mock the data that real systems are exposing to the policy. It is designed to be
+CI-friendly and enables continuous testing of your policies. This is necessary
+for [policy as code](/sentinel/concepts/policy-as-code).
+
+Detailed documentation on testing policies is available in [the Sentinel Testing
+reference](/sentinel/writing/testing).
+
+## Writing Tests
+
+For this example, save the following policy as `officehours.sentinel`:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Next, let's write a passing test case. This will test that the policy actually passes
+when we expect it to pass. Sentinel is opiniated about the [test folder structure](/sentinel/docs/writing/testing#test-folder-structure). Save the following in `test/officehours/good.hcl`:
+
+```hcl
+global "day" {
+ value = "monday"
+}
+
+global "hour" {
+ value = 14
+}
+```
+
+And run `sentinel test`:
+
+```
+$ sentinel test
+PASS - officehours.sentinel
+ PASS - test/officehours/good.hcl
+```
+
+The `sentinel test` command will automatically find all Sentinel policies
+and their associated test cases and run them all. The test framework will
+run all HCL files as individual test cases, allowing you to test a variety
+of scenarios for your policies.
+
+Try adding another test case to force the policy to fail. This test case can
+be saved at `test/officehours/fail.hcl`. For test cases with intentional
+failures, you'll need to use the [test assertions described in the testing
+reference](/sentinel/writing/testing#a-failing-test).
+
+Now that you have a good grasp of what Sentinel is and how to use it, please feel
+free to look through the
+[next steps](/sentinel/intro/getting-started/next-steps)
+you can take to further your Sentinel knowledge.
diff --git a/content/sentinel/v0.27.x/content/sentinel/intro/index.mdx b/content/sentinel/v0.27.x/content/sentinel/intro/index.mdx
new file mode 100644
index 0000000000..785a442fb8
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/intro/index.mdx
@@ -0,0 +1,32 @@
+---
+layout: intro
+page_title: Documentation
+sidebar_current: docs-home
+description: >-
+ Sentinel is a language and framework for policy built to be embedded in
+ existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+[introduction](/sentinel/intro) to understand what Sentinel is, how it
+compares to other software, and more.
+
+
diff --git a/content/sentinel/v0.27.x/content/sentinel/intro/what.mdx b/content/sentinel/v0.27.x/content/sentinel/intro/what.mdx
new file mode 100644
index 0000000000..36853d366c
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/intro/what.mdx
@@ -0,0 +1,64 @@
+---
+page_title: Introduction
+sidebar_current: intro-what
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: intro
+---
+
+# Introduction to Sentinel
+
+Welcome to the intro guide to Sentinel! This guide is the best
+place to start with Sentinel.
+
+If you are already familiar with the basics of Sentinel, the
+[documentation](/sentinel) provides a better reference
+guide for all available features as well as internals.
+
+## What is Sentinel?
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+Sentinel provides a language for writing policy and a workflow for developing
+and testing policies independent of the software they'll be written to. We
+also provide a framework for developers to build plugins that allow a
+Sentinel-enabled system to access external information to make policy
+decisions.
+
+Most systems today have some degree of access control. You are able to define
+identities and what they have access to. These ACL systems solve an immediate
+and necessary problem of locking down a system in very broad strokes. Sentinel
+is a reusable system for more advanced software policy decisions. Sentinel
+enables:
+
+- **Fine-Grained Policy**: Most ACL systems only enable coarse-grained
+ behaviors: "read", "write", etc. Sentinel enables fine-grained behavior such
+ as disallowing a certain API call when specific parameters are present.
+
+- **Logic-Based Policy**: You can write policy using full
+ conditional logic. For example, you may only allow a certain application behavior
+ on Monday to Thursday unless there is a manager override.
+
+- **Accessing External Information**: Sentinel can source external information
+ to be used in policy decisions. For example, a policy that restricts the size
+ of a payload may read data from [Consul](https://www.consul.io) to determine
+ the payload size limit.
+
+- **Enforcement levels**: Sentinel allows policies to be defined along
+ with an "enforcement level" that dictates the pass/fail behavior of a policy.
+ Advisory policies warn if they fail, soft mandatory policies can have their
+ failures overridden, and hard mandatory policies must pass under all
+ circumstances. Having this as a built-in concept enables you to model
+ policy more accurately and completely for your organization.
+
+## Next Steps
+
+See the page on [Why Sentinel?](/sentinel/intro/why) to learn more
+about the origins of the product. Then, continue onwards with
+the [getting started guide](/sentinel/intro/getting-started/install) to write
+your first policies and see how Sentinel works in practice.
diff --git a/content/sentinel/v0.27.x/content/sentinel/intro/why.mdx b/content/sentinel/v0.27.x/content/sentinel/intro/why.mdx
new file mode 100644
index 0000000000..98b58aab92
--- /dev/null
+++ b/content/sentinel/v0.27.x/content/sentinel/intro/why.mdx
@@ -0,0 +1,49 @@
+---
+page_title: Why Sentinel?
+sidebar_current: intro-why
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: intro
+---
+
+# Why Sentinel?
+
+The growth of infrastructure and applications has been enabled in part
+by an increasing trend towards automation everywhere.
+Configuration as code (such as Chef or Puppet with [Packer](https://www.packer.io))
+has enabled automated machine configuration. Infrastructure
+as code (such as [Terraform](https://www.terraform.io)) has enabled
+automatic infrastructure creation. And schedulers (such as
+[Nomad](https://www.nomadproject.io) and Kubernetes) have enabled
+automatic application deployment.
+Sentinel enables guardrails to be put in place
+on automation while allowing the codification and automatic enforcement
+of business requirements in critical areas of your infrastructure.
+
+Meanwhile, businesses have business requirements and sometimes legal
+requirements which must be expressed in policies. Traditionally, these
+policies are enforced by humans. But in a highly automated world, the
+automation is only as fast as its slowest component. In many cases, this
+is the human verification step.
+
+As an example: before infrastructure as code and autoscaling, if an order
+came through for 5,000 new machines, a human would likely respond to the
+ticket verifying that the user really intended to order 5,000 new machines.
+Today, automation can almost always freely order 5,000 new compute instances
+without any hesitation, which can result in unintended expense or system
+instability.
+
+Sentinel introduces [policy as code](/sentinel/concepts/policy-as-code)
+and a powerful framework built-in to HashiCorp tooling to allow automation
+guardrails, business requirements, legal compliance, and more to be
+actively enforced by the running systems in realtime.
+
+With Sentinel, you can require an override to create certain numbers of
+infrastructure resources. You can disallow unsafe deployment configurations
+with Nomad. You can enforce certain key/value formats in Consul. You
+can restrict secret access by time in Vault. And more.
+
+Sentinel is available today in HashiCorp enterprise products. You can
+try Sentinel immediately with the [getting started guide](/sentinel/intro/getting-started/install) or learn more about the integrations in the
+[documentation](/sentinel).
diff --git a/content/sentinel/v0.27.x/data/docs-nav-data.json b/content/sentinel/v0.27.x/data/docs-nav-data.json
new file mode 100644
index 0000000000..3c24a533cc
--- /dev/null
+++ b/content/sentinel/v0.27.x/data/docs-nav-data.json
@@ -0,0 +1,387 @@
+[
+ {
+ "title": "Release Notes",
+ "path": "changelog"
+ },
+ {
+ "title": "Basic Concepts",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "concepts"
+ },
+ {
+ "title": "Policy as Code",
+ "path": "concepts/policy-as-code"
+ },
+ {
+ "title": "Policy Language",
+ "path": "concepts/language"
+ },
+ {
+ "title": "Imports",
+ "path": "concepts/imports"
+ },
+ {
+ "title": "Enforcement Levels",
+ "path": "concepts/enforcement-levels"
+ }
+ ]
+ },
+ {
+ "title": "Configuration File Syntax",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "configuration"
+ },
+ {
+ "title": "Override Files",
+ "path": "configuration/overrides"
+ },
+ {
+ "title": "Remote Sources",
+ "path": "configuration/remote-sources"
+ }
+ ]
+ },
+ {
+ "title": "Commands (CLI)",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "commands"
+ },
+ {
+ "title": "apply",
+ "path": "commands/apply"
+ },
+ {
+ "title": "fmt",
+ "path": "commands/fmt"
+ },
+ {
+ "title": "test",
+ "path": "commands/test"
+ }
+ ]
+ },
+ {
+ "title": "Writing Policy",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "writing"
+ },
+ {
+ "title": "Basics",
+ "path": "writing/basic"
+ },
+ {
+ "title": "Rules",
+ "path": "writing/rules"
+ },
+ {
+ "title": "Traces",
+ "path": "writing/tracing"
+ },
+ {
+ "title": "Testing",
+ "path": "writing/testing"
+ },
+ {
+ "title": "Imports",
+ "path": "writing/imports"
+ },
+ {
+ "title": "Debugging",
+ "path": "writing/debugging"
+ }
+ ]
+ },
+ {
+ "title": "Extending Sentinel",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "extending"
+ },
+ {
+ "title": "Modules",
+ "path": "extending/modules"
+ },
+ {
+ "title": "Plugins",
+ "path": "extending/plugins"
+ },
+ {
+ "title": "Static Imports",
+ "path": "extending/static-imports"
+ },
+ {
+ "title": "Internals",
+ "path": "extending/internals"
+ }
+ ]
+ },
+ {
+ "title": "Features",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "features"
+ },
+ {
+ "title": "terraform",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "features/terraform"
+ },
+ {
+ "title": "tfplan/v1",
+ "path": "features/terraform/tfplan-v1"
+ },
+ {
+ "title": "tfplan/v2",
+ "path": "features/terraform/tfplan-v2"
+ },
+ {
+ "title": "tfconfig/v1",
+ "path": "features/terraform/tfconfig-v1"
+ },
+ {
+ "title": "tfconfig/v2",
+ "path": "features/terraform/tfconfig-v2"
+ },
+ {
+ "title": "tfstate/v1",
+ "path": "features/terraform/tfstate-v1"
+ },
+ {
+ "title": "tfstate/v2",
+ "path": "features/terraform/tfstate-v2"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Language",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "language"
+ },
+ {
+ "title": "Variables",
+ "path": "language/variables"
+ },
+ {
+ "title": "Values",
+ "path": "language/values"
+ },
+ {
+ "title": "Lists",
+ "path": "language/lists"
+ },
+ {
+ "title": "Maps",
+ "path": "language/maps"
+ },
+ {
+ "title": "Rules",
+ "path": "language/rules"
+ },
+ {
+ "title": "Imports",
+ "path": "language/imports"
+ },
+ {
+ "title": "Parameters",
+ "path": "language/parameters"
+ },
+ {
+ "title": "Boolean Expressions",
+ "path": "language/boolexpr"
+ },
+ {
+ "title": "Arithmetic",
+ "path": "language/arithmetic"
+ },
+ {
+ "title": "Slices",
+ "path": "language/slices"
+ },
+ {
+ "title": "Conditionals",
+ "path": "language/conditionals"
+ },
+ {
+ "title": "Loops",
+ "path": "language/loops"
+ },
+ {
+ "title": "Collection Operations",
+ "path": "language/collection-operations"
+ },
+ {
+ "title": "Functions",
+ "path": "language/functions"
+ },
+ {
+ "title": "Scope",
+ "path": "language/scope"
+ },
+ {
+ "title": "Undefined",
+ "path": "language/undefined"
+ },
+ {
+ "title": "Logging and Errors",
+ "path": "language/logging-errors"
+ },
+ {
+ "title": "Specification",
+ "path": "language/spec"
+ }
+ ]
+ },
+ {
+ "title": "Builtin Functions",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "functions"
+ },
+ {
+ "title": "append",
+ "path": "functions/append"
+ },
+ {
+ "title": "compare",
+ "path": "functions/compare"
+ },
+ {
+ "title": "delete",
+ "path": "functions/delete"
+ },
+ {
+ "title": "error",
+ "path": "functions/error"
+ },
+ {
+ "title": "keys",
+ "path": "functions/keys"
+ },
+ {
+ "title": "length",
+ "path": "functions/length"
+ },
+ {
+ "title": "print",
+ "path": "functions/print"
+ },
+ {
+ "title": "range",
+ "path": "functions/range"
+ },
+ {
+ "title": "values",
+ "path": "functions/values"
+ }
+ ]
+ },
+ {
+ "title": "Standard Imports",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "imports"
+ },
+ {
+ "title": "collection",
+ "routes": [
+ {
+ "title": "Reference",
+ "path": "imports/collection"
+ },
+ {
+ "title": "collection/maps",
+ "path": "imports/collection/maps"
+ },
+ {
+ "title": "collection/lists",
+ "path": "imports/collection/lists"
+ }
+ ]
+ },
+ {
+ "title": "base64",
+ "path": "imports/base64"
+ },
+ {
+ "title": "decimal",
+ "path": "imports/decimal"
+ },
+ {
+ "title": "http",
+ "path": "imports/http"
+ },
+ {
+ "title": "json",
+ "path": "imports/json"
+ },
+ {
+ "title": "runtime",
+ "path": "imports/runtime"
+ },
+ {
+ "title": "sockaddr",
+ "path": "imports/sockaddr"
+ },
+ {
+ "title": "strings",
+ "path": "imports/strings"
+ },
+ {
+ "title": "time",
+ "path": "imports/time"
+ },
+ {
+ "title": "types",
+ "path": "imports/types"
+ },
+ {
+ "title": "units",
+ "path": "imports/units"
+ },
+ {
+ "title": "version",
+ "path": "imports/version"
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Consul",
+ "path": "consul"
+ },
+ {
+ "title": "Nomad",
+ "path": "nomad"
+ },
+ {
+ "title": "Terraform",
+ "path": "terraform"
+ },
+ {
+ "title": "Vault",
+ "path": "vault"
+ }
+]
diff --git a/content/sentinel/v0.27.x/data/intro-nav-data.json b/content/sentinel/v0.27.x/data/intro-nav-data.json
new file mode 100644
index 0000000000..c95181af70
--- /dev/null
+++ b/content/sentinel/v0.27.x/data/intro-nav-data.json
@@ -0,0 +1,47 @@
+[
+ {
+ "title": "What is Sentinel?",
+ "path": "what"
+ },
+ {
+ "title": "Why Sentinel?",
+ "path": "why"
+ },
+ {
+ "title": "Getting Started",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "getting-started"
+ },
+ {
+ "title": "Installing the CLI",
+ "path": "getting-started/install"
+ },
+ {
+ "title": "Your First Policy",
+ "path": "getting-started/first-policy"
+ },
+ {
+ "title": "Rules",
+ "path": "getting-started/rules"
+ },
+ {
+ "title": "Logic",
+ "path": "getting-started/logic"
+ },
+ {
+ "title": "Imports",
+ "path": "getting-started/imports"
+ },
+ {
+ "title": "Testing",
+ "path": "getting-started/testing"
+ },
+ {
+ "title": "Next Steps",
+ "path": "getting-started/next-steps"
+ }
+ ]
+ }
+]
diff --git a/content/sentinel/v0.27.x/img/sentinel-import-topology.svg b/content/sentinel/v0.27.x/img/sentinel-import-topology.svg
new file mode 100644
index 0000000000..36f7c79bc9
--- /dev/null
+++ b/content/sentinel/v0.27.x/img/sentinel-import-topology.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/content/sentinel/v0.27.x/redirects.jsonc b/content/sentinel/v0.27.x/redirects.jsonc
new file mode 100644
index 0000000000..1c7c77f138
--- /dev/null
+++ b/content/sentinel/v0.27.x/redirects.jsonc
@@ -0,0 +1,7 @@
+[
+ {
+ "source": "/sentinel/commands/config",
+ "destination": "/sentinel/configuration",
+ "permanent": true,
+ },
+]
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/changelog.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/changelog.mdx
new file mode 100644
index 0000000000..a9c2700426
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/changelog.mdx
@@ -0,0 +1,922 @@
+---
+page_title: Sentinel Runtime Release Notes
+sidebar_current: docs-changelog
+description: >-
+ These are the public release notes for the Sentinel runtime. See this document
+ for the latest updates.
+layout: docs
+---
+
+# Sentinel Runtime Release Notes
+
+These are the release notes for the Sentinel runtime.
+
+Each version of the runtime is released with a corresponding version of
+[Sentinel CLI](/sentinel/commands). To download the CLI, see the [install
+page](/sentinel/install).
+
+Sentinel integrations and embedded runtimes may not always have the latest
+version installed, depending on the product's individual release cycle. For more
+information, contact the support team for your specific integration.
+
+
+## 0.28.0 (September 26, 2024)
+
+BUG FIXES:
+
+- `imports/collection`: The `matches` helper now returns no results if there
+are no matches found in the collection.
+
+ENHANCEMENTS:
+
+- `cmd/apply`: A new argument, `-timeout`, has been added to the apply command.
+This argument will allow the apply command to exit after a certain time has been
+reached.
+
+NOTES:
+
+- This release introduces a EULA and Terms Of Evaluation to the release artifacts,
+which will be included in all releases going forward.
+
+## 0.27.0 (August 7, 2024)
+
+ENHANCEMENTS:
+- `imports/collection/maps`: Added an `unset` helper to the `collection/maps`
+import, providing the ability to remove keys using a provided path.
+- `imports/collection/maps`: Added an `omit` helper to the `collection/maps`
+import, which returns a map with all provided paths removed from the source
+map.
+- `imports/strings`: Added the `replace`, `trim`, `trim_left`, `trim_right` and
+`trim_space` helpers to the `strings` import.
+
+## 0.26.3 (July 19, 2024)
+
+ENHANCEMENTS:
+
+- `imports/collection/maps`: Added a `set` helper to the `collection/maps`
+import, providing the ability to set values deeply within a map.
+- `imports/collection/maps`: Added a `pick` helper to the `collection/maps`
+import, providing the ability to select a series of values from a map.
+
+## 0.26.2 (June 19, 2024)
+
+ENHANCEMENTS:
+
+- `imports/collection`: Added a `filter` helper to the `collection` import,
+providing a functional approach to filtering collections.
+
+## 0.26.1 (May 22, 2024)
+
+NOTES:
+
+- This release changes lower-level components and there are no user-facing changes.
+
+## 0.26.0 (May 15, 2024)
+
+FEATURES:
+
+- `imports/collection`, `imports/collection/lists` and `import/collection/maps`:
+Adding new helper imports for dealing with collections (lists, maps), helping
+avoid complexities within policy code.
+- `sentinel/eval`: Fixed an issue with per-policy parameters where parameters
+may leak from one policy into the following policy.
+
+## 0.25.1 (Apr 18, 2024)
+
+BREAKING CHANGES:
+
+- `imports/http`: The default `Accept` header has been changed to `*/*`, removing a redundant
+extension that some servers may not accept by default.
+
+## 0.25.0 (Apr 8, 2024)
+
+ENHANCEMENTS:
+
+- `cmd/apply`: JSON results will return an additional `duration` field for individual policies as well as the evaluation as a whole.
+- `cmd/test`: `sentinel test` will return an additional `duration` field in the JSON output.
+
+BUG FIXES:
+
+- `cmd`: Removed redundant debug logging for custom plugins.
+
+## 0.24.4 (Mar 21, 2024)
+
+ENHANCEMENTS:
+
+- `runtime/format`: Null values will now print correctly for rule value outputs.
+- `config`: Some internal changes to configuration parsing workflow.
+
+## 0.24.3 (Feb 9, 2024)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.24.2 (Jan 31, 2024)
+
+BUG FIXES:
+
+- `imports/static`: Fixed an issue where `nil` values provided to the built-in
+static import were being treated as `undefined` in policy.
+
+## 0.24.1 (Jan 19, 2024)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where the Sentinel cache was unstable due to concurrent
+tests.
+
+## 0.24.0 (Dec 7, 2023)
+
+ENHANCEMENTS:
+
+- `cmd/apply`, `cmd/test`: Custom import plugins can now be fetched from remote
+sources.
+- `cmd/apply`, `cmd/test`: Static imports can now be fetched from remote
+sources.
+- `features/terraform`: Added support for `resource_drift` in the `tfplan/v2` import.
+
+## 0.23.1 (Oct 19, 2023)
+
+BUG FIXES:
+
+- `cmd/apply`: Warnings and errors are included in the JSON output if the json flag is enabled.
+
+## 0.23.0 (Sept 5, 2023)
+
+FEATURES:
+
+- `features/apply_all`: Adds the `apply-all` feature, allowing for all policies to evaluate regardless of result, instead of exiting on first failure.
+
+BUG FIXES:
+
+- `features/terraform`: The `tfplan/v1` import correctly handles nested attribute schemas.
+
+## 0.22.1 (June 22, 2023)
+
+BUG FIXES:
+
+- `sentinel/eval`: Under certain conditions, per-policy parameters would cause
+ Sentinel to panic. This has been resolved.
+
+## 0.22.0 (May 31, 2023)
+
+- `config`: Configuration now supports a `sentinel` block to manage the
+ Sentinel runtime.
+
+## 0.21.1 (May 8, 2023)
+
+BUG FIXES:
+
+- `config`: An issue with certain identifiers being treated as incorrectly invalid
+ has been resolved.
+
+## 0.21.0 (March 8, 2023)
+
+BREAKING CHANGES:
+
+- `lang/ast`: `is empty` and `is not empty` are now treated as `ast.UnaryExpr`
+ expressions, with `ast.IsEmptyExpr` being removed.
+- `lang/ast`: You can now assert if a value is defined or not using the `is defined`
+ and `is not defined` syntax.
+
+FEATURES:
+
+- `config`: Parameter values can now be provided for individual policies within
+ a policy block.
+
+## 0.20.0 (February 16, 2023)
+
+ENHANCEMENTS:
+
+- `cmd/testcmd`: Allows sentinel tests to run concurrently by default. Sequential style testing can
+ be enabled by running with the `-maxConcurrency=1` option.
+- `cmd/testcmd`: Allows sentinel test command to timeout after a certain duration. This can be provided
+ by the user or will default to 5 minutes.
+- `cmd/apply`: The policy enforcement level is now included in the JSON output.
+- `lang/ast`: Functions can now be declared as named statements, providing
+ a safer function declaration.
+
+BREAKING CHANGES:
+
+- `cmd/apply`: Policies provided directly to the apply command will now default their enforcement
+ level to `advisory`, aligning with the `policy` configuration block.
+- `sentinel`: JSON results will no longer return `allowed_failure` or `can_override` fields.
+- `sentinel/result`: A new package has been added which provides additional methods to return
+ supplemental data about the evaluation result.
+
+## 0.19.5 (February 9, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `SelectorExpr` will correctly rewrite `X`.
+
+## 0.19.4 (February 8, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `Compile` function will correctly set the `Original`
+ field when rewriting a `CallExpr` as an `ImportExpr`.
+
+## 0.19.3 (February 3, 2023)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.19.2 (January 17, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `Compile` function will no longer modify `ast.CallExpr`
+ arguments in place.
+
+## 0.19.1 (December 14, 2022)
+
+BUG FIXES:
+
+- `lang/ast`: The `Rewrite` function will no longer modify the provided Node
+ in place.
+
+ENHANCEMENTS:
+
+- `lang/semantic`: The Break semantic check uses the AST walker.
+
+## 0.19.0 (December 6, 2022)
+
+BREAKING CHANGES:
+
+- `config`: Attempts to override a standard import with a custom import will
+ now be met with an error.
+- `imports`: All plugin imports now return `sdk.Plugin` instead of `sdk.Import`,
+ as per the SDK update to v0.4.0.
+
+ENHANCEMENTS:
+
+- `runtime/importer`: An `Import` and `ImportInstance` interface have been
+ added to improve the import capabilities.
+- `runtime/importer`: The `Importer` interface now returns a `Import` interface
+ instead of a `sdk.Import`.
+- `lang/semantic`: The Import Assignment semantic check uses the AST walker.
+- `lang/ast`: Added an AST Walker so the AST rewriter does not need to be used for basic AST tasks.
+
+FEATURES:
+
+- `config`: Configuration syntax now allows for configuration of imports within
+ the standard library.
+- `config`: A new configuration syntax for defining modules and plugins is now
+ available.
+- Static data can now be added via `import` configuration.
+- `cmd/apply`: The `apply` command now supports multiple primary configuration
+ files by default, loading all configuration files within the working
+ directory.
+- `cmd/apply`: The `apply` command now accepts applying configuration overrides
+ to primary configuration.
+
+## 0.18.13 (October 31, 2022)
+
+BUG FIXES:
+
+- `cmd/apply`: `sentinel apply` no longer aborts on advisory policy failure.
+
+## 0.18.12 (September 15, 2022)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where nested `CallExpr` were not rewritten.
+
+## 0.18.11 (June 8, 2022)
+
+NOTES:
+
+- `config`: The `enforcement_level` key for policies is now optional, defaulting to `"advisory"` when not supplied.
+
+## 0.18.10 (May 20, 2022)
+
+NOTES:
+
+- `runtime/initwd`: Improvements to the installer, including timeouts and filesize limits, as well as disabling support
+ for symlinks within `git` or `hg` repositories.
+
+## 0.18.9 (March 23, 2022)
+
+NOTES:
+
+- This release fixes an issue with the Docker container build pipeline. There are no changes to the
+Sentinel runtime or CLI.
+
+## 0.18.8 (March 22, 2022)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue in the installer for Windows paths.
+
+## 0.18.7 (February 24, 2022)
+
+NOTES:
+
+- **Darwin arm64**. This release will be our first to include Darwin arm64 architecture.
+
+BUG FIXES:
+
+- `lang/ast`: Fixed issues where `IndexSpec` and `RuleLit` nodes where returning incorrect end positions.
+- `lang/ast`: Fixed an issue where `ParamSpec` nodes where returning incorrect end positions.
+
+## 0.18.6 (February 2, 2022)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.18.5 (January 14, 2022)
+
+IMPROVEMENTS:
+
+- `runtime/initwd`: Changed the sum algorithm used during remote installation from `md5` to `sha256` to reduce chance of collision.
+
+## 0.18.4 (July 20, 2021)
+
+FEATURES:
+
+- `imports/static`: Improved handling of struct tags when performing encoding, allowing `-` to mark as ignored.
+
+## 0.18.3 (June 1, 2021)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue for remote source installation where duplicate sub-directories resulted in incorrect installation.
+- `imports/static`: Fixed an issue where global configuration that contained an empty map could not be accessed without raising selector issues.
+
+## 0.18.2 (May 18, 2021)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where import expressions inside lists and maps were not being evaluated correctly.
+
+## 0.18.1 (May 11, 2021)
+
+BUG FIXES:
+
+- `imports/json`: Fixed an issue where empty maps where failing when passed at any level to `marshal`.
+
+## 0.18.0 (March 25, 2021)
+
+FEATURES:
+
+- `imports/http`: Added support for POST actions within the `http` import
+ through addition of the `post` function. Additionally, support for adding
+ a body to the `request` object has been added via `with_body`.
+
+## 0.17.4 (February 2, 2021)
+
+BUG FIXES:
+
+- `runtime/format`: Fixed a nesting issue with the rule printer where nesting
+ state leaks were leading to all collection values eventually being truncated,
+ regardless of their nesting level.
+
+## 0.17.3 (January 15, 2021)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue where local file installation failed on Windows
+ when using `sentinel test`.
+
+## 0.17.2 (January 13, 2021)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where duplicate log messages were being printed with
+ `sentinel test`.
+
+## 0.17.1 (January 7, 2021)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where hard-coded path separator caused `sentinel
+ test` issues in Windows.
+
+## 0.17.0 (January 5, 2021)
+
+LANGUAGE CHANGES:
+
+- **Map Expression** `map`. A new quantifier expression, `map` has been added.
+ `map` takes a collection and applies an operation to each element in the
+ collection, returning a list of results with the resulting values.
+- **Emptiness checks** `is empty` and `is not empty`. Two new expressions,
+ `is empty` and `is not empty` have been added. As they are aliases to the
+ `length` built-in, only `string`, `map`, `list` or `undefined` is
+ supported.
+- **Comparable maps** Maps (the data type) are now comparable. Maps are equal if
+ they are of equal length and both their corresponding keys and values are
+ comparable and equal.
+- **Rich Return Types** Rules can now process and return non-boolean data.
+ Scalar, list, and map types are supported. When non-boolean values are used,
+ the presence of a non-zero or non-zero-length `main` rule determines policy
+ failure. More details can be found on
+ https://docs.hashicorp.com/sentinel/language#main.
+
+FEATURES:
+
+- **CLI**: `sentinel apply` and `sentinel test` now have options for JSON
+ output. For more details, see the CLI documentation.
+- `imports/base64`: Added a new import, `base64`, to assist with encoding and
+ decoding base64 strings.
+
+## 0.16.1 (October 21, 2020)
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where `contains` to ensure any instance of `undefined` would correctly result in `undefined`.
+- `runtime/eval`: Fixed an issue in `any` and `all` expressions to ensure correct boolean logic when dealing with a collection containing `undefined`.
+- `imports`: Fixed an issue where import processes were not killed, which caused non-graceful closures for the clients.
+- `lang/scanner`: Fixed an issue where inline `#` style comments were not being consumed.
+
+## 0.16.0 (October 14, 2020)
+
+BREAKING CHANGES:
+
+- `cmd/doc`: Removal of the deprecated `sentinel doc` command.
+- `sentinel`: Replace `whitelist` and `blacklist` with `allowlist` and `denylist`, as per inclusive language changes.
+
+FEATURES:
+
+- `imports/version`: Added a new import, `version`, which supplies helpers for dealing with semantic versioning.
+- `cmd/apply`: Added an `HCL` based configuration file, which will eventually deprecate the legacy `JSON` configuration.
+- `cmd/apply`: Added support for download remote policies and modules.
+- `cmd/test`: Added support for downloading remote test modules.
+- `cmd/apply`: Added support for evaluating a policy based on it's key within the configuration.
+- `cmd/apply`: Added support for running all policies in a configuration by default.
+
+## 0.15.6 (July 7, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.15.5 (May 20, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel integrations. There are no user-facing changes.
+
+## 0.15.4 (May 13, 2020)
+
+BUG FIXES:
+
+- `imports`: Fixed an issue with the standard imports that was affecting the handling of null data within lists.
+
+## 0.15.3 (April 16, 2020)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where `IndexExpr` were not rewritten, as well as ensuring `SelectorExpr` uses any nested rewrites.
+- `lang/printer`: Fixed issue printing deeply nested structures and/or loops.
+
+IMPROVEMENTS:
+
+- `imports/decimal`: Added alias methods to improve consistency of method names within the `decimal` import. The alias methods are `is_nan` for `isnan`, as well as both `is_inf` and `is_infinite` for `isinfinite`.
+- `command/apply`: `sentinel apply` will now output the policy description when a trace is forced via `-trace`.
+- `sentinel`: Improved `String` output formatting of Policy docstring for `EvalPolicyResult`.
+
+## 0.15.2 (April 2, 2020)
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue with the `printer` that was causing a panic
+ when a `#` line comment was used with no text following it.
+- `imports/types`: Fixed an issue with the `types` import where calls to
+ `type_of` for undefined types were incorrectly returning as map types. These
+ will now be correctly be identified as undefined as expected.
+
+## 0.15.1 (March 6, 2020)
+
+BUG FIXES:
+
+- `sentinel`: Fixed how modules are managed internally in the runtime to correct
+ race conditions in concurrent scenarios and to allow for per-evaluation module
+ restrictions. This functionality is only of interest to embedded applications
+ with long-lived runtimes and is currently not implemented in any HashiCorp
+ integration.
+
+## 0.15.0 (March 5, 2020)
+
+NOTES:
+
+- **Windows Digital Signature**. Our windows releases for this version and up will be signed
+ and verified according to Microsoft's requirements.
+
+FEATURES:
+
+- `cmd/config`: Modules can now be loaded off of local disk using the Sentinel
+ CLI. For information on how to do so, see the Sentinel documentation.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: Changed modules so that they now have singleton scope when
+ loaded. Importing a module under two different aliases, or within a nested
+ module, will now share scopes - modification of state in one will alter the
+ other.
+- `lang/object`: Introduced an interface to allow the duplication of various
+ types, like collections.
+- `runtime/eval`: Changed how collection data is returned from modules. This
+ data is now duplicated to prevent accidental or deliberate circumvention of
+ the prohibition on assignment to import data, and brings behavior to parity
+ with binary imports.
+
+## 0.14.4 (February 6, 2020)
+
+IMPROVEMENTS:
+
+- `imports/time`: Changed the `add` timespace function in the `time` import to
+ allow it to take float types as durations. These values will be truncated to
+ the appropriate integer-value duration.
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where out-of-place right-hand braces were being
+ parsed as empty statements, instead of raising syntax errors.
+
+## 0.14.3 (January 22, 2020)
+
+IMPROVEMENTS:
+
+- `cmd/test`: Fail when an assertion is not found in a trace.
+- `cmd/test`: Added a message to notify when no rules were evaluated in a test.
+
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue where import aliases (the `as baz` in `import
+ "foo/bar" as baz`) were being removed when formatting with `sentinel fmt`.
+
+- `imports/http`: Fixed an issue with the http import where the client library's
+ debug logs were being printed to standard error.
+
+## 0.14.2 (January 15, 2020)
+
+NOTES:
+
+With this release, we are discontinuing support for MacOS 32-bit. 64-bit builds
+are now the only builds available for MacOS.
+
+IMPROVEMENTS:
+
+- `lang/printer`: A newline is now added to files formatted via `sentinel fmt`.
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue in printing where multi-line expressions would
+ indent infinitely. They will now only indent once at the most. This is mostly
+ seen when using `sentinel fmt`.
+- `runtime/encoding`: Fixed an issue where if a plugin returned a deeply
+ embedded undefined value, the value was instead decoded into a map.
+
+## 0.14.1 (January 6, 2020)
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where reserved words could not be used as
+ selectors when the selector expression was the last lexeme on a line.
+
+## 0.14.0 (December 18, 2019)
+
+LANGUAGE CHANGES:
+
+- **Filter Expression** `filter`. A new quantifier expression, `filter` has been added.
+ `filter` returns a subset of the provided collection based on a boolean condition that
+ is asserted for each element.
+
+NOTES:
+
+- **Apple Notarization**. Our darwin releases for this version and up will be signed
+ and notarized according to Apple's requirements.
+
+## 0.13.1 (November 25, 2019)
+
+BUG FIXES:
+
+- `runtime/eval`: Parameters are no longer allowed in mock files. Adding one to
+ a mock will result in a runtime error.
+- `runtime/eval`: Fixed an issue where import calls from within mocks were
+ failing under certain circumstances.
+- `imports/decimal`: `int` should now correctly provide the truncated integer
+ representation of a decimal number, not the rounded one.
+
+## 0.13.0 (November 15, 2019)
+
+LANGUAGE CHANGES:
+
+- **Policy Parameters**. The new [policy
+ parameters](https://docs.hashicorp.com/sentinel/language/parameters) feature
+ allows authors to use a `param` declaration at the top of a policy to supply
+ values that are expected to come from outside of a policy. See the linked
+ documentation for more details.
+
+FEATURES:
+
+- `imports/http`: The [`http`
+ import](https://docs.hashicorp.com/sentinel/imports/http) is a new addition
+ to the standard library enabling the use of HTTP-accessible data from outside
+ the runtime in policy rules.
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where loading other imports before a mocked
+ import would cause those imports to no longer be visible from within the
+ policy.
+
+## 0.12.0 (October 7, 2019)
+
+LANGUAGE CHANGES:
+
+- **New `case` statement**. This statement is a selection control mechanism to
+ conditionally execute a branch based on expression equality, allowing
+ simplification of complex conditional chains that may otherwise need to be
+ written with `else if`. See the [Case
+ Statements](https://docs.hashicorp.com/sentinel/language/spec#case-statements)
+ section of the Sentinel language specification for more details.
+
+IMPROVEMENTS:
+
+- `lang/semantic`: Added a semantic check to ensure usage of append is not using
+ a return value.
+
+BUG FIXES:
+
+- `runtime/eval`: Corrected an issue where calling a method on an import object
+ value that was the result of a method call on another import object value
+ would have erroneously tried to call an import of the name of the "parent"
+ import object value. Example: in `a = subject.new(); b = a.call(); c =
+ b.call()`, `b.call()` would attempt to call a method named `call` on the root
+ namespace for an import named `a`. This has now been corrected so that
+ `b.call()` will now correctly call the `call` method for the respective object
+ namespace residing in the import named `subject`.
+- `imports`: Some standard imports may have been returning null for some unknown
+ keys in objects when they should have been returning undefined. This was due
+ to an SDK issue which was corrected in SDK version 0.3.2, which has now been
+ corrected.
+- `cmd/test`: `sentinel test` will now correctly fail a policy if it encounters
+ an error.
+- `cmd/test`: `sentinel test` will now correctly display errors and other output
+ that were missing due to a formatting issue.
+- `runtime/eval`: The `append` builtin now correctly returns undefined for all
+ calls, as called for by the Specification. Note that in most cases, the
+ semantic check outlined above will trigger an error if the return value is
+ used.
+
+## 0.11.0 (September 5, 2019)
+
+LANGUAGE CHANGES:
+
+- **New builtin function:** `range()`. This function existed in the spec in
+ earlier versions but was removed as it lacked an implementation. This has now
+ been implemented and re-added to the spec. See the
+ [Range](https://docs.hashicorp.com/sentinel/language/spec#range) section of
+ the Sentinel Specification for more details.
+- Lists are now comparable. Lists are equal if their corresponding elements are
+ comparable and equal.
+- Method calls on values returned by imports are now supported. See the
+ [Imports](https://docs.hashicorp.com/sentinel/language/spec#imports) section
+ of the Sentinel Specification for more details.
+
+FEATURES:
+
+- `imports/decimal`: This is a new import designed to do exact precision
+ mathematical calculations.
+- `runtime/eval`: Compound call expressions that refer to imports (example:
+ foo.bar().baz() when `foo` is a loaded import) will now function as expected.
+ Previously, this was only supported up to the first call (example:
+ `foo.bar()`).
+- `imports/time`: Timespaces returned by calls such as `time.now` are now
+ callable. Example: `t = time.now; t.after(some_previous_time)` will now
+ function.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: The implementation of comparison of non-comparable types has
+ now changed. Rather than triggering a runtime error, non-comparable types will
+ now return false when attempting to compare.
+
+## 0.10.4 (August 15, 2019)
+
+BUG FIXES:
+
+- `runtime/encoding`: Fixed an issue with conversion of null values that could
+ lead to crashes in imports.
+
+## 0.10.3 (July 15, 2019)
+
+BUG FIXES:
+
+- `command/config`: Parsing a configuration file where malformed data is
+ encountered after apparently properly-formed data will now report an error. An
+ example would be a situation where misplaced braces would cause a JSON object
+ to only parse part of the configuration file.
+
+## 0.10.2 (June 25, 2019)
+
+BUG FIXES:
+
+- `lang/parser`: Corrected an issue where parsing certain compound binary
+ expressions where the negation predicate (`not`) was in use would cause the
+ negation to have no effect. Example: `foo else "bar" not in "baz"` would have
+ been parsed and evaluated as `foo else "bar" in "baz"`, effectively producing
+ the opposite result.
+
+## 0.10.1 (May 9, 2019)
+
+BUG FIXES:
+
+- `command/test`: `sentinel test` now displays policies without
+tests as an unknown result with [no test files], instead of the somewhat
+erroneous behavior of displaying it as a PASS. A full test run with a mixture
+of passing tests and no tests still results in an overall successful result.
+
+## 0.10.0 (April 18, 2019)
+
+LANGUAGE CHANGES:
+
+- Mixed-number arithmetic operations are now allowed. Addition (`+`),
+ subtraction (`-`), multiplication (`*`), division (`/`), and remainder
+ operations (`%`) are no longer restricted to number values of the same
+ type, and can mix integer and floating point. The result of these operations
+ is always a floating-point number.
+- Remainder (modulo, `%`) operations are now allowed on floating-point numbers.
+
+
+BUG FIXES:
+
+- `lang/parser`: Fixed a bug that affected the use of `not` with `contains`,
+ `matches`, or `in` in a compound expression.
+- `runtime/eval`: Fixed error messages on evaluation errors with `contains` and
+ `in` to indicate that strings are an allowed type along with lists and maps.
+
+
+## 0.9.2 (March 15, 2019)
+
+This is a dependency update related to the changes mentioned in 0.9.1. No other
+changes have been made.
+
+## 0.9.1 (March 14, 2019)
+
+This is a patch release that is required to integrate with the latest versions
+of the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). No other
+changes have been made.
+
+## 0.9.0 (January 28, 2019)
+
+FEATURES:
+
+- `imports/strings`: Added a `join` function to the `strings` import. This can
+ be used to join a list into a string with a specific separator.
+ Multi-dimensional lists and all Sentinel primitives are supported.
+
+## 0.8.1 (January 17, 2019)
+
+BUG FIXES:
+
+- `lang/eval`: Fixed a bug that prevents the effective use of `return`
+ statements in `for` loops.
+
+## 0.8.0 (January 14, 2019)
+
+FEATURES:
+
+- Mocks can now be represented by Sentinel code. This allows for the mocking of
+ functions and other complex data structures that cannot be represented in
+ JSON. For more information on using this feature via mocks, click
+ [here](https://docs.hashicorp.com/sentinel/commands/config#mocking-with-sentinel-code).
+
+
+IMPROVEMENTS:
+
+- Import validation has now been moved to the semantic checking phase. This
+ should result in better reporting of validation errors. In addition, import
+ validation will now enforce the use of an `as` identifier when an import path
+ is not a valid identifier on its own (example: `import "foo/bar"`).
+
+## 0.7.0 (December 12, 2018)
+
+BUG FIXES:
+
+- There have been changes to the runtime in how scope is handled over multiple
+ policy executions. Scope is now correctly unique per single policy execution,
+ and values set or builtins that are overridden in one policy will no longer
+ affect those values within another.
+
+## 0.6.0 (November 30, 2018)
+
+FEATURES:
+
+- `imports/runtime`: This new import allows for one to check various aspects of
+ the Sentinel runtime as it may be embedded in the simulator or a specific
+ implementation. For now, it allows the version to be checked.
+
+## 0.5.1 (November 28, 2018)
+
+IMPROVEMENTS:
+
+- `imports/time`: Added the `zone` and `zone_string` attributes to assist with
+ validation of a timespace's zone.
+- `command/fmt`: Added a new -check flag. This option does not commit changes,
+ but instead checks to see what files need formatting and outputs them on
+ stdout.
+
+BUG FIXES:
+
+- `command/test`: Ensure that passing test results are correctly output one per
+ line. Tests are also now run in a deterministic fashion based on
+ lexicographical (alphabetical) order.
+- `imports/time`: `month_name` and `weekday_name` will now show up correctly in
+ a returned timespace result.
+
+
+## 0.5.0 (November 5, 2018)
+
+IMPROVEMENTS:
+
+- `spec`: Selectors can now contain any reserved word (example: `rule`) or
+ keyword operator (example: `any`, `all`, `is`, `not`). This only works for the
+ _selector_ part of the expression (after the first period) - the first primary
+ expression (before the first period) still needs to be an identifier that does
+ not conflict with reserved words.
+
+BUG FIXES:
+
+- The simulator should now display import function call names correctly in
+ import errors.
+
+## 0.4.0 (October 1, 2018)
+
+FEATURES:
+
+- `builtin`: Added the `bool` built-in type conversion function. Booleans will
+ also now accepted as conversion into other values as well, with the full list
+ of behaviors available in the spec.
+
+## 0.3.2 (September 27, 2018)
+
+FEATURES:
+
+- `command/apply`: `sentinel apply` now prints out messages output by the
+ `print()` function when a trace is output on policy failure, or when a trace
+ is forced with `-trace`.
+- `imports/time`: Added the `month_name` and `weekday_name` keys to the
+ timespace, which return full-English names for the month and day of the week.
+
+
+BUG FIXES:
+
+- `command/fmt`: `sentinel fmt -` Will no longer print out the filter status
+ message on the output stream when `-write=false` Is not explicitly stated.
+ This brings the behavior of the command in line with the help text.
+- `runtime`: Index operations on the right-hand-side that have negative indexes
+ that go out of range (example: `length(list) * -1 - 1`) now correctly return
+ `undefined`. left-hand-side index assignments with a out-of-range negative
+ index still return runtime errors.
+
+## 0.3.1 (August 3, 2018)
+
+BUG FIXES:
+
+- `runtime`: Basic index assignment has been implemented as per the spec.
+- `runtime`: Index expressions for lists with negative indexes will no longer panic if the
+ list index is less than `length(list) * -1`.
+
+## 0.3.0 (July 20, 2018)
+
+FEATURES:
+
+- **New standard import: `types`.** This can be used to dynamicaly detect the
+ type of some value.
+
+## 0.2.0 (April 11, 2018)
+
+FEATURES:
+
+- **New standard import: `json`.** Marshal and unmarshal JSON documents and
+ access their contents as native Sentinel values.
+- **`break` and `continue`.** These are now both specified and implemented.
+ `break` allows loop exiting and `continue` allows immediate execution of the
+ next iteration.
+
+IMPROVEMENTS:
+
+- runtime: `print()` map values are now ordered alphabetically by keys.
+
+BUG FIXES:
+
+- command/test: If no `test` block exists, test behaves like it is asserting
+ `main: true`.
+- runtime: default maximum stack depth to 500
+- runtime: `print()` map values now appear like more typical maps.
+- runtime: division by zero is an error, not a crash
+- runtime: plugins that send map values with `null` values now decode properly
+ into native Sentinel values.
+
+## 0.1.0 (September 19, 2017)
+
+Initial release.
+
+
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/commands/apply.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/commands/apply.mdx
new file mode 100644
index 0000000000..74b74d41fa
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/commands/apply.mdx
@@ -0,0 +1,66 @@
+---
+page_title: 'Command: apply'
+sidebar_current: docs-commands-apply
+description: >-
+ The `sentinel apply` command is used to execute a policy locally for
+ development purposes.
+layout: docs
+---
+
+# Command: `apply`
+
+The `sentinel apply` command is used to execute a policy locally for development
+purposes.
+
+## Usage
+
+Usage: `sentinel apply [options] POLICY`
+
+This command executes the policy file at the path specified by POLICY. In
+addition to a path to a policy, POLICY can reference a label of a
+[policy](/sentinel/configuration#policies) entry in the configuration.
+
+Use the exit code of this command to determine the exact status of the policy
+evaluation. `0` is pass, `1` is fail, `2` is undefined (fail, but because the
+result was undefined), and `3` is a runtime error. Errors unrelated to the
+policy status itself are returned with an exit status of `9`.
+
+To control the behavior of the `apply` command, create a [configuration
+file](/sentinel/configuration). With this, you can define available
+[import plugins](/sentinel/concepts/imports), mock data, and global values.
+This can help you simulate a policy embedded within an application.
+
+As of the 0.16 release, POLICY is optional. When it is not supplied,
+`sentinel apply` will run **all** policies in the supplied configuration.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-config=path` - Path to a configuration file specifying available imports,
+ mock data, globals, etc. The default is `sentinel.[hcl|json]`.
+
+- `-json` Enable JSON output. Using this mode, all other output will be
+ suppressed and the full detail of the apply will be output in a
+ machine-readable format. Enabling this implies `-trace`. See the section on
+ [tracing JSON output](/sentinel/writing/tracing#json-output) for more details.
+
+- `-json-rule=RULE` Enable JSON output, outputting only the value of the
+ specified rule. When running against a policy set, the policy must be supplied
+ to enable per-rule filtering. Implies `-json`.
+
+- `-global key=value` - Set global values. This is the same as setting `global`
+ in the configuration file, and will override any of these respective values
+ set in the configuration. The value is either a string, or a JSON number,
+ array, or object. To force strings, use quotes.
+
+- `-param key=value` - Set parameters, the same as setting `param` in the
+ configuration file. Values are handled in the same way they are with the
+ `-global` flag.
+
+- `-timeout` - Allows users to specify a timeout after which the apply command will stop running.
+ There is no timeout if not provided. The timeout needs to be specified as a Duration type, eg `100ms, 5s etc`
+
+- `-trace` - Always show the execution trace. This shows intermediate
+ boolean expression values. This always shows for failed policies.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/commands/fmt.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/commands/fmt.mdx
new file mode 100644
index 0000000000..8297316d8b
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/commands/fmt.mdx
@@ -0,0 +1,33 @@
+---
+page_title: 'Command: fmt'
+sidebar_current: docs-commands-fmt
+description: The `sentinel fmt` command formats a policy source to a canonical format.
+layout: docs
+---
+
+# Command: `fmt`
+
+The `sentinel fmt` command formats a policy source to a canonical format.
+
+## Usage
+
+Usage: `sentinel fmt [options] FILE ...`
+
+This command formats all the specified policy files to a canonical format.
+
+By default, policy files are overwritten in place. This behavior can be
+changed with the `-write` flag. If a specified FILE is `-` then stdin is
+read and the output is always written to stdout.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-write=true` - Write formatted policy to the named source file. If false,
+ output will go to stdout. If multiple files are specified, the output will
+ be concatenated directly.
+
+- `-check=false` - Don't format, only check if formatting is necessary. Files
+ that require formatting are printed, and a non-zero exit code is returned if
+ changes are required.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/commands/index.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/commands/index.mdx
new file mode 100644
index 0000000000..7177093cec
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/commands/index.mdx
@@ -0,0 +1,23 @@
+---
+page_title: Commands (CLI)
+sidebar_current: docs-commands
+description: >-
+ Sentinel provides a `sentinel` command-line interface (CLI) for developing and
+ testing policies.
+layout: docs
+---
+
+# Sentinel CLI Commands
+
+The Sentinel command-line interface (CLI) allows for the developing and testing
+of policies outside of a particular Sentinel implementation. Having a standard
+workflow to develop policies is critical for our mission of [policy as
+code](/sentinel/concepts/policy-as-code).
+
+The CLI takes a subcommand to execute. The complete list of subcommands is in
+the navigation to the left.
+
+The Sentinel CLI is a well-behaved command line application. In erroneous cases,
+a non-zero exit status will be returned. It also responds to -h and --help as
+you'd expect. To view a list of the available commands at any time, just run
+`sentinel` with no arguments.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/commands/test.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/commands/test.mdx
new file mode 100644
index 0000000000..ed50aacc22
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/commands/test.mdx
@@ -0,0 +1,53 @@
+---
+page_title: 'Command: test'
+sidebar_current: docs-commands-test
+description: The `sentinel test` command runs policies against the Sentinel test framework.
+layout: docs
+---
+
+# Command: `test`
+
+The `sentinel test` command runs policies against the Sentinel test framework.
+
+To learn more about testing, see the [testing
+policies](/sentinel/writing/testing) page.
+
+## Usage
+
+Usage: `sentinel test [options] [PATH] ...`
+
+This command runs the defined test cases against a set of policies.
+
+The test cases are expected to live at the path `test//*.[hcl|json]` relative
+to the policy being tested. `` should be the name of the policy
+excluding the file extension. The files should be in the [configuration
+file structure](/sentinel/configuration). The `test` key is used for
+assertions. Test cases ignore the root level configuration file and must have
+all required configuration provided in each test case.
+
+The PATH arguments specified may be a list of zero or more files or directories.
+When no arguments are given, it defaults to the current directory. When a
+directory is given, all policies ending with the extension `.sentinel` are
+tested.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-json` - Suppress normal output and output test results as a JSON document.
+ See the [testing JSON output](/sentinel/writing/testing#test-json-output)
+ section for more details.
+
+- `-run=regexp` - Run only tests matching the regular expression pattern.
+ A `/` splits policy name and test case name.
+
+- `-verbose` - Show tracing and log output for tests that succeed in addition
+ to tests that fail. By default, traces and logs are only shown for tests that
+ fail.
+
+- `-maxConcurrency` - Allows users to specify the number of tests to be run concurrently.
+ Defaults to the number of logical CPUs if not provided. To run tests sequentially, use `-maxConcurrency=1`
+
+- `-timeout` - Allows users to specify a timeout after which the test command will stop running.
+Defaults to 5 minutes if not provided. The timeout needs to be specified as a Duration type, eg `100ms, 5s etc`
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/concepts/enforcement-levels.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/concepts/enforcement-levels.mdx
new file mode 100644
index 0000000000..fc4b8062d0
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/concepts/enforcement-levels.mdx
@@ -0,0 +1,43 @@
+---
+page_title: Enforcement Levels
+sidebar_current: docs-concepts-levels
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Enforcement Levels
+
+Enforcement levels are a first class concept in Sentinel allowing pass/fail
+behavior to be associated separately from the policy logic. This enables
+any policy to be a warning, allow overrides, or be absolutely mandatory.
+Because this level is not part of the policy body itself, different uses of
+the same policy can have different enforcement levels.
+
+Sentinel has three enforcement levels:
+
+- **Advisory:** The policy is allowed to fail. However, a warning should be
+ shown to the user or logged. Advisory is the default enforcement level.
+
+- **Soft Mandatory:** The policy must pass unless an override is specified.
+ The semantics of "override" are specific to each Sentinel-enabled application.
+ The purpose of this level is to provide a level of privilege separation
+ for a behavior. Additionally, the override provides non-repudiation since
+ at least the primary actor was explicitly overriding a failed policy.
+
+- **Hard Mandatory:** The policy must pass no matter what. The only way to
+ override a hard mandatory policy is to explicitly remove the policy.
+ It should be used in situations where an override is not possible.
+
+## Configuring Enforcement Levels
+
+Enforcement levels are configured when a policy is deployed to a
+Sentinel-enabled application. The exact mechanism that the level is specified
+is determined by each application. Please reference the documentation for
+your Sentinel-enabled application for more information.
+
+Enforcement levels are not configured and are not known by the policy body
+itself. All policies should be written to describe exactly the behavior
+they're attempting to control. For example, a policy that restricts deploys
+to business hours should be written exactly like so. When that policy is
+configured on an application, the operator may specify that it is advisory,
+soft, or hard mandatory.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/concepts/imports.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/concepts/imports.mdx
new file mode 100644
index 0000000000..1306e1512e
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/concepts/imports.mdx
@@ -0,0 +1,32 @@
+---
+page_title: Imports
+sidebar_current: docs-concepts-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom imports](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only
+local context for making policy decisions.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that the
+import calendar allows access to engineer calendars. This import only exists
+for the purpose of this example and at the time of writing is not a real
+available import.
+
+```json sentinel
+{
+ "policy": "import \"calendar\"\n// Get the calendar for Bob for today\nbob_calendar = calendar.of(\"bob\").today\n\n// Allow this policy to pass if Bob is not on vacation.\nmain = rule { not bob_calendar.has_event(\"vacation\") }",
+ "mocks": {
+ "calendar": "has_event = func(event) {\n\treturn true\n}\nof = func(name) {\n\treturn { \"today\": { \"has_event\": has_event } }\n}"
+ }
+}
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/concepts/index.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/concepts/index.mdx
new file mode 100644
index 0000000000..ce7ac51454
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/concepts/index.mdx
@@ -0,0 +1,15 @@
+---
+page_title: Basic Concepts
+sidebar_current: docs-concepts
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Basic Concepts
+
+This section covers some high level basic concepts that are important
+to understand for day to day Sentinel usage. Every page in
+this section is recommended reading for anyone consuming
+or extending Sentinel.
+
+Please use the navigation to the left to learn more about a topic.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/concepts/language.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/concepts/language.mdx
new file mode 100644
index 0000000000..ce91f3eb8f
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/concepts/language.mdx
@@ -0,0 +1,91 @@
+---
+page_title: Policy Language
+sidebar_current: docs-concepts-language
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Policy Language
+
+Sentinel defines and uses its own [policy language](/sentinel/language).
+
+The language was designed to be approachable by non-programmers, since
+there are many use cases where the individual defining policy may not
+be a developer. However, the language includes constructs that
+are familiar to developers to enable powerful policies.
+
+To learn more about the language, please see the
+[writing policy section](/sentinel/writing)
+or the [language reference](/sentinel/language).
+
+An example of the policy language is shown below:
+
+```sentinel playground
+import "time"
+
+# Validate time is between 8 AM and 4 PM
+valid_time = rule { time.now.hour >= 8 and time.now.hour < 16 }
+
+# Validate day is M - Th
+valid_day = rule {
+ time.now.weekday_name in ["Monday", "Tuesday", "Wednesday", "Thursday"]
+}
+
+main = rule { valid_time and valid_day }
+```
+
+## Why?
+
+To explain why Sentinel defines and uses its own language, the question
+can be more easily split into roughly two types of languages: _configuration_ languages
+and _programming_ languages.
+
+### Configuration Languages
+
+Configuration languages are formats such as JSON, YAML, XML, etc. Many applications
+use configuration languages as their ACL format. Configuration languages
+are good for static, declarative information. ACL systems are a good fit
+for this. ACL systems are focused, providing the limiting options necessary
+to restrict access to a system.
+
+Configuration languages are not good for dynamic or logical rules. They
+typically only contain limited conditions, loops, or functions. Further
+configuration is often declarative, which can introduce complexities to
+logical statements that depend on ordering.
+
+The use cases that Sentinel was built for require the ability to perform
+complex behavior that didn't model well into a configuration format or a
+declarative form.
+
+### Programming Languages
+
+The Sentinel language was designed with the following goals:
+
+- **Non-programmer friendly.** Sentinel was built to be used by non-programmers.
+ For the use cases we discovered, non-programmers needed the ability to
+ enforce certain rules within a system. For example, a person responsible for
+ compliance may need to insert rules into a system.
+
+- **Programmer friendly.** At the same time, the language needed to support
+ programmer-friendly constructs such as conditionals, loops, and functions
+ for complex policies that a programmer may be writing.
+
+- **Embeddable.** Sentinel is embedded in existing software. The language
+ itself needs to be easily embeddedable. Further, Sentinel was designed
+ to be embedded in [HashiCorp](https://www.hashicorp.com) software
+ written in Go, so it needed to be easily embeddable in Go.
+
+- **Safe.** The language is used in highly security-sensitive environments.
+ It must not be able to crash its host system or access system resources
+ without explicit approval.
+
+Prior to building our own language, Sentinel evaluated other languages.
+Some languages worked quite well. As we continued to develop Sentinel, we
+found that the flexibility and control over designing our own language
+outweighed the costs.
+
+Because the language itself doesn't need to be general purpose, building
+a language focused on policy proved to be the best route for Sentinel.
+It allows us to build powerful tracing capabilities for debugging, make
+interesting performance choices, and extend the language as needed in the
+future.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/concepts/policy-as-code.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/concepts/policy-as-code.mdx
new file mode 100644
index 0000000000..6dac593e18
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/concepts/policy-as-code.mdx
@@ -0,0 +1,72 @@
+---
+page_title: Policy as Code
+sidebar_current: docs-concepts-pac
+description: >-
+ Policy as code is the idea of writing code in a high-level language to manage
+ and automate policies. By representing policies as code in text files, proven
+ software development best practices can be adopted such as version control,
+ automated testing, and automated deployment.
+layout: docs
+---
+
+# Policy as Code
+
+Policy as code is the idea of writing code in a high-level language to
+manage and automate policies. By representing policies as code in text files,
+proven software development best practices can be adopted such as version
+control, automated testing, and automated deployment.
+
+Many existing policy or ACL systems do not practice policy as code. Many
+policies are set by clicking in a GUI, which isn't easily repeatable nor
+versionable. They usually don't provide any system for testing policies
+other than testing an action that would violate the policy. This makes it
+difficult for automated testing. And the policy language itself varies by
+product.
+
+Sentinel is built around the idea and provides all the benefits of policy as code.
+
+## Benefits
+
+Policy as code provides a number of benefits:
+
+- **Sandboxing.** Policies provide the guardrails for other automated systems.
+ As the number of automated systems grow, there is also a growing need to
+ protect those automated systems from performing dangerous actions. Manual
+ verification is too slow; policies need to be represented as code to
+ keep up with other automated systems.
+
+- **Codification.** By representing policy logic as code, the information
+ and logic about a policy is directly represented in code and can be augmented
+ with comments rather than relying on oral tradition to learn about the
+ reason for policies.
+
+- **Version Control.** Policies are encouraged to be stored as simple text
+ files managed by a version control system. This lets you gain all the
+ benefits of a modern VCS such as history, diffs, pull requests, and more.
+
+- **Testing.** Policies are just code. Their syntax and behavior can be
+ [easily validated with Sentinel](/sentinel/commands/test). This also encourages
+ automated testing such as through a CI. Paired with a VCS system, this
+ allows a pull request workflow to verify that a policy keeps the system
+ behavior as expected before merging.
+
+- **Automation.** With all policies as code in simple text files, various
+ automation tools can be used. For example, it is trivial to create tools
+ to automatically deploy the policies into a system.
+
+## Sentinel and Policy as Code
+
+Sentinel fully embraces policy as code in a number of ways:
+
+- **Language.** All Sentinel policies are written using the
+ [Sentinel language](/sentinel/concepts/language). This language is
+ made to be inputted directly to text files. As an additional benefit,
+ all Sentinel-enabled applications share the same policy language.
+
+- **Development.** Sentinel provides a [CLI](/sentinel/commands)
+ for development and testing. This local CLI can be used to verify policies
+ before deploying them to a system.
+
+- **Testing.** Sentinel provides a [test framework](/sentinel/commands/test)
+ designed specifically for automation. This allows developers and CI systems
+ to further verify policies.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/configuration/index.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/configuration/index.mdx
new file mode 100644
index 0000000000..c7d4a8e3bd
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/configuration/index.mdx
@@ -0,0 +1,422 @@
+---
+page_title: Sentinel CLI Configuration File Syntax
+sidebar_current: docs-configuration
+description: >-
+ The Sentinel CLI's configuration file can be used to control the
+ behavior of the simulator during apply and test operations.
+layout: docs
+---
+
+# Sentinel CLI Configuration File Syntax
+
+The Sentinel CLI's configuration file can be used to control the behavior
+of the simulator during [`apply`](/sentinel/commands/apply) and
+[`test`](/sentinel/commands/test) operations.
+
+## Usage
+
+The configuration file is used in different ways depending on what operation you
+are trying to execute.
+
+### Apply
+
+When using `sentinel apply`, configuration files that have the `.hcl`
+extension are loaded into a single configuration. This allows for configuration
+to be split across multiple files, which is useful for large policy sets. To
+supply a single configuration file, the `-config=FILE` flag can be used, where
+`FILE` is the path to the configuration file. This argument also allows for a
+`.json` configuration file to be supplied. The default is `sentinel.[hcl|json]`.
+
+See the [apply command](/sentinel/commands/apply) reference for more details.
+
+### Test
+
+When using `sentinel test`, each file matching `test//*.[hcl|json]` is a
+configuration file representing a single test case, where `` is the name
+of the policy being tested. Configure assertions for each [test
+case](#test-cases) using the test section.
+
+See the [test command](/sentinel/commands/test) reference for more details.
+
+#### Remote sources
+
+When declaring a `policy` or `module` block within the configuration, a
+`source` attribute must be supplied. This `source` should declare the location
+of the resource, which can be either a local file or a URL pointing to a remote
+file. Examples of local `source` attributes are detailed both in the
+[Policies](#policies) and [Modules](#modules) sections of this page. Below
+are a few examples of remote `source` attributes.
+
+Example:
+
+```hcl
+policy "foo" {
+ source = "git::https://github.com/hashicorp/sentinel-example.git//main.sentinel"
+ enforcement_level = "hard-mandatory"
+}
+```
+
+The above example will fetch the policy from the provided URL and place it into
+the cache ready for use. Be sure to read the
+[Remote Sources](/sentinel/configuration/remote-sources) page for an
+in-depth overview.
+
+## Configuration File Reference
+
+The format of the configuration file is either JSON or HCL. The available
+blocks are:
+
+- [`mock`](#mock-imports) - A mock import specification.
+- [`policy`](#policies) - Configuration for a [policy](/sentinel/writing).
+- [`import`](#imports) - Configuration for a [module](/sentinel/extending/modules), [standard import](/sentinel/imports), [plugin](/sentinel/extending/plugins) or
+ [static import](/sentinel/extending/static-imports).
+- [`global`](#globals) - Data that is inserted into the global scope.
+- [`param`](#parameters) - Values for [parameters](/sentinel/language/parameters) defined in the policy.
+- [`test`](#test-cases) - Test cases for the [test command](/sentinel/commands/test).
+- [`sentinel`](#sentinel) - Configuration to manage the Sentinel runtime
+
+### Mock Imports
+
+Mock imports allow running a policy with an
+[import](/sentinel/concepts/imports) that you may not have access to or is
+too difficult to run. For example, it can be easier to mock data for [Terraform
+Enterprise](https://www.terraform.io/docs/enterprise/sentinel/index.html)
+locally instead of having to run a workspace through a plan/policy check cycle.
+
+Mock imports are specified using the `mock` block, labelled by the import
+name that you want to mock. For example, if you wanted to mock the `time`
+import, you could create an entry with the `time` label pointing to the data
+you want to mock.
+
+The data can take one of two forms:
+
+#### Mocking static data
+
+Static data can be mocked directly via HCl using the `data` attribute on the
+`mock` block.
+
+Example:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ hour = 9
+ minute = 42
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"time\"\n\nmain = rule { time.now.hour is 9 }",
+ "mocks": {
+ "time": "now = { \"hour\": 9, \"minute\": 42 }"
+ }
+}
+```
+
+#### Mocking with Sentinel code
+
+There are some Sentinel types that raw data cannot mock, such as functions,
+and maps that don't have string keys. To mock this data, you can use
+a Sentinel file itself. In this case, you can set the `module` attribute to
+a file with the Sentinel code in it, relative to the current working directory in
+the event of `apply`, or relative to the configuration file location in the
+event of `test`.
+
+Note that `main` is not required in a mock data file.
+
+Example:
+
+```hcl
+mock "foo" {
+ module {
+ source = "mock-foo.sentinel"
+ }
+}
+```
+
+`mock-foo.sentinel` would contain:
+
+```sentinel
+bar = func() {
+ return "baz"
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"foo\"\n\nmain = rule { foo.bar() is \"baz\" }",
+ "mocks": {
+ "foo": "bar = func() { return \"baz\" }"
+ }
+}
+```
+
+### Policies
+
+The `policy` block allows for the the configuration of policies to assist
+with integrating into other commands.
+
+Each `policy` block has a label to identify the policy, with the block
+providing configuration of the policy. The available configuration options for
+policy are `source`, `enforcement_level` and an optional `params` attribute. The
+`source` key provides the location of the policy, while `enforcement_level` is
+currently used by integrations such as [HCP Terraform](/terraform/cloud-docs/policy-enforcement/manage-policy-sets#enforcement-levels).
+For more information on the `source` value, see [Policy and Module Sources](#policy-and-module-sources).
+
+The optional `params` attribute is used to provide values to [parameters](/sentinel/language/parameters)
+defined within the policy file.
+
+```hcl
+policy "foo" {
+ source = "foo.sentinel"
+ enforcement_level = "hard-mandatory"
+ params = {
+ "name" = "Sample"
+ }
+}
+```
+
+If `enforcement_level` is not supplied, it will fallback to the `advisory` level.
+
+### Imports
+
+Import configuration allows you to configure [modules](/sentinel/extending/modules),
+[standard imports](/sentinel/imports) and [import plugins](/sentinel/extending/plugins).
+
+The `import` is a multi-label block, with the first label determining the kind of import
+being configured. Currently the supported values for this label are:
+
+* [`"module"`](#import-modules) for configuring import modules
+* [`"plugin"`](#import-plugins) for configuring standard imports and import plugins
+* [`"static"`](#static-imports) for configuring static data files as imports
+
+#### Import Modules
+
+The `import "module"` block allows you to map a Sentinel import to a
+[module](/sentinel/extending/modules). The Sentinel CLI will parse the module
+source and make it available for evaluation with the policy.
+
+Each block has a label aligning to the name of the import, with
+the block set to the configuration object for the module. Currently, the only
+value within the configuration object is `source`, which points to the
+location of the module. For more information on the `source` value, see
+[Policy and Module Sources](#policy-and-module-sources).
+
+```hcl
+import "module" "foo" {
+ source = "modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "modules/bar.sentinel"
+}
+```
+
+Note when using [`sentinel test`](/sentinel/commands/test), module paths must be
+specified relative to the location of the configuration file.
+
+```hcl
+import "module" "foo" {
+ source = "../../modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "../../modules/bar/sentinel"
+}
+```
+
+See [Writing and Using a
+Module](/sentinel/extending/modules#writing-and-using-a-module) for more details
+on using a module with this configuration.
+
+### Import Plugins
+
+Import `"plugin"` configuration allows you to configure both [standard imports](/sentinel/imports)
+as well as [import plugins](/sentinel/extending/plugins) that can be used within
+a policy. If defining a custom import plugin, the Sentinel CLI will launch the
+plugin, connect to it, configure it, and execute it as needed by the policy.
+
+The available configuration attributes for an `import "plugin"` are:
+
+- `source` (string, required) - Path to the import plugin executable.
+- `args` (list of string, optional) - A list of arguments to pass to the
+ executable when starting it.
+- `env` (map of string to string, optional) - A set of environmental variables
+ to set when launching the plugin.
+- `config` (map, optional) - Configuration for the plugin itself. This is
+ specific to each individual plugin. Please reference the documentation for
+ the plugin for more information.
+
+Standard import example:
+
+```hcl
+import "plugin" "time" {
+ config = { "timezone": "Australia/Brisbane" }
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"time\"\n\nmain = time.now.zone_string is \"+10:00\"",
+ "mocks": {
+ "time": "now = { \"zone_string\": \"+10:00\" }"
+ }
+}
+```
+
+Custom plugin example:
+
+```hcl
+# flipper receives a boolean and returns the opposite
+import "plugin" "flipper" {
+ source = "/path/to/flipper"
+}
+```
+
+```json sentinel
+{
+ "policy": "import \"flipper\"\n\nmain = flipper.flip(false)",
+ "mocks": {
+ "flipper": "flip = func(input) { return not input }"
+ }
+}
+```
+
+#### Static Imports
+
+Static import configuration allows you to supply a data file and make
+it available to a policy via an import statement.
+
+The available configuration attributes for an `import "static"` are:
+
+- `source` (string, required) - Path to the static data file.
+- `format` (string, required) - The format of the static data. Currently, only
+ the "json" value is supported.
+
+Static import example:
+
+```hcl
+import "static" "people" {
+ source = "./data/people.json"
+ format = "json"
+}
+```
+
+```json sentinel
+{
+ "policy": "import \"people\"\n\nmain = length(people.names) > 0",
+ "mocks": {
+ "people": "names = [\"John Smith\", \"Jane Smith\"]"
+ }
+}
+```
+
+### Globals
+
+Global data is injected directly into the global scope of the policy.
+This can be used to simulate global data that may be injected by a
+Sentinel-enabled application.
+
+Global data is specified by the `global` key. The value is a map of
+variables to inject directly into the running policy.
+
+Example:
+
+```hcl
+global "time" {
+ value = {
+ now = {
+ day = 31
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass. Notice
+that we don't have to do any `import`. The values of `global` are
+injected directly into the global scope.
+
+```json sentinel
+{
+ "policy": "main = time.now.day == 31",
+ "globals": {
+ "time": {
+ "now": {
+ "day": 31
+ }
+ }
+ }
+}
+```
+
+### Parameters
+
+The `param` section allows you to supply values for the
+[parameters](/sentinel/language/parameters) found in a policy. Values
+entered here will satisfy required parameters in a policy, in addition to
+override any defaults. Note that any of the manual parameter value methods
+supported by `sentinel apply` will override the values set here.
+
+```hcl
+param "foo" {
+ value = "bar"
+}
+```
+
+### Test Cases
+
+Test cases specify the test cases for the [test command](/sentinel/commands/test).
+
+Tests are specified by the `test` key. The value of this is a map of
+string to boolean. The key is the name of a rule and the value is the
+expected value of that rule. If a rule is not specified, than any value is
+allowed.
+
+Example:
+
+```hcl
+test {
+ rules = {
+ main = true
+ days = []
+ }
+}
+```
+
+For the policy:
+
+```json sentinel
+{
+ "policy": "valid_days = rule {\n\tfilter days as d {\n\td is \"monday\"\n\t}\n}\n\nmain = rule { valid_days is empty }",
+ "globals": {
+ "days": []
+ }
+}
+```
+
+For more information, read about the [test command](/sentinel/commands/test).
+
+### Sentinel
+
+The `sentinel` block provides configuration specific to the Sentinel runtime.
+
+An example of how the `sentinel` block can be used to enable the [terraform](/sentinel/features/terraform)
+feature is as follows:
+
+```hcl
+sentinel {
+ features = {
+ terraform = true
+ }
+}
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/configuration/overrides.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/configuration/overrides.mdx
new file mode 100644
index 0000000000..ca1a6842b1
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/configuration/overrides.mdx
@@ -0,0 +1,80 @@
+---
+page_title: Override Files
+sidebar_current: docs-configuration-overrides
+description: >-
+ The Sentinel CLI's configuration can be overriden using override files.
+ Override files merge additional settings into existing configuration.
+layout: docs
+---
+
+# Override Files
+
+As outlined in the configuration [overview](/sentinel/configuration#Apply),
+the normal behavior of Sentinel is to load all `.hcl` files into a single
+configuration object.
+
+In addition to appending multiple configuration files, Sentinel allows for the
+rare case of overriding configuration through override files. Override files
+are any files that match either `_override.{hcl,json}` or `override.{hcl,json}`.
+
+Sentinel will parse override files after it has loaded the primary
+configuration, and then process each override file in turn (in lexicographical
+order). All top level blocks other than [test](/sentinel/configuration#test-cases)
+require the block to already be defined in the primary configuration. It will
+then attempt to merge the override block into the existing configuration.
+
+## Example
+
+If you had a Sentinel configuration `sentinel.hcl` that contained the following:
+
+```hcl
+policy "main" {
+ source = "./main.sentinel"
+ enforcement_level = "advisory"
+}
+```
+
+And you created a file `override.hcl` that contained the following:
+
+```hcl
+policy "main" {
+ enforcement_level = "soft-mandatory"
+}
+```
+
+Sentinel will merge the contents of the override into the former, providing the
+following configuration:
+
+```hcl
+policy "main" {
+ source = "./main.sentinel"
+ enforcement_level = "soft-mandatory"
+}
+```
+
+## Merging Behavior
+
+The general rule, which applies in most cases, is:
+
+- A top-level block in an override file merges with a block in a normal
+ configuration file that has the same block header. The block header is the
+ block type and any quoted labels that follow it.
+- Within a top-level block, an attribute argument within an override block
+ replaces any argument of the same name in the original block.
+- Within a top-level block, any nested blocks within an override block replace
+ all blocks of the same type in the original block. Any block types that do not
+ appear in the override block remain from the original block.
+- The contents of nested configuration blocks are not merged.
+- The resulting merged block must still comply with any validation rules that
+ apply to the given block type.
+
+If more than one override file defines the same top-level block, the overriding
+effect is compounded, with later blocks taking precedence over earlier blocks.
+Overrides are processed in order first by filename (in lexicographical order)
+and then by position in each file.
+
+## Required Attributes
+
+It is important to note that all attributes inside an override file are
+considered to be optional, meaning that an override file can itself fail
+validation rules for block types.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/configuration/remote-sources.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/configuration/remote-sources.mdx
new file mode 100644
index 0000000000..b64e4d3abc
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/configuration/remote-sources.mdx
@@ -0,0 +1,165 @@
+---
+page_title: Remote Sources
+sidebar_current: docs-configuration-remote-sources
+description: >-
+ There are a number of options for formatting the URL provided to the
+ source attribute on policy and module blocks.
+layout: docs
+---
+
+# Remote Sources
+
+There are a number of options for formatting the URL provided to the
+`source` attribute on `policy` and `import "module"` blocks. This flexibility
+should solve most use cases and allow for reusable policies and modules.
+
+### Supported Protocols
+
+-> **NOTE:** All protocols are supported on the CLI, however each production
+Sentinel integration may not. Be sure to read the appropriate integration
+documentation to check the supported protocols.
+
+- Local filesystem
+- Git
+- Mercurial
+- HTTP
+- Amazon S3
+- Google GCP
+
+### Forced Protocols
+
+Due to the ambiguous nature of URLs, it is possible to force a particular
+protocol to be used during the fetch. To achieve this, simply prefix the url
+with the appropriate protocol as listed below:
+
+- `file` - Local filesystem
+- `git` - Git
+- `hg` - Mercurial
+- `http` or `https` - HTTP
+- `s3` - Amazon S3
+- `gcp` - Google GCP
+
+Example:
+
+```
+git::http://github.com/hashicorp/sentinel.git
+```
+
+The above would download the provided HTTP URL using the Git protocol.
+
+### Protocol Options
+
+In addition to some global options, there are options available to specific
+protocols to perform features such as authentication.
+
+#### Subdirectories / File Pathing
+
+When supplying URLs that point to a directory (eg. `git`), a subdirectory and
+file can be provided by suffixing the URL with `//` and the path. For example,
+if we have a git repository that has a policy, `main.sentinel` in the root, we
+could directly fetch the file by using the following:
+
+```
+git::https://github.com/hashicorp/sentinel-docs-example.git//main.sentinel
+```
+
+Or, if the file was nested in the `policies` folder:
+
+```
+git::https://github.com/hashicorp/sentinel-docs-example.git//policies/main.sentinel
+```
+
+#### Checksumming
+
+For downloads of any protocol, you can automatically verify a checksum. To
+checksum a file, append a `checksum` query parameter to the URL. The parameter
+value can be in the format of type:value or just value, where type is "md5",
+"sha1", "sha256", "sha512" or "file" . The "value" should be the actual
+checksum value or download URL for "file". When type part is omitted, type will
+be guessed based on the length of the checksum string.
+
+```
+./foo.sentinel?checksum=md5:b7d96c89d09d9e204f5fedc4d5d55b21
+
+./foo.sentinel?checksum=b7d96c89d09d9e204f5fedc4d5d55b21
+
+./foo.sentinel?checksum=file:./foo.txt.sha256sum
+```
+
+#### Unarchiving
+
+An `archive` query parameter can be supplied to explicitly state what format
+to attempt to extract, if required. The supported formats are:
+
+- `tar.gz` and `tgz`
+- `tar.bz2` and `tbz2`
+- `tar.xz` and `txz`
+- `zip`
+- `gz`
+- `bz2`
+- `xz`
+
+If a URL has an extension that matches one of the supported formats, it will
+use that format to unarchive.
+
+```
+./file.zip
+```
+
+The above would use the `zip` format to unarchive.
+
+```
+./path/to/file?archive=zip
+```
+
+Would force the `zip` format. If for some reason you required archiving to be
+disabled, this can also be achieved by using the `false` value.
+
+```
+./path/to/file?archive=false
+```
+
+#### Git (`git`)
+
+- `ref` - The Git ref to checkout. This is a ref, so it can point to a commit
+ SHA, a branch name, etc. If it is a named ref such as a branch name, it will
+ be updated to the latest on each get.
+- `sshkey` - An SSH private key to use during clones. The provided key must be
+ a base64-encoded string. For example, to generate a suitable sshkey from a
+ private key file on disk, you would run base64 -w0 \.
+ **Note:** Git 2.3+ is required to use this feature.
+- `depth` - The Git clone depth. The provided number specifies the last `n`
+ revisions to clone from the repository.
+
+The git getter accepts both URL-style SSH addresses like
+`git::ssh://git@example.com/foo/bar`, and "scp-style" addresses like
+`git::git@example.com/foo/bar`. In the latter case, omitting the `git::` forced
+prefix is allowed if the username prefix is exactly git@.
+
+The "scp-style" addresses cannot be used in conjunction with the `ssh://`
+scheme prefix, because in that case the colon is used to mark an optional port
+number to connect on, rather than to delimit the path from the host.
+
+#### Mercurial (`hg`)
+
+- `rev` - The Mercurial revision to checkout.
+
+#### HTTP (`http` or `https`)
+
+Basic Authentication
+
+To use HTTP basic authentication, simply prepend `username:password@` to the
+hostname in the URL such as `https://Aladdin:OpenSesame@www.example.com/index.html`.
+All special characters, including the username and password, must be URL
+encoded.
+
+#### S3 (`s3`)
+
+The following query parameters are available and will take priority when
+authenticating against an S3 bucket.
+
+- `aws_access_key_id` - AWS access key.
+- `aws_access_key_secret` - AWS access key secret.
+- `aws_access_token` - AWS access token if this is being used.
+- `aws_profile` - Use this profile from local ~/.aws/ config. Takes priority
+ over the other three.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/consul.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/consul.mdx
new file mode 100644
index 0000000000..7a3aaaa697
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/consul.mdx
@@ -0,0 +1,47 @@
+---
+page_title: Consul and Sentinel
+sidebar_current: docs-app-consul
+description: >-
+ Consul Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies are applied during writes to
+ the KV Store.
+layout: docs
+---
+
+# Consul
+
+[Consul Enterprise](https://www.hashicorp.com/products/consul/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies are applied during writes to the KV Store.
+
+Sentinel policies have access to the key/value being written. They can be used to
+allow or deny the modification. The information that Sentinel policies have access
+to will expand over time.
+
+The Consul integration with Sentinel is documented in depth in the
+[Consul Enterprise documentation](https://www.consul.io/docs/guides/sentinel.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Input validation depending on the name of the key.**
+
+```sentinel
+main = rule { valid_key() }
+
+required = [
+ ["port", "\\d+"], # ports must be integers
+ ["name", "\\w+"], # name must be a word
+]
+
+valid_key = func() {
+ for required as v {
+ if key is v[0] {
+ return value matches v[1]
+ }
+ }
+
+ return false
+}
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/extending/index.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/extending/index.mdx
new file mode 100644
index 0000000000..a39cb0384d
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/extending/index.mdx
@@ -0,0 +1,52 @@
+---
+page_title: Extending Sentinel
+sidebar_current: docs-extending
+description: Learn how to create your own Sentinel imports to extend Sentinel's functionality.
+layout: docs
+---
+
+# Extending Sentinel
+
+-> **NOTE:** Sentinel's extensibility is currently undergoing large
+improvements - watch this space for future updates!
+
+Sentinel can be extended by writing additional
+[imports](/sentinel/language/imports). These imports can bring new functionality
+to Sentinel by allowing access to new data or by the addition of new functions.
+This section is designed to show you how to accomplish this extensibility and
+describe how it works.
+
+Currently, the only usable way to add additional imports is via
+[modules](#modules). While [plugins](#plugins) currently work under the Sentinel
+CLI, no Sentinel integration currently supports their use.
+
+-> You may also be interested in advanced details on [import
+internals](/sentinel/extending/internals).
+
+## Modules
+
+Modules allow you to re-use Sentinel code as an import. Modules are loaded
+external of policies and mapped to imports. This is usually configured via a
+file such as the [CLI configuration file](/sentinel/configuration).
+
+See the [modules](/sentinel/extending/modules) section for more details.
+
+## Plugins
+
+-> **NOTE:** Plugins are currently only supported on the CLI and not in any
+production Sentinel integration, with the exception of existing configuration
+in [Nomad](https://www.nomadproject.io/docs/configuration/sentinel).
+
+Imports can also be implemented as plugins when Sentinel code itself is too
+restrictive to represent the import, or if you need access to features not
+normally available to the Sentinel runtime.
+
+See the [plugins](/sentinel/extending/plugins) section for more details.
+
+## Static Imports
+
+Static imports provide support for loading data files into the Sentinel runtime,
+making them available as an import.
+
+See the [static imports](/sentinel/extending/static-imports) section for more
+details.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/extending/internals.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/extending/internals.mdx
new file mode 100644
index 0000000000..7133e6df1b
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/extending/internals.mdx
@@ -0,0 +1,252 @@
+---
+page_title: >-
+ Extending Sentinel: Internals
+sidebar_current: docs-extending-internals
+description: This Section covers some details about Sentinel's import internals.
+layout: docs
+---
+
+# Extending Sentinel: Internals
+
+-> **Advanced Topic!** This page covers low-level technical details of Sentinel.
+You don't need to understand these details to effectively use Sentinel. The
+details are documented here for those who wish to learn about them.
+
+This section covers some of the internal details of Sentinel's import system.
+The goal of this section is to help remove notion of "magic" from Sentinel, to
+allow you to trust and understand what Sentinel is doing with imports, and also
+to help practitioners and developers alike understand the differences between
+the ways that imports can be loaded into Sentinel.
+
+## Language and Runtime
+
+Understanding the import system in Sentinel generally requires understanding of
+two key concepts within Sentinel: the _language_, and the _runtime
+implementation_ of the language.
+
+The Sentinel language and the rules governing it are detailed in the [Sentinel
+Language Specification](/sentinel/language/spec). A runtime implementation must
+conform to the rules laid out in the specification at a minimum. However, to
+meet the design goals of a particular implementation, the runtime may implement
+features on top of the language. The subtle implementation details within the
+runtime may also vary while still being compliant with the specification.
+
+The core runtime included within the [Sentinel CLI](/sentinel/commands) is
+sometimes referred to as the _reference implementation_ of the Sentinel
+language. This runtime is also the main runtime embedded within each
+Sentinel-enabled HashiCorp product such as HCP Terraform, Terraform Enterprise, Vault
+Enterprise, Nomad Enterprise, and Consul Enterprise. The runtime includes an API
+to allow these integrations implement Sentinel in as robust or as restrictive of
+a way as possible, so depending on the implementation, your experience may vary.
+
+## Sentinel's Import Model
+
+The concept of Imports within Sentinel is a _language_ feature. You can see the
+exact rules governing imports within the
+[Imports](/sentinel/language/spec#imports) section of the specification.
+
+Within the runtime, there are currently two major classifications of imports:
+
+- **Modules:** The mapping of Sentinel code to an import, essentially mapping a
+ scope of values to a particular package.
+- **Binary Imports:** A grouping of embedded and external plugins written using
+ the Sentinel SDK. These are comprised of internal or embedded imports (usually
+ the [standard imports](/sentinel/imports) and imports included by an
+ integration) and import plugins.
+
+Below is a diagram explaining in brief the relationship between the langauge
+and runtime features.
+
+
+
+This kind of topology ultimately minimizes the visibility of implementation
+internals to the actual policy language. This allows us to keep the Sentinel
+language itself simple and keep concerns such as module or plugin management,
+validation, and configuration out of the language. These kinds of things would
+impact the readability of a policy, possibly present security challenges, and
+would ultimately be of no use to more minimal embedded applications.
+
+### Implementation Differences Between Import Types
+
+As one would imagine, both modules and binary imports are loaded in much
+different ways, and the methods that they expose data to Sentinel differ as
+well. The effect is generally the same however across both, and we take care to
+ensure that both modules and binary imports behave as close as possible to each
+other, however there are some subtle differences:
+
+- Modules currently support the exporting of rules, but do not support function
+ calls with object receiver data (as seen in some standard imports such as
+ [`decimal`](/sentinel/imports/decimal), [`http`](/sentinel/imports/http), and
+ [`time`](/sentinel/imports/time)). This will be coming in a later release.
+- Binary imports cannot export rules. However, they do support object receiver
+ data (see [`framework.New`](/sentinel/extending/plugins#framework-new) in
+ [Extending Sentinel: Plugins](/sentinel/extending/plugins)).
+
+## Modules
+
+_Modules_ are essentially Sentinel code that is intended to be re-used in multiple
+policies as an import.
+
+The mechanism of module loading is fairly straightforward: during initialization
+of the Sentinel runtime, modules are parsed along with policies. The parsed
+_abstract syntax tree_ (AST) for each module is loaded into the runtime under
+the specified _import path_. These are then passed to the lower-level
+interpreter during the evaluation of each policy.
+
+As with policy code, during evaluation, a module's AST is walked to execute the
+code within that specific module. The module data is then stored within an
+specific scope mapped to the import name. The module data can then be accessed
+through the import via selector expressions and function calls (e.g.,
+`foo.some_map` or `foo.some_func()` for a module loaded via `import "foo"`).
+
+The AST for a module is only walked if a policy imports it, and it is _only
+walked once_. That means if a policy and a module import the same module, or a
+policy imports the same module twice (using [import
+aliases](/sentinel/language/imports#aliases)), those instances will share state.
+If you call a function that alters a singleton variable within the module, that
+singleton will be modified for all instances of the import.
+
+-> **Fun fact!** When you [mock an import with Sentinel
+code](/sentinel/configuration#mocking-with-sentinel-code), you are actually
+using a module. The module is loaded with a flag that allows it to override an
+existing import, which would normally give a runtime error.
+
+### Map and List Cloning for Module Calls
+
+It's also worthwhile noting that map and list values are cloned for modules.
+
+Consider the case where you have a module with a map singleton.
+
+```sentinel
+// modules/foo.sentinel
+a_map = {"a": "b"}
+```
+
+Normally, assignment to the singleton, even when using an index expression, is
+blocked, so `foo.a_map["c"] = "d"` would not pass semantic checking.
+
+However, even when you try to do assignment via an intermediary, you will still
+be only modifying the copy, and the original will not be affected:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+a_map_copy = foo.a_map
+a_map_copy["c"] = "d" // Only modifies copy, does not modify module singleton
+print(foo.a_map) // Still only prints {"a": "b"}
+```
+
+This is to ensure that modules are consistent with binary imports in how return
+data is handled, and the expectation that most non-object data within an import
+is read-only, with the exception of modification of singleton data via
+functions. As return of complex object and collection data in a binary import is
+always via copy, it's not possible to modify any value elements within the
+import in a similar fashion.
+
+There are also some other implications of this cloning that are of note:
+
+- Rules are not cloned, either within a list or map, or by themselves. This is
+ to ensure that [memoization](/sentinel/language/rules#lazy-and-memoized)
+ functions correctly. As only a module can export rules at this time, there is no
+ parity for this in binary imports.
+- Functions are _not_ cloned. This mainly has implications for scope - for
+ example, if you assign a function to another value, or assign an object to a
+ value that has a method that utilizes a global module variable, those calls will
+ reference and/or modify those values.
+
+Functions, while represented differently in binary imports, generally follow the
+same logic here - as they would be invoked in the same package within the binary
+import, any singleton values they accessed there would be affected in a similar
+fashion. As rules are pseudo-functions, this generally makes sense here too.
+
+## Binary Imports
+
+_Binary imports_ is a grouping referring to all imports that are designed with
+the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). These can be
+either internally embedded, or executed via a plugin.
+
+All imports found in the [standard library](/sentinel/imports) are binary
+imports, embedded internally.
+
+The main difference between internally embedded imports and external plugins is
+whether or not the runtime needs to execute a plugin binary as part of
+[lifecycle management](#lifecycle), and whether or not it needs to
+[communicate](#communication) over RPC. Internal binary imports do not require
+these steps, so their execution model is somewhat simpler; however, technically,
+they still are the same as external plugins in terms of design model and value
+conversion. Most standard imports are even tested using the [SDK test
+suite](/sentinel/extending/plugins#testing), which builds the import as an
+external plugin.
+
+The remainder of this section discusses topics mostly relevant to external
+plugins. A large amount of technical detail regarding binary import development
+(also applicable to embedded binary imports) can be seen on the
+[Plugins](/sentinel/extending/plugins) page.
+
+-> **NOTE:** At this time, plugins can only be written for the Sentinel CLI, and
+cannot be used with any of Sentinel's integrations.
+
+### Plugin Basics
+
+Sentinel plugins are built on top of the HashiCorp [go-plugin
+system](https://github.com/hashicorp/go-plugin). This is the same plugin system
+powering all pluggable HashiCorp tools such as
+[Terraform](https://www.terraform.io), [Vault](https://www.vaultproject.io), and
+more. go-plugin is a system that has been used in production for millions of
+users for over 5 years.
+
+Plugins are executable binaries. Sentinel is responsible for launching and
+managing the lifecycle of plugins. Once a plugin is running, Sentinel
+communicates with the plugin via [gRPC](https://grpc.io/). The [protocol is open
+source](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/import.proto)
+to allow anyone to write a Sentinel plugin.
+
+### Lifecycle
+
+Sentinel launches plugins and is responsible for plugin lifecycle.
+
+The runtime optimizes for latency by launching the plugin as soon as it is
+configured, rather than when a policy requires it. This ensures that the plugin
+is ready to be used immediately. A plugin is only closed when Sentinel is
+reconfigured to no longer allow that plugin or if the Sentinel-enabled
+application is closing.
+
+When a plugin is removed from the configuration, Sentinel may wait to shut down
+the plugin until all currently executing policies that are using that plugin
+complete. New policy executions will not be allowed to use the old plugins.
+
+Plugins are automatically restarted if they shut down unexpectedly. Policies
+that were executing while this happens may fail. The Sentinel system is
+improving to more gracefully understand locations where it is safe to retry an
+execution.
+
+### Communication
+
+An [initial
+handshake](https://github.com/hashicorp/go-plugin/blob/master/docs/internals.md)
+is done via stdout to determine the main communication location. Following the
+handshake, communication will either occur on a local Unix domain socket or via
+a local-only TCP socket. This communication is done mostly over the low-level
+[`Get`](https://github.com/hashicorp/sentinel-sdk/blob/2b4db07dd12b55142f0c016f301af80aa4422520/proto/import.proto#L12)
+RPC call.
+
+#### Value Conversion
+
+As can generally be seen in [Developing an Import
+Plugin](/sentinel/extending/plugins#developing-an-import-plugin), the Sentinel
+type system is not exposed to binary imports. While explicit values for null and
+undefined exist, values are mostly converted over the wire from their Go values
+to their Sentinel equivalents.
+
+This has a few implications:
+
+- As discussed in [Map and List Cloning for Module
+ Calls](#map-and-list-cloning-for-module-calls), Map and list data returned from
+ binary import value lookups or function calls is always cloned. These can be
+ freely modified without affecting anything within the binary import.
+- It's currently impossible to represent certain Sentinel types such as
+ [rules](/sentinel/language/rules) within a binary import. This is not a major
+ issue at this point in time as practitioners generally do not look to binary
+ imports for rules; we may examine this as a later time if this situation
+ changes.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/extending/modules.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/extending/modules.mdx
new file mode 100644
index 0000000000..9846d263e5
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/extending/modules.mdx
@@ -0,0 +1,135 @@
+---
+page_title: >-
+ Extending Sentinel: Modules
+sidebar_current: docs-extending-modules
+description: >-
+ Modules allow you to re-use Sentinel code as an import.
+layout: docs
+---
+
+# Extending Sentinel: Modules
+
+Modules allow you to re-use Sentinel code as an import. This allows for the
+export of any general construct that Sentinel supports, including values,
+functions, and even rules. As such, it's a great way to package code that is
+useful across multiple policies, reducing boilerplate and making final policy
+code simpler.
+
+You may want to use modules for:
+
+- **Writing a simple re-usable helper library.** If you mostly know Sentinel or
+ have helpers that you have written in Sentinel, moving these helpers to a module
+ will offer a low-friction way of facilitating their re-use.
+- **Writing re-usable rules.** If you have a set of
+ [rules](/sentinel/language/rules) you know you want to use across multiple
+ policies, bundling them into a module is a great way of abstracting the logic
+ away.
+- **Abstracting existing Sentinel imports.** Using a module is a great way to
+ extend existing Sentinel imports by abstracting the common functionality that
+ you use within an import to your own workflow.
+
+This page describes how you can use modules within the context of the Sentinel
+CLI. For instructions for a particular integration, see the documentation for
+that product.
+
+-> **Not all Sentinel-enabled applications support modules.** Please refer
+to the documentation of the specific Sentinel-enabled application. Some
+applications disable modules for security reasons.
+
+## Configuring a Module
+
+A module is configured within the Sentinel CLI by adding an entry for it in
+within the `imports` section of the [CLI configuration
+file](/sentinel/configuration). The key for each entry specifies the path that
+the module is imported as.
+
+```hcl
+import "module" "foo" {
+ source = "modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "modules/bar.sentinel"
+}
+```
+
+In this example, we have two modules:
+
+- Import path `foo`, being loaded from the code within `modules/foo.sentinel`.
+- Import path `bar`, being loaded from the code within `modules/bar.sentinel`.
+
+See the [Import Modules](/sentinel/configuration#import-modules) section within
+the CLI configuration file syntax page for more details.
+
+### Remote Modules
+
+In addition to loading from a local source, modules can be loaded from a remote
+location. This can increase reusability and allow for sharing modules across
+multiple configurations.
+
+### Testing Using Modules
+
+As with [mocks](/sentinel/configuration#mocking-with-sentinel-code), modules
+are loaded relative to the configuration file location when using `sentinel test`. As such, you need to account for this in your test configuration files:
+
+```hcl
+import "module" "foo" {
+ source = "../../modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "../../modules/bar.sentinel"
+}
+```
+
+This could be a test file for a policy (example: `policy.sentinel`), residing
+under the correct test file directory for this policy (example:
+`test/policy/pass.json`).
+
+## Writing and Using a Module
+
+Writing a module is nearly the same as writing a Sentinel policy, except for the
+following two exceptions:
+
+- Modules do not require [`main`](/sentinel/writing/basic#the-simplest-policy)
+ to be present. It can be, but it will not carry any extra significance as it
+ does within a policy.
+- [`param`](/sentinel/language/parameters) declarations cannot be present in a module. If they are encountered,
+ a runtime error is given.
+
+Once you have a complete module ready for use and configured, using the module
+is as simple as importing it.
+
+Building on the above [configuration example](#configuring-a-module), we can
+export a simple test function in the module `foo` by adding this code to
+`modules/foo.sentinel`:
+
+```sentinel
+// modules/foo.sentinel
+hello = func() {
+ print("hello world!")
+ return undefined
+}
+```
+
+We can then import it within our policy and use it:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+// prints "hello world" in the trace
+foo.hello()
+
+main = true
+```
+
+Note that modules are considered [imports](/sentinel/language/spec#imports) by
+the Sentinel runtime and as such conform to the specification. This means that
+you cannot directly assign values to anything behind a module scope. You can,
+however, use functions to change state.
+
+-> **NOTE:** At this time, modules do not support object receiver data in the
+way that some imports do, like [`time`](/sentinel/imports/time),
+[`decimal`](/sentinel/imports/decimal), and [`http`](/sentinel/imports/http).
+Support for this will be coming in later versions of Sentinel.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/extending/plugins.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/extending/plugins.mdx
new file mode 100644
index 0000000000..e80de5d4b1
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/extending/plugins.mdx
@@ -0,0 +1,521 @@
+---
+page_title: >-
+ Extending Sentinel: Plugins
+sidebar_current: docs-extending-plugins
+description: >-
+ Plugins allow you to write imports as standalone executables, allowing you to
+ extend Sentinel in ways not normally possible with policy code alone.
+layout: docs
+---
+
+# Extending Sentinel: Plugins
+
+-> **NOTE:** Plugins are currently only supported on the CLI and not in any
+production Sentinel integration, with the exception of existing configuration
+in [Nomad](https://www.nomadproject.io/docs/configuration/sentinel).
+
+Plugins allow you to write imports as standalone executables, allowing you to
+extend Sentinel in ways not normally possible with policy code alone.
+
+You might want to write or use a plugin when you need to:
+
+- **Use a provider or API integration for Sentinel.** In this case, you will
+ probably be working with a provider SDK, or other libraries that will be making
+ complex network requests to external services. Sentinel only provides limited
+ capabilities for these kinds of operations in the standard library, so writing
+ this kind of import as a module is not optimal.
+- **Introduce novel functionality not currently supported by Sentinel.**
+ Sentinel's policy language is limited by design to encourage simple policies,
+ reduce its runtime footprint, and to keep it efficient and safe. This will
+ mean that some functionality may be difficult or impossible to express as
+ Sentinel code, and would require a plugin to write.
+- **Extend a complex module.** Additionally, modules are restricted to a single
+ file of Sentinel code, so these will naturally become more and more unwieldy as
+ you continue to add to them. Eventually, there might be a time where a plugin is
+ better suited for the functionality, especially if it's a general-purpose
+ library.
+
+This section details how import plugins are currently configured in the Sentinel
+CLI, and some detail on how they are written. As we continue to build on the
+ability to use import plugins, we will extend this section with more details.
+
+-> **Not all Sentinel-enabled applications will support plugins.** Some
+applications will disable plugins for security reasons. For those that do enable
+plugins, the method to configure plugins will vary.
+
+## Installing Plugins
+
+Sentinel plugins are standalone executables. Sentinel-enabled applications such
+as the CLI launch these plugins and communicate with them via
+[RPC](https://en.wikipedia.org/wiki/Remote_procedure_call). To install a plugin,
+the first step is to download the plugin executable.
+
+After downloading the plugin, you must add it in the CLI configuration file,
+setting its name, path, and any arguments, environment variables, or
+configuration. An example is below:
+
+```hcl
+import "plugin" "custom_time" {
+ source = "/path/to/sentinel-import-time"
+ config = { "fixed_time": 1504155600 }
+}
+```
+
+-> **NOTE:** We are using "custom_time" as standard import paths cannot be
+overriden. See [Configuration File Syntax](/sentinel/configuration#imports) for
+more details.
+
+After configuring the plugin, it will be available on the next `sentinel apply`
+or `sentinel test`.
+
+For more details on configuration, see the
+[plugins](/sentinel/configuration#plugins) section of the [CLI configuration
+file syntax](/sentinel/configuration).
+
+## Debugging Plugins
+
+The Sentinel CLI logs when it launches, configures, and closes a plugin. The
+plugin may also log additional information when it is running.
+
+To debug issues with a plugin, you can usually refer to these logs. Depending on
+the plugin configuration, the logs you see may vary - refer to the plugin
+documentation on how to enable logs for a particular plugin.
+
+## Developing an Import Plugin
+
+Anyone can develop a Sentinel import plugin using the [Sentinel
+SDK](https://github.com/hashicorp/sentinel-sdk). The SDK contains a high-level
+framework for writing plugins in [Go](https://golang.org) including a test
+framework. Additionally, the SDK contains the low-level [gRPC protocol buffers
+definition](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/plugin.proto)
+for writing plugins in other languages.
+
+The rest of this page will document how to write a new Sentinel plugin in Go
+using the Sentinel SDK and the high-level framework provided. You are expected
+to already know the basics of Go and have a properly configured Go installation.
+
+Throughout this page, we'll be developing a simple plugin to access time values.
+
+-> **NOTE:** The example here is not reflective of the actual implementation of
+the [`time`](/sentinel/imports/time) standard import, so make sure to not
+get the two confused.
+
+### Plugin Framework
+
+The Sentinel SDK provides a high-level
+[framework](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework)
+for writing plugins. We recommend using this framework.
+
+#### Basic Concepts
+
+This framework works by implementing the correct Go interfaces.
+
+As a general overview:
+[`framework.Plugin`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Plugin)
+implements the
+[`sdk.Plugin`](https://godoc.org/github.com/hashicorp/sentinel-sdk#Plugin)
+interface and therefore is a valid import plugin. You must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface to configure the plugin. The root may then delegate nested access to
+various
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+implementations. Other interfaces are implemented to provide functionality past
+what is provided by the basic `framework.Namespace` implementation - these are
+documented below.
+
+This all may sound like a lot of interfaces, but each interface typically only
+requires a single function implementation and you're only required to implement
+a single namespace (`framework.Namespace`).
+
+As we move on, we'll use real examples to make this clear.
+
+#### Implementing framework.Root
+
+To begin, you must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface. This is the interface representing the root of your plugin. The root
+itself must be implement
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace),
+which allows values to be accessed. Note that the root can optionally implement
+other interfaces, but they're more advanced and omitted here for simplicity.
+
+For our custom time example, our root may look like this:
+
+```sentinel
+type root struct {
+ time time.Time
+}
+
+// framework.Root impl.
+func (m *root) Configure(raw map[string]interface{}) error {
+ if _, ok := raw["timestamp"]; !ok {
+ raw["timestamp"] = time.Now().Unix()
+ }
+
+ v := raw["timestamp"]
+ timestamp, ok := v.(int)
+ if !ok {
+ return fmt.Errorf("invalid timestamp type %T", v)
+ }
+
+ m.time = time.Unix(timestamp, 0).UTC()
+ return nil
+}
+
+// framework.Namespace impl.
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "minute":
+ return m.time.Minute(), nil
+ }
+
+ return nil, nil
+}
+```
+
+This example implements `framework.Root` and `framework.Namespace` and would
+enable the following within a Sentinel policy once the completed plugin is
+installed.
+
+```sentinel
+import "custom_time"
+
+print(custom_time.minute)
+main = true
+```
+
+#### framework.Namespace
+
+The
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+interface implements a namespace of values. You saw above that
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+must itself be a namespace. However, a namespace `Get` implementation may
+further return namespaces to access nested values.
+
+This enables behavior such as `custom_time.month.string` vs. `custom_time.month.index`.
+Notice each of these examples accesses a nested value within `month`. This can
+be modeled as namespaces within the framework.
+
+In the example below, we return a new namespace for `month` to do just this:
+
+```sentinel
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "month":
+ return &namespaceMonth{Month: m.time.Month()}, nil
+ }
+
+ // ...
+}
+
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil nil
+}
+```
+
+#### Primitive Types and Structs
+
+A namespace may also return any primitive Go type as well as structs. The
+framework automatically exposes primitive values as you would expect. For
+structs, exported fields are lowercased and exposed to the policies. The
+`sentinel` struct tag can be used to control how Sentinel can access the field.
+
+In the example below, we expose a struct directly from the root namespace:
+
+```sentinel
+type Location struct {
+ Name string
+ TimeZone string `sentinel:"time_zone"`
+}
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "location":
+ return &Location{Name: "somewhere", TimeZone: "some zone"}, nil
+ }
+
+ // ...
+}
+```
+
+This makes the following values work within a Sentinel policy:
+`time.location.name`, `time.location.time_zone`.
+
+If a field has an empty `sentinel` struct tag (example: `sentinel:""`),
+then that field will not be accessible from a policy.
+
+#### Optional Interfaces
+
+The following are optional interfaces that can be implemented on top of
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace).
+All of these interfaces can be implemented at any level, except for
+[`framework.New`](#framework-new), which only works at the root level.
+
+##### `framework.Call`
+
+The
+[`framework.Call`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Call)
+interface can be implemented to support function calls.
+
+Implementing this interface is very similar to attribute access, except instead
+of returning the attribute value, you return a function. The framework uses Go
+reflection to determine the argument and result types and calls it. If the
+argument types do not match or the signature is otherwise invalid, an error is
+returned.
+
+For example, let's implement a function to add months to the current month:
+
+```sentinel
+func (m *root) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *root) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.time.AddDate(0, n, 0).Month()}
+}
+```
+
+You can now call the function. If today was in the month of September,
+the policy below would pass:
+
+```sentinel
+import "custom_time"
+
+main = custom_time.add_month(4).string == "January"
+```
+
+##### `framework.Map`
+
+The optional
+[`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+interface allows an alternative method to to present a namespace back to a
+Sentinel policy as a map.
+
+`framework.Map` is best paired with
+[`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys),
+which allows you to quickly fetch the necessary data via `Get` calls on the
+namespace:
+
+```
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil, nil
+}
+
+func (m *namespaceMonth) Map() (map[string]interface{}, error) {
+ return framework.MapFromKeys(m, []string{"string", "index"})
+}
+```
+
+##### `framework.New`
+
+The optional
+[`framework.New`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#New)
+interface can be implemented on a root namespace to add _methods_ to your
+namespaces.
+
+Data returned from a namespace is memoized and returned as a map, and is
+normally not callable - to call a function to operate on the data, you would
+need to create a new namespace from the top-level of the plugin, and then make a
+function call on the result within the same expression.
+
+Let's consider the [root example](#implementing-framework-root). Let's say,
+instead of hard-coding the time value at configuration, we wanted to load it
+on-demand using a `time.now` key, and allow `add_month` to be callable on that
+value. Our root and de-coupled time namespace would now look like:
+
+```
+type root struct{}
+
+func (m *root) Configure(raw map[string]interface{}) error { return nil }
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "now":
+ return &namespaceTime{Time: time.Now()}
+ }
+
+ return nil
+}
+
+func (m *root) New(data map[string]interface{}) (framework.Namespace, error) {
+ if v, ok := data["unix"]; ok {
+ if t, ok := v.(int64); !ok {
+ return &namespaceTime{Time: time.Unix(t, 0)}
+ }
+
+ return nil, fmt.Errorf("expected timestamp to be int64, got %T", v)
+ }
+
+ return nil, nil
+}
+
+type namespaceTime struct {
+ Time time.Time
+}
+
+func (m *namespaceTime) Get(key string) interface{} {
+ switch key {
+ case "unix":
+ return m.Time.Unix()
+
+ // case ...
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) Get(key string) (interface{}, error) {
+ return framework.MapFromKeys(m, []string{"unix", "..."})
+}
+
+func (m *namespaceTime) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.Time.AddDate(0, n, 0).Month()}
+}
+```
+
+Via this example, we can now create and assign a `namespaceTime` using `t = custom_time.now`, and then call `t.add_month(months_to_add)` to return a
+`namespaceMonth` for the corresponding month.
+
+Methods on a namespace can also _modfiy_ the receiver data. So you can, for
+example, add a method named `increment_month` that takes no arguments, but
+increments the time stored in `namespaceTime.Time` by a month. Subsequent
+statements using the receiver would see the new value.
+
+Note that there are some restrictions and guidelines that you should take into
+account when using `framework.New`:
+
+- Only data that makes it back to a policy can be used as receiver data to
+ instantiate new namespaces. As such it's recommended to make use of
+ [`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+ and
+ [`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys)
+ to return all of the attribute data you need to construct new objects.
+- `framework.New` is only supported on the root namespace and has no effect on
+ namespaces below the root. Check all possible cases of receiver data within
+ the top-level `New` function and return the appropriate namespace based on the
+ input data.
+- Do not return general errors from your `New` method. When an unknown lookup is
+ made off of memoized data, it will hit your `New` method for possible
+ instantiation and key calls. This will ensure `undefined` is correctly
+ returned for these cases.
+- `framework.New` is designed to add utility to plugins where calling methods on
+ a value is more intuitive than just making a function call, or just returning
+ all of a data set as a memoized map. Use it sparingly and avoid using it on
+ recursively complex data sets.
+
+### Testing
+
+The SDK exposes a testing framework to verify your plugin works as
+expected. This test framework integrates directly into `go test` so it
+is part of a familiar workflow.
+
+The test framework works by dynamically building your plugin and
+running it against the `sentinel` CLI to verify it behaves as expected.
+This ensures that your plugin builds, your plugin communicates via RPC
+correctly, and that the behavior within a policy is also correct.
+
+The example below shows a complete ready table-driven test for our time plugin.
+You can run this with a normal `go test`.
+
+```sentinel
+package main
+
+import (
+ "os"
+ "testing"
+
+ "github.com/hashicorp/sentinel-sdk"
+ plugintesting "github.com/hashicorp/sentinel-sdk/testing"
+)
+
+func TestMain(m *testing.M) {
+ exitCode := m.Run()
+ plugintesting.Clean()
+ os.Exit(exitCode)
+}
+
+func TestPlugin(t *testing.T) {
+ cases := []struct {
+ Name string
+ Data map[string]interface{}
+ Source string
+ }{
+ {
+ "month",
+ map[string]interface{}{"timestamp": 1495483674},
+ `main = subject.month.string == "September"`,
+ },
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.Name, func(t *testing.T) {
+ plugintesting.TestPlugin(t, plugintesting.TestPluginCase{
+ Config: tc.Data,
+ Source: tc.Source,
+ })
+ })
+ }
+}
+```
+
+### Building Your Plugin
+
+To build your plugin, you must first implement the `main` function.
+The main function should just use the `rpc.Serve` method to serve the
+plugin over the Sentinel RPC layer:
+
+```sentinel
+package main
+
+import (
+ "github.com/hashicorp/sentinel-sdk"
+ "github.com/hashicorp/sentinel-sdk/rpc"
+)
+
+func main() {
+ rpc.Serve(&rpc.ServeOpts{
+ PluginFunc: func() sdk.Plugin {
+ return &framework.Plugin{Root: &root{}}
+ },
+ })
+}
+```
+
+You can then build this using `go build`. The output can be named
+anything. Once your plugin is built, [install it](#installing-plugins)
+and try it out!
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/extending/static-imports.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/extending/static-imports.mdx
new file mode 100644
index 0000000000..cb51beb5d8
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/extending/static-imports.mdx
@@ -0,0 +1,48 @@
+---
+page_title: >-
+ Extending Sentinel: Static Imports
+sidebar_current: docs-extending-static-imports
+description: >-
+ Static imports allow you to write imports that use static data files.
+layout: docs
+---
+
+# Extending Sentinel: Static Imports
+
+Static imports allow you to write imports that use static data files. This can
+provide methods of consuming data in ways that are not available via
+[modules](./modules) or [plugins](./plugins).
+
+### Configuring a Static Import
+
+The [configuration page](/sentinel/configuration#static-imports) details how to
+configure static imports.
+
+### Usage
+
+Using a static import is not that different to using a plugin or module. The
+import must first be introduced to the policy via an import statement.
+
+```sentinel
+import "people"
+```
+
+From here, the static data can be used throughout the policy as you would any
+normal Sentinel value.
+
+```sentinel
+admins = filter people as person {
+ person.role is "Admin"
+}
+```
+
+One differentiator for static imports is that you can directly reference the
+import without the use of selectors. For example, to print the value;
+
+```sentinel
+print(people)
+```
+
+This is due to the static data being directly loaded and converted into a
+Sentinel value, ready for use. Static imports, like modules and plugins, cannot
+be assigned a new value.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/features/index.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/features/index.mdx
new file mode 100644
index 0000000000..e035be978c
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/features/index.mdx
@@ -0,0 +1,28 @@
+---
+page_title: Features
+sidebar_current: docs-features
+description: Learn how features can enhance the Sentinel runtime by enabling further capabilities.
+layout: docs
+---
+
+# Features
+
+Features are a set of capabilities that enhance the runtime experience. They
+are enabled through the [`sentinel`](/sentinel/configuration#sentinel) block of the
+configuration, using the features attribute. An example of how to enable features
+is as follows:
+
+```hcl
+sentinel {
+ features = {
+ apply-all = true
+ terraform = true
+ }
+}
+```
+
+The current set of features are:
+
+- [terraform](/sentinel/features/terraform) - Allowing the Sentinel runtime
+ to interact with Terraform data.
+- apply-all - Force Sentinel to evaluate all policies within a policy set without prematurely terminating on failures.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/features/terraform/index.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/features/terraform/index.mdx
new file mode 100644
index 0000000000..a810dbe478
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/features/terraform/index.mdx
@@ -0,0 +1,24 @@
+---
+page_title: terraform
+sidebar_current: docs-features-terraform
+description: Learn about the terraform feature and its capabilities.
+layout: docs
+---
+
+# terraform feature
+
+The `terraform` feature enhances the Sentinel runtime to work with Terraform
+data. It will add the following imports to the standard library:
+
+- [tfplan/v1](/sentinel/features/terraform/tfplan-v1)
+- [tfplan/v2](/sentinel/features/terraform/tfplan-v2)
+- [tfconfig/v1](/sentinel/features/terraform/tfconfig-v1)
+- [tfconfig/v2](/sentinel/features/terraform/tfconfig-v2)
+- [tfstate/v1](/sentinel/features/terraform/tfstate-v1)
+- [tfstate/v2](/sentinel/features/terraform/tfstate-v2)
+
+-> **NOTE:** The above imports will only work with Terraform 0.12 and above,
+as they rely on the output from the `terraform show -json` command.
+
+It is recommended that the `/v2` suffixed imports are used, as they provide
+the best experience when interacting with the underlying data structures.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/features/terraform/tfconfig-v1.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/features/terraform/tfconfig-v1.mdx
new file mode 100644
index 0000000000..95fb5840ab
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/features/terraform/tfconfig-v1.mdx
@@ -0,0 +1,949 @@
+---
+page_title: tfconfig/v1 - terraform - Features
+sidebar_current: docs-features-terraform-config-v1
+description: The tfconfig/v1 import provides access to a Terraform configuration.
+layout: docs
+---
+
+# Import: tfconfig/v1
+
+~> **Warning:** The `tfconfig/v1` import is deprecated and will be permanently removed in August 2025.
+Use the [tfconfig/v2](/sentinel/docs/features/terraform/tfconfig-v2) import as soon as possible to avoid disruptions.
+The `tfconfig/v2` import offers improved functionality and is designed to better support your policy enforcement needs.
+
+The `tfconfig/v1` import provides access to a Terraform configuration.
+
+The Terraform configuration is the set of `*.tf` files that are used to
+describe the desired infrastructure state. Policies using the `tfconfig/v1`
+import can access all aspects of the configuration: providers, resources,
+data sources, modules, and variables.
+
+Some use cases for `tfconfig/v1` include:
+
+* **Organizational naming conventions**: requiring that configuration elements
+ are named in a way that conforms to some organization-wide standard.
+* **Required inputs and outputs**: organizations may require a particular set
+ of input variable names across all workspaces or may require a particular
+ set of outputs for asset management purposes.
+* **Enforcing particular modules**: organizations may provide a number of
+ "building block" modules and require that each workspace be built only from
+ combinations of these modules.
+* **Enforcing particular providers or resources**: an organization may wish to
+ require or prevent the use of providers and/or resources so that configuration
+ authors cannot use alternative approaches to work around policy
+ restrictions.
+
+Note with these use cases that this import is concerned with object _names_
+in the configuration. Since this is the configuration and not an invocation
+of Terraform, you can't see values for variables, the state, or the diff for
+a pending plan. If you want to write policy around expressions used
+within configuration blocks, you likely want to use the
+[`tfplan/v1`](/sentinel/features/terraform/tfplan-v1) import.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfconfig/v1" {
+ config = {
+ "plan": "./path/to/plan.json"
+ }
+}
+```
+
+## Namespace Overview
+
+The following is a tree view of the import namespace. For more detail on a
+particular part of the namespace, see below.
+
+-> **Note:** The root-level alias keys shown here (`data`, `modules`,
+`providers`, `resources`, and `variables`) are shortcuts to a [module
+namespace](#namespace-module) scoped to the root module. For more details, see
+the section on [root namespace aliases](#root-namespace-aliases).
+
+```
+tfconfig/v1
+├── module() (function)
+│ └── (module namespace)
+│ ├── data
+│ │ └── TYPE.NAME
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── provisioners
+│ │ └── NUMBER
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── type (string)
+│ ├── modules
+│ │ └── NAME
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ ├── source (string)
+│ │ └── version (string)
+│ ├──outputs
+│ │ └── NAME
+│ │ ├── depends_on (list of strings)
+│ │ ├── description (string)
+│ │ ├── sensitive (boolean)
+│ │ ├── references (list of strings)
+│ │ └── value (value)
+│ ├── providers
+│ │ └── TYPE
+│ │ ├── alias
+│ │ │ └── ALIAS
+│ │ │ ├── config (map of keys)
+│ │ | ├── references (map of keys)
+│ │ │ └── version (string)
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── version (string)
+│ ├── resources
+│ │ └── TYPE.NAME
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── provisioners
+│ │ └── NUMBER
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── type (string)
+│ └── variables
+│ └── NAME
+│ ├── default (value)
+│ └── description (string)
+├── module_paths ([][]string)
+│
+├── data (root module alias)
+├── modules (root module alias)
+├── outputs (root module alias)
+├── providers (root module alias)
+├── resources (root module alias)
+└── variables (root module alias)
+```
+
+### `references` Overview
+
+As an example, consider the following resource block:
+
+```hcl
+resource "local_file" "accounts" {
+ content = "some text"
+ filename = "${var.subdomain}.${var.domain}/accounts.txt"
+}
+```
+
+In this example, one might want to ensure `domain` and `subdomain` input
+variables are used within `filename` in this configuration.
+
+-> Any non-static values (such as interpolated strings) are not present within the
+configuration value and `references` should be used instead:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+# filename_references is a list of string values containing the references used in the expression
+filename_references = tfconfig.resources.local_file.accounts.references.filename
+
+main = rule {
+ filename_references contains "var.domain" and
+ filename_references contains "var.subdomain"
+}
+```
+
+The `references` value is present in any namespace where non-constant
+configuration values can be expressed. This is essentially every namespace
+which has a `config` value as well as the `outputs` namespace.
+
+-> **Note:** Remember, this import enforces policy around the literal Terraform
+configuration and not the final values as a result of invoking Terraform. If
+you want to write policy around the _result_ of expressions used within
+configuration blocks (for example, if you wanted to ensure the final value of
+`filename` above includes `accounts.txt`), you likely want to use the
+[`tfplan/v1`](/sentinel/features/terraform/tfplan-v1) import.
+
+## Namespace: Root
+
+The root-level namespace consists of the values and functions documented below.
+
+In addition to this, the root-level `data`, `modules`, `providers`, `resources`,
+and `variables` keys all alias to their corresponding namespaces within the
+[module namespace](#namespace-module).
+
+
+
+### Function: `module()`
+
+```
+module = func(ADDR)
+```
+
+* **Return Type:** A [module namespace](#namespace-module).
+
+The `module()` function in the [root namespace](#namespace-root) returns the
+[module namespace](#namespace-module) for a particular module address.
+
+The address must be a list and is the module address, split on the period (`.`),
+excluding the root module.
+
+Hence, a module with an address of simply `foo` (or `root.foo`) would be
+`["foo"]`, and a module within that (so address `foo.bar`) would be read as
+`["foo", "bar"]`.
+
+[`null`][ref-null] is returned if a module address is invalid, or if the module
+is not present in the configuration.
+
+[ref-null]: /sentinel/language/spec#null
+
+As an example, given the following module block:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+If the module contained the following content:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { subject.module(["foo"]).resources.null_resource.foo.config.triggers[0].foo is "bar" }
+```
+
+
+
+### Value: `module_paths`
+
+* **Value Type:** List of a list of strings.
+
+The `module_paths` value within the [root namespace](#namespace-root) is a list
+of all of the modules within the Terraform configuration.
+
+Modules not present in the configuration will not be present here, even if they
+are present in the diff or state.
+
+This data is represented as a list of a list of strings, with the inner list
+being the module address, split on the period (`.`).
+
+The root module is included in this list, represented as an empty inner list.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The value of `module_paths` would be:
+
+```
+[
+ [],
+ ["foo"],
+]
+```
+
+And the following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.module_paths contains ["foo"] }
+```
+
+#### Iterating Through Modules
+
+Iterating through all modules to find particular resources can be useful. This
+[example][iterate-over-modules] shows how to use `module_paths` with the
+[`module()` function](#function-module-) to find all resources of a
+particular type from all modules using the `tfplan` import. By changing `tfplan`
+in this function to `tfconfig`, you could make a similar function find all
+resources of a specific type in the Terraform configuration.
+
+[iterate-over-modules]: https://developer.hashicorp.com/terraform/cloud-docs/policy-enforcement/sentinel#sentinel-imports
+
+## Namespace: Module
+
+The **module namespace** can be loaded by calling [`module()`](#root-function-module)
+for a particular module.
+
+It can be used to load the following child namespaces:
+
+* `data` - Loads the [resource namespace](#namespace-resources-data-sources),
+ filtered against data sources.
+* `modules` - Loads the [module configuration
+ namespace](#namespace-module-configuration).
+* `outputs` - Loads the [output namespace](#namespace-outputs).
+* `providers` - Loads the [provider namespace](#namespace-providers).
+* `resources` - Loads the [resource
+ namespace](#namespace-resources-data-sources), filtered against resources.
+* `variables` - Loads the [variable namespace](#namespace-variables).
+
+### Root Namespace Aliases
+
+The root-level `data`, `modules`, `providers`, `resources`, and `variables` keys
+all alias to their corresponding namespaces within the module namespace, loaded
+for the root module. They are the equivalent of running `module([]).KEY`.
+
+
+
+## Namespace: Resources/Data Sources
+
+The **resource namespace** is a namespace _type_ that applies to both resources
+(accessed by using the `resources` namespace key) and data sources (accessed
+using the `data` namespace key).
+
+Accessing an individual resource or data source within each respective namespace
+can be accomplished by specifying the type and name, in the syntax
+`[resources|data].TYPE.NAME`.
+
+In addition, each of these namespace levels is a map, allowing you to filter
+based on type and name. Some examples of multi-level access are below:
+
+* To fetch all `aws_instance` resources within the root module, you can specify
+ `tfconfig.resources.aws_instance`. This would give you a map of resource
+ namespaces indexed from the names of each resource (`foo`, `bar`, and so
+ on).
+* To fetch all resources within the root module, irrespective of type, use
+ `tfconfig.resources`. This is indexed by type, as shown above with
+ `tfconfig.resources.aws_instance`, with names being the next level down.
+
+As an example, perhaps you wish to deny use of the `local_file` resource
+in your configuration. Consider the following resource block:
+
+```hcl
+resource "local_file" "foo" {
+ content = "foo!"
+ filename = "${path.module}/foo.bar"
+}
+```
+
+The following policy would fail:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.resources not contains "local_file" }
+```
+
+Further explanation of the namespace will be in the context of resources. As
+mentioned, when operating on data sources, use the same syntax, except with
+`data` in place of `resources`.
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+The `config` value within the [resource
+namespace](#namespace-resources-data-sources) is a map of key-value pairs that
+directly map to Terraform config keys and values.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#resources-value-references) should be used instead.
+
+As an example, consider the following resource block:
+
+```hcl
+resource "local_file" "accounts" {
+ content = "some text"
+ filename = "accounts.txt"
+}
+```
+
+In this example, one might want to access `filename` to validate that the correct
+file name is used. Given the above example, the following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule {
+ tfconfig.resources.local_file.accounts.config.filename is "accounts.txt"
+}
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [resource namespace](#namespace-resources-data-sources)
+contains the identifiers within non-constant expressions found in [`config`](#resources-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+
+
+### Value: `provisioners`
+
+* **Value Type:** List of [provisioner namespaces](#namespace-provisioners).
+
+The `provisioners` value within the [resource namespace](#namespace-resources)
+represents the [provisioners][ref-tf-provisioners] within a specific resource.
+
+Provisioners are listed in the order they were provided in the configuration
+file.
+
+While the `provisioners` value will be present within data sources, it will
+always be an empty map `null` (in Terraform 0.12) since data sources cannot
+actually have provisioners.
+
+The data within a provisioner can be inspected via the returned [provisioner
+namespace](#namespace-provisioners).
+
+[ref-tf-provisioners]: https://developer.hashicorp.com/terraform/language/resources/provisioners/syntax
+
+## Namespace: Provisioners
+
+The **provisioner namespace** represents the configuration for a particular
+[provisioner][ref-tf-provisioners] within a specific resource.
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+The `config` value within the [provisioner namespace](#namespace-provisioners)
+represents the values of the keys within the provisioner.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#provisioners-value-references) should be used instead.
+
+As an example, given the following resource block:
+
+```hcl
+resource "null_resource" "foo" {
+ # ...
+
+ provisioner "local-exec" {
+ command = "echo ${self.private_ip} > file.txt"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule {
+ tfconfig.resources.null_resource.foo.provisioners[0].config.command is "echo ${self.private_ip} > file.txt"
+}
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [provisioner namespace](#namespace-provisioners)
+contains the identifiers within non-constant expressions found in [`config`](#provisioners-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+
+
+### Value: `type`
+
+* **Value Type:** String.
+
+The `type` value within the [provisioner namespace](#namespace-provisioners)
+represents the type of the specific provisioner.
+
+As an example, in the following resource block:
+
+```hcl
+resource "null_resource" "foo" {
+ # ...
+
+ provisioner "local-exec" {
+ command = "echo ${self.private_ip} > file.txt"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.resources.null_resource.foo.provisioners[0].type is "local-exec" }
+```
+
+## Namespace: Module Configuration
+
+The **module configuration** namespace displays data on _module configuration_
+as it is given within a `module` block. This means that the namespace concerns
+itself with the contents of the declaration block (example: the `source`
+parameter and variable assignment keys), not the data within the module
+(example: any contained resources or data sources). For the latter, the module
+instance would need to be looked up with the [`module()`
+function](#root-function-module).
+
+
+
+### Value: `source`
+
+* **Value Type:** String.
+
+The `source` value within the [module configuration
+namespace](#namespace-module-configuration) represents the module source path as
+supplied to the module configuration.
+
+As an example, given the module declaration block:
+
+```hcl
+module "foo" {
+ source = "./foo"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.modules.foo.source is "./foo" }
+```
+
+
+
+### Value: `version`
+
+* **Value Type:** String.
+
+The `version` value within the [module configuration
+namespace](#namespace-module-configuration) represents the [version
+constraint][module-version-constraint] for modules that support it, such as
+modules within the [Terraform Module Registry][terraform-module-registry] or the
+[HCP Terraform private module registry][tfe-private-registry].
+
+[module-version-constraint]: https://developer.hashicorp.com/terraform/language/modules#module-versions
+
+[terraform-module-registry]: https://registry.terraform.io/
+
+[tfe-private-registry]: https://developer.hashicorp.com/terraform/cloud-docs/registry
+
+As an example, given the module declaration block:
+
+```hcl
+module "foo" {
+ source = "foo/bar"
+ version = "~> 1.2"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.modules.foo.version is "~> 1.2" }
+```
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#modules-value-references) should be used instead.
+
+The `config` value within the [module configuration
+namespace](#namespace-module-configuration) represents the values of the keys
+within the module configuration. This is every key within a module declaration
+block except [`source`](#modules-value-source) and [`version`](#modules-value-version), which
+have their own values.
+
+As an example, given the module declaration block:
+
+```hcl
+module "foo" {
+ source = "./foo"
+
+ bar = "baz"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.modules.foo.config.bar is "baz" }
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [module configuration namespace](#namespace-module-configuration)
+contains the identifiers within non-constant expressions found in [`config`](#modules-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+## Namespace: Outputs
+
+The **output namespace** represents _declared_ output data within a
+configuration. As such, configuration for the [`value`](#outputs-value-value) attribute
+will be in its raw form, and not yet interpolated. For fully interpolated output
+values, see the [`tfstate` import][ref-tfe-sentinel-tfstate].
+
+[ref-tfe-sentinel-tfstate]: /sentinel/features/terraform/tfstate-v1
+
+This namespace is indexed by output name.
+
+
+
+### Value: `depends_on`
+
+* **Value Type:** A list of strings.
+
+The `depends_on` value within the [output namespace](#namespace-outputs)
+represents any _explicit_ dependencies for this output. For more information,
+see the [depends_on output setting][ref-depends_on] within the general Terraform
+documentation.
+
+[ref-depends_on]: https://developer.hashicorp.com/terraform/language/values/outputs#depends_on
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ depends_on = ["null_resource.bar"]
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.depends_on[0] is "null_resource.bar" }
+```
+
+
+
+### Value: `description`
+
+* **Value Type:** String.
+
+The `description` value within the [output namespace](#namespace-outputs)
+represents the defined description for this output.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ description = "foobar"
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.description is "foobar" }
+```
+
+
+
+### Value: `sensitive`
+
+* **Value Type:** Boolean.
+
+The `sensitive` value within the [output namespace](#namespace-outputs)
+represents if this value has been marked as sensitive or not.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ sensitive = true
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { subject.outputs.id.sensitive }
+```
+
+
+
+### Value: `value`
+
+* **Value Type:** Any primitive type, list or map.
+
+The `value` value within the [output namespace](#namespace-outputs) represents
+the defined value for the output as declared in the configuration. Primitives
+will bear the implicit type of their declaration (string, int, float, or bool),
+and maps and lists will be represented as such.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#outputs-value-references) should be used instead.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ value = "foo"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.value is "foo" }
+```
+
+
+
+### Value: `references`
+
+* **Value Type:**. List of strings.
+
+The `references` value within the [output namespace](#namespace-outputs)
+contains the names of any referenced identifiers when [`value`](#outputs-value-value)
+is a non-constant expression.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.references contains "null_resource.foo.id" }
+```
+
+## Namespace: Providers
+
+The **provider namespace** represents data on the declared providers within a
+namespace.
+
+This namespace is indexed by provider type and _only_ contains data about
+providers when actually declared. If you are using a completely implicit
+provider configuration, this namespace will be empty.
+
+This namespace is populated based on the following criteria:
+
+* The top-level namespace [`config`](#providers-value-config) and
+ [`version`](#providers-value-version) values are populated with the configuration and
+ version information from the default provider (the provider declaration that
+ lacks an alias).
+* Any aliased providers are added as namespaces within the
+ [`alias`](#providers-value-alias) value.
+* If a module lacks a default provider configuration, the top-level `config` and
+ `version` values will be empty.
+
+
+
+### Value: `alias`
+
+* **Value Type:** A map of [provider namespaces](#namespace-providers), indexed
+ by alias.
+
+The `alias` value within the [provider namespace](#namespace-providers)
+represents all declared [non-default provider
+instances][ref-tf-provider-instances] for a specific provider type, indexed by
+their specific alias.
+
+[ref-tf-provider-instances]: https://developer.hashicorp.com/terraform/language/providers/configuration#alias-multiple-provider-configurations
+
+The return type is a provider namespace with the data for the instance in
+question loaded. The `alias` key will not be available within this namespace.
+
+As an example, given the following provider declaration block:
+
+```hcl
+provider "aws" {
+ alias = "east"
+ region = "us-east-1"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.providers.aws.alias.east.config.region is "us-east-1" }
+```
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#providers-value-references) should be used instead.
+
+The `config` value within the [provider namespace](#namespace-providers)
+represents the values of the keys within the provider's configuration, with the
+exception of the provider version, which is represented by the
+[`version`](#providers-value-version) value. [`alias`](#providers-value-alias) is also not included
+when the provider is aliased.
+
+As an example, given the following provider declaration block:
+
+```hcl
+provider "aws" {
+ region = "us-east-1"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.providers.aws.config.region is "us-east-1" }
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [provider namespace](#namespace-providers)
+contains the identifiers within non-constant expressions found in [`config`](#providers-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+
+
+### Value: `version`
+
+* **Value Type:** String.
+
+The `version` value within the [provider namespace](#namespace-providers)
+represents the explicit expected version of the supplied provider. This includes
+the pessimistic operator.
+
+As an example, given the following provider declaration block:
+
+```hcl
+provider "aws" {
+ version = "~> 1.34"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.providers.aws.version is "~> 1.34" }
+```
+
+## Namespace: Variables
+
+The **variable namespace** represents _declared_ variable data within a
+configuration. As such, static data can be extracted, such as defaults, but not
+dynamic data, such as the current value of a variable within a plan (although
+this can be extracted within the [`tfplan` import][ref-tfe-sentinel-tfplan]).
+
+[ref-tfe-sentinel-tfplan]: /sentinel/features/terraform/tfplan-v1
+
+This namespace is indexed by variable name.
+
+
+
+### Value: `default`
+
+* **Value Type:** Any primitive type, list, map, or `null`.
+
+The `default` value within the [variable namespace](#namespace-variables)
+represents the default for the variable as declared in the configuration.
+
+The actual value will be as configured. Primitives will bear the implicit type
+of their declaration (string, int, float, or bool), and maps and lists will be
+represented as such.
+
+If no default is present, the value will be [`null`][ref-sentinel-null] (not to
+be confused with [`undefined`][ref-sentinel-undefined]).
+
+[ref-sentinel-null]: /sentinel/language/spec#null
+
+[ref-sentinel-undefined]: /sentinel/language/undefined
+
+As an example, given the following variable blocks:
+
+```hcl
+variable "foo" {
+ default = "bar"
+}
+
+variable "number" {
+ default = 42
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+default_foo = rule { tfconfig.variables.foo.default is "bar" }
+default_number = rule { tfconfig.variables.number.default is 42 }
+
+main = rule { default_foo and default_number }
+```
+
+
+
+### Value: `description`
+
+* **Value Type:** String.
+
+The `description` value within the [variable namespace](#namespace-variables)
+represents the description of the variable, as provided in configuration.
+
+As an example, given the following variable block:
+
+```hcl
+variable "foo" {
+ description = "foobar"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.variables.foo.description is "foobar" }
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/features/terraform/tfconfig-v2.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/features/terraform/tfconfig-v2.mdx
new file mode 100644
index 0000000000..1fe4fff092
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/features/terraform/tfconfig-v2.mdx
@@ -0,0 +1,441 @@
+---
+page_title: tfconfig/v2 - terraform - Features
+sidebar_current: docs-features-terraform-tfconfig-v2
+description: The tfconfig/v2 import provides access to a Terraform configuration.
+layout: docs
+---
+
+# Import: tfconfig/v2
+
+The `tfconfig/v2` import provides access to a Terraform configuration.
+
+The Terraform configuration is the set of `*.tf` files that are used to
+describe the desired infrastructure state. Policies using the `tfconfig`
+import can access all aspects of the configuration: providers, resources,
+data sources, modules, and variables.
+
+Some use cases for `tfconfig` include:
+
+* **Organizational naming conventions**: requiring that configuration elements
+ are named in a way that conforms to some organization-wide standard.
+* **Required inputs and outputs**: organizations may require a particular set
+ of input variable names across all workspaces or may require a particular
+ set of outputs for asset management purposes.
+* **Enforcing particular modules**: organizations may provide a number of
+ "building block" modules and require that each workspace be built only from
+ combinations of these modules.
+* **Enforcing particular providers or resources**: an organization may wish to
+ require or prevent the use of providers and/or resources so that configuration
+ authors cannot use alternative approaches to work around policy
+ restrictions.
+
+The data in the `tfconfig/v2` import is sourced from the JSON configuration file
+that is generated by the [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) command. For more information on
+the file format, see the [JSON Output Format](https://developer.hashicorp.com/terraform/internals/json-format)
+page.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfconfig/v2" {
+ config = {
+ "plan": "./path/to/plan.json"
+ }
+}
+```
+
+## Import Overview
+
+The `tfconfig/v2` import is structured as a series of _collections_, keyed as a
+specific format, such as resource address, module address, or a
+specifically-formatted provider key.
+
+```
+tfconfig/v2
+├── strip_index() (function)
+├── providers
+│ └── (indexed by [module_address:]provider[.alias])
+│ ├── provider_config_key (string)
+│ ├── name (string)
+│ ├── full_name (string)
+│ ├── alias (string)
+│ ├── module_address (string)
+│ ├── config (block expression representation)
+│ └── version_constraint (string)
+├── resources
+│ └── (indexed by address)
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── provider_config_key (string)
+│ ├── provisioners (list)
+│ │ └── (ordered provisioners for this resource only)
+│ ├── config (block expression representation)
+│ ├── count (expression representation)
+│ ├── for_each (expression representation)
+│ └── depends_on (list of strings)
+├── provisioners
+│ └── (indexed by resource_address:index)
+│ ├── resource_address (string)
+│ ├── type (string)
+│ ├── index (string)
+│ └── config (block expression representation)
+├── variables
+│ └── (indexed by module_address:name)
+│ ├── module_address (string)
+│ ├── name (string)
+│ ├── default (value)
+│ └── description (string)
+├── outputs
+│ └── (indexed by module_address:name)
+│ ├── module_address (string)
+│ ├── name (string)
+│ ├── sensitive (boolean)
+│ ├── value (expression representation)
+│ ├── description (string)
+│ └── depends_on (list of strings)
+└── module_calls
+ └── (indexed by module_address:name)
+ ├── module_address (string)
+ ├── name (string)
+ ├── source (string)
+ ├── config (block expression representation)
+ ├── count (expression representation)
+ ├── depends_on (expression representation)
+ ├── for_each (expression representation)
+ └── version_constraint (string)
+```
+
+The collections are:
+
+* [`providers`](#the-providers-collection) - The configuration for all provider
+ instances across all modules in the configuration.
+* [`resources`](#the-resources-collection) - The configuration of all resources
+ across all modules in the configuration.
+* [`variables`](#the-variables-collection) - The configuration of all variable
+ definitions across all modules in the configuration.
+* [`outputs`](#the-outputs-collection) - The configuration of all output
+ definitions across all modules in the configuration.
+* [`module_calls`](#the-module_calls-collection) - The configuration of all module
+ calls (individual [`module`](/terraform/language/modules) blocks) across
+ all modules in the configuration.
+
+These collections are specifically designed to be used with the
+[`filter`](/sentinel/language/collection-operations#filter-expression)
+quantifier expression in Sentinel, so that one can collect a list of resources
+to perform policy checks on without having to write complex module or
+configuration traversal. As an example, the following code will return all
+`aws_instance` resource types within the configuration, regardless of what
+module they are in:
+
+```
+all_aws_instances = filter tfconfig.resources as _, r {
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+You can add specific attributes to the filter to narrow the search, such as the
+module address. The following code would return resources in a module named
+`foo` only:
+
+```
+all_aws_instances = filter tfconfig.resources as _, r {
+ r.module_address is "module.foo" and
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+### Address Differences Between `tfconfig`, `tfplan`, and `tfstate`
+
+This import deals with configuration before it is expanded into a
+resource graph by Terraform. As such, it is not possible to compute an index as
+the import is building its collections and computing addresses for resources and
+modules.
+
+As such, addresses found here may not always match the expanded addresses found
+in the [`tfplan/v2`](/sentinel/features/terraform/tfplan-v2) and [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2)
+imports, specifically when
+[`count`](https://developer.hashicorp.com/terraform/language/resources#count-multiple-resource-instances-by-count)
+and
+[`for_each`](https://developer.hashicorp.com/terraform/language/resources#for_each-multiple-resource-instances-defined-by-a-map-or-set-of-strings),
+are used.
+
+As an example, consider a resource named `null_resource.foo` with a count of `2`
+located in a module named `bar`. While there will possibly be entries in the
+other imports for `module.bar.null_resource.foo[0]` and
+`module.bar.null_resource.foo[1]`, in `tfconfig/v2`, there will only be a
+`module.bar.null_resource.foo`. As mentioned in the start of this section, this
+is because configuration actually _defines_ this scaling, whereas _expansion_
+actually happens when the resource graph is built, which happens as a natural
+part of the refresh and planning process.
+
+The `strip_index` helper function, found in this import, can assist in
+removing the indexes from addresses found in the `tfplan/v2` and `tfstate/v2`
+imports so that data from those imports can be used to reference data in this
+one.
+
+## The `strip_index` Function
+
+The `strip_index` helper function can be used to remove indexes from addresses
+found in [`tfplan/v2`](/sentinel/features/terraform/tfplan-v2) and [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2),
+by removing the indexes from each resource.
+
+This can be used to help facilitate cross-import lookups for data between plan,
+state, and config.
+
+```
+import "tfconfig/v2" as tfconfig
+import "tfplan/v2" as tfplan
+
+main = rule {
+ all filter tfplan.resource_changes as _, rc {
+ rc.mode is "managed" and
+ rc.type is "aws_instance"
+ } as _, rc {
+ tfconfig.resources[tfconfig.strip_index(rc.address)].config.ami.constant_value is "ami-abcdefgh012345"
+ }
+}
+```
+
+## Expression Representations
+
+Most collections in this import will have one of two kinds of _expression
+representations_. This is a verbose format for expressing a (parsed)
+configuration value independent of the configuration source code, which is not
+100% available to a policy check in HCP Terraform.
+
+```
+(expression representation)
+├── constant_value (value)
+└── references (list of strings)
+```
+
+There are two major parts to an expression representation:
+
+* Any _strictly constant value_ is expressed as an expression with a
+ `constant_value` field.
+* Any expression that requires some degree of evaluation to generate the final
+ value - even if that value is known at plan time - is not expressed in
+ configuration. Instead, any particular references that are made are added to
+ the `references` field. More details on this field can be found in the
+ [expression
+ representation](https://developer.hashicorp.com/terraform/internals/json-format#expression-representation)
+ section of the JSON output format documentation.
+
+For example, to determine if an output is based on a particular
+resource value, one could do:
+
+```
+import "tfconfig/v2" as tfconfig
+
+main = rule {
+ tfconfig.outputs["instance_id"].value.references is ["aws_instance.foo"]
+}
+```
+
+-> **Note:** The representation does not account for
+complex interpolations or other expressions that combine constants with other
+expression data. For example, the partially constant data in `"foo${var.bar}"` would be lost.
+
+### Block Expression Representation
+
+Expanding on the above, a multi-value expression representation (such as the
+kind found in a [`resources`](#the-resources-collection) collection element) is
+similar, but the root value is a keyed map of expression representations. This
+is repeated until a "scalar" expression value is encountered, ie: a field that
+is not a block in the resource's schema.
+
+```
+(block expression representation)
+└── (attribute key)
+ ├── (child block expression representation)
+ │ └── (...)
+ ├── constant_value (value)
+ └── references (list of strings)
+```
+
+As an example, one can validate expressions in an
+[`aws_instance`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance) resource using the
+following:
+
+```
+import "tfconfig/v2" as tfconfig
+
+main = rule {
+ tfconfig.resources["aws_instance.foo"].config.ami.constant_value is "ami-abcdefgh012345"
+}
+```
+
+Note that _nested blocks_, sometimes known as _sub-resources_, will be nested in
+configuration as as list of blocks (reflecting their ultimate nature as a list
+of objects). An example would be the `aws_instance` resource's
+[`ebs_block_device`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#ebs-ephemeral-and-root-block-devices) block:
+
+```
+import "tfconfig/v2" as tfconfig
+
+main = rule {
+ tfconfig.resources["aws_instance.foo"].config.ebs_block_device[0].volume_size < 10
+}
+```
+
+## The `providers` Collection
+
+The `providers` collection is a collection representing the configurations of
+all provider instances across all modules in the configuration.
+
+This collection is indexed by an opaque key. This is currently
+`module_address:provider.alias`, the same value as found in the
+`provider_config_key` field. `module_address` and the colon delimiter are
+omitted for the root module.
+
+The `provider_config_key` field is also found in the `resources` collection and
+can be used to locate a provider that belongs to a configured resource.
+
+The fields in this collection are as follows:
+
+* `provider_config_key` - The opaque configuration key, used as the index key.
+* `name` - The name of the provider, ie: `aws`.
+* `full_name` - The fully-qualified name of the provider, e.g. `registry.terraform.io/hashicorp/aws`.
+* `alias` - The alias of the provider, ie: `east`. Empty for a default provider.
+* `module_address` - The address of the module this provider appears in.
+* `config` - A [block expression
+ representation](#block-expression-representation) with provider configuration
+ values.
+* `version_constraint` - The defined version constraint for this provider.
+
+## The `resources` Collection
+
+The `resources` collection is a collection representing all of the resources
+found in all modules in the configuration.
+
+This collection is indexed by the resource address.
+
+The fields in this collection are as follows:
+
+* `address` - The resource address. This is the index of the collection.
+* `module_address` - The module address that this resource was found in.
+* `mode` - The resource mode, either `managed` (resources) or `data` (data
+ sources).
+* `type` - The type of resource, ie: `null_resource` in `null_resource.foo`.
+* `name` - The name of the resource, ie: `foo` in `null_resource.foo`.
+* `provider_config_key` - The opaque configuration key that serves as the index
+ of the [`providers`](#the-providers-collection) collection.
+* `provisioners` - The ordered list of provisioners for this resource. The
+ syntax of the provisioners matches those found in the
+ [`provisioners`](#the-provisioners-collection) collection, but is a list
+ indexed by the order the provisioners show up in the resource.
+* `config` - The [block expression
+ representation](#block-expression-representation) of the configuration values
+ found in the resource.
+* `count` - The [expression data](#expression-representations) for the `count`
+ value in the resource.
+* `for_each` - The [expression data](#expression-representations) for the
+ `for_each` value in the resource.
+* `depends_on` - The contents of the `depends_on` config directive, which
+ declares explicit dependencies for this resource.
+
+## The `provisioners` Collection
+
+The `provisioners` collection is a collection of all of the provisioners found
+across all resources in the configuration.
+
+While normally bound to a resource in an ordered fashion, this collection allows
+for the filtering of provisioners within a single expression.
+
+This collection is indexed with a key following the format
+`resource_address:index`, with each field matching their respective field in the
+particular element below:
+
+* `resource_address`: The address of the resource that the provisioner was found
+ in. This can be found in the [`resources`](#the-resources-collection)
+ collection.
+* `type`: The provisioner type, ie: `local_exec`.
+* `index`: The provisioner index as it shows up in the resource provisioner
+ order.
+* `config`: The [block expression
+ representation](#block-expression-representation) of the configuration values
+ in the provisioner.
+
+## The `variables` Collection
+
+The `variables` collection is a collection of all variables across all modules
+in the configuration.
+
+Note that this tracks variable definitions, not values. See the [`tfplan/v2`
+`variables` collection](/sentinel/features/terraform/tfplan-v2#the-variables-collection) for variable
+values set within a plan.
+
+This collection is indexed by the key format `module_address:name`, with each
+field matching their respective name below. `module_address` and the colon
+delimiter are omitted for the root module.
+
+* `module_address` - The address of the module the variable was found in.
+* `name` - The name of the variable.
+* `default` - The defined default value of the variable.
+* `description` - The description of the variable.
+
+## The `outputs` Collection
+
+The `outputs` collection is a collection of all outputs across all modules in
+the configuration.
+
+Note that this tracks variable definitions, not values. See the [`tfstate/v2`
+`outputs` collection](/sentinel/features/terraform/tfstate-v2#the-outputs-collection) for the final
+values of outputs set within a state. The [`tfplan/v2` `output_changes`
+collection](/sentinel/features/terraform/tfplan-v2#the-output_changes-collection) also contains a more
+complex collection of planned output changes.
+
+This collection is indexed by the key format `module_address:name`, with each
+field matching their respective name below. `module_address` and the colon
+delimiter are omitted for the root module.
+
+* `module_address` - The address of the module the output was found in.
+* `name` - The name of the output.
+* `sensitive` - Indicates whether or not the output was marked as
+ [`sensitive`](https://developer.hashicorp.com/terraform/language/values/outputs#sensitive-suppressing-values-in-cli-output).
+* `value` - An [expression representation](#expression-representations) for the output.
+* `description` - The description of the output.
+* `depends_on` - A list of resource names that the output depends on. These are
+ the hard-defined output dependencies as defined in the
+ [`depends_on`](https://developer.hashicorp.com/terraform/language/values/outputs#depends_on-explicit-output-dependencies)
+ field in an output declaration, not the dependencies that get derived from
+ natural evaluation of the output expression (these can be found in the
+ `references` field of the expression representation).
+
+## The `module_calls` Collection
+
+The `module_calls` collection is a collection of all module declarations at all
+levels within the configuration.
+
+Note that this is the
+[`module`](https://developer.hashicorp.com/terraform/language/modules#calling-a-child-module) stanza in
+any particular configuration, and not the module itself. Hence, a declaration
+for `module.foo` would actually be declared in the root module, which would be
+represented by a blank field in `module_address`.
+
+This collection is indexed by the key format `module_address:name`, with each
+field matching their respective name below. `module_address` and the colon
+delimiter are omitted for the root module.
+
+* `module_address` - The address of the module the declaration was found in.
+* `name` - The name of the module.
+* `source` - The contents of the `source` field.
+* `config` - A [block expression
+ representation](#block-expression-representation) for all parameter values
+ sent to the module.
+* `count` - An [expression representation](#expression-representations) for the
+ `count` field.
+* `depends_on`: An [expression representation](#expression-representations) for the
+ `depends_on` field.
+* `for_each` - An [expression representation](#expression-representations) for
+ the `for_each` field.
+* `version_constraint` - The string value found in the `version` field of the
+ module declaration.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/features/terraform/tfplan-v1.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/features/terraform/tfplan-v1.mdx
new file mode 100644
index 0000000000..1e9f9f1f9b
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/features/terraform/tfplan-v1.mdx
@@ -0,0 +1,614 @@
+---
+page_title: tfplan/v1 - terraform - Features
+sidebar_current: docs-features-terraform-tfplan-v1
+description: >-
+ The tfplan/v1 import provides access to a Terraform plan. A Terraform plan is the
+ file created as a result of `terraform plan` and is the input to `terraform
+ apply`. The plan represents the changes that Terraform needs to make to
+ infrastructure to reach the desired state represented by the configuration.
+layout: docs
+---
+
+# Import: tfplan/v1
+
+~> **Warning:** The `tfplan/v1` import is deprecated and will be permanently removed in August 2025.
+Use the updated [tfplan/v2](/sentinel/docs/features/terraform/tfplan-v2) import as soon as possible to avoid disruptions.
+The `tfplan/v2` import offers improved functionality and is designed to better support your policy enforcement needs.
+
+The `tfplan/v1` import provides access to a Terraform plan. A Terraform plan is the
+file created as a result of `terraform plan` and is the input to `terraform
+apply`. The plan represents the changes that Terraform needs to make to
+infrastructure to reach the desired state represented by the configuration.
+
+In addition to the diff data available in the plan, there is an
+[`applied`](#value-applied) state available that merges the plan with the state
+to create the planned state after apply.
+
+Finally, this import also allows you to access the configuration files and the
+Terraform state at the time the plan was run. See the section on [accessing a
+plan's state and configuration
+data](#accessing-a-plan-39-s-state-and-configuration-data) for more information.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan and schemas file.
+
+```hcl
+import "plugin" "tfplan/v1" {
+ config = {
+ "plan_path": "./path/to/plan.json",
+ "schemas_path": "./path/to/schemas.json"
+ }
+}
+```
+
+## Namespace Overview
+
+The following is a tree view of the import namespace. For more detail on a
+particular part of the namespace, see below.
+
+-> Note that the root-level alias keys shown here (`data`, `path`, and
+`resources`) are shortcuts to a [module namespace](#namespace-module) scoped to
+the root module. For more details, see the section on [root namespace
+aliases](#root-namespace-aliases).
+
+```
+tfplan/v1
+├── module() (function)
+│ └── (module namespace)
+│ ├── path ([]string)
+│ ├── data
+│ │ └── TYPE.NAME[NUMBER]
+│ │ ├── applied (map of keys)
+│ │ └── diff
+│ │ └── KEY
+│ │ ├── computed (bool)
+│ │ ├── new (string)
+│ │ └── old (string)
+│ └── resources
+│ └── TYPE.NAME[NUMBER]
+│ ├── applied (map of keys)
+│ ├── destroy (bool)
+│ ├── requires_new (bool)
+│ └── diff
+│ └── KEY
+│ ├── computed (bool)
+│ ├── new (string)
+│ └── old (string)
+├── module_paths ([][]string)
+├── terraform_version (string)
+├── variables (map of keys)
+│
+├── data (root module alias)
+├── path (root module alias)
+├── resources (root module alias)
+│
+├── config (tfconfig namespace alias)
+└── state (tfstate import alias)
+```
+
+## Namespace: Root
+
+The root-level namespace consists of the values and functions documented below.
+
+In addition to this, the root-level `data`, `path`, and `resources` keys alias
+to their corresponding namespaces or values within the [module
+namespace](#namespace-module).
+
+### Accessing a Plan's State and Configuration Data
+
+The `config` and `state` keys alias to the [`tfconfig`](/sentinel/features/terraform/tfconfig-v1) and
+[`tfstate`](/sentinel/features/terraform/tfstate-v1) namespaces, respectively, with the data sourced from
+the Terraform _plan_ (as opposed to actual configuration and state).
+
+-> Note that these aliases are not represented as maps. While they will appear
+empty when viewed as maps, the specific import namespace keys will still be
+accessible.
+
+### Function: `module()`
+
+```
+module = func(ADDR)
+```
+
+* **Return Type:** A [module namespace](#namespace-module).
+
+The `module()` function in the [root namespace](#namespace-root) returns the
+[module namespace](#namespace-module) for a particular module address.
+
+The address must be a list and is the module address, split on the period (`.`),
+excluding the root module.
+
+Hence, a module with an address of simply `foo` (or `root.foo`) would be
+`["foo"]`, and a module within that (so address `foo.bar`) would be read as
+`["foo", "bar"]`.
+
+[`null`](/sentinel/language/spec#null) is returned if a module address is
+invalid, or if the module is not present in the diff.
+
+As an example, given the following module block:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+If the module contained the following content:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.module(["foo"]).resources.null_resource.foo[0].applied.triggers.foo is "bar" }
+```
+
+### Value: `module_paths`
+
+* **Value Type:** List of a list of strings.
+
+The `module_paths` value within the [root namespace](#namespace-root) is a list
+of all of the modules within the Terraform diff for the current plan.
+
+Modules not present in the diff will not be present here, even if they are
+present in the configuration or state.
+
+This data is represented as a list of a list of strings, with the inner list
+being the module address, split on the period (`.`).
+
+The root module is included in this list, represented as an empty inner list, as
+long as there are changes.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The value of `module_paths` would be:
+
+```
+[
+ [],
+ ["foo"],
+]
+```
+
+And the following policy would evaluate to `true`:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.module_paths contains ["foo"] }
+```
+
+-> Note the above example only applies if the module is present in the diff.
+
+#### Iterating Through Modules
+
+Iterating through all modules to find particular resources can be useful. This
+[example][iterate-over-modules] shows how to use `module_paths` with the
+[`module()` function](#function-module-) to find all resources of a
+particular type from all modules that have pending changes using the `tfplan/v1`
+import.
+
+[iterate-over-modules]: https://developer.hashicorp.com/terraform/cloud-docs/policy-enforcement/sentinel#iterate-over-modules-and-find-resources
+
+### Value: `terraform_version`
+
+* **Value Type:** String.
+
+The `terraform_version` value within the [root namespace](#namespace-root)
+represents the version of Terraform used to create the plan. This can be used to
+enforce a specific version of Terraform in a policy check.
+
+As an example, the following policy would evaluate to `true`, as long as the
+plan was made with a version of Terraform in the 0.11.x series, excluding any
+pre-release versions (example: `-beta1` or `-rc1`):
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.terraform_version matches "^0\\.11\\.\\d+$" }
+```
+
+### Value: `variables`
+
+* **Value Type:** A string-keyed map of values.
+
+The `variables` value within the [root namespace](#namespace-root) represents
+all of the variables that were set when creating the plan. This will only
+contain variables set for the root module.
+
+Note that unlike the [`default`][import-tfconfig-variables-default] value in the
+[`tfconfig` variables namespace][import-tfconfig-variables], primitive values
+here are stringified, and type conversion will need to be performed to perform
+comparison for int, float, or boolean values. This only applies to variables
+that are primitives themselves and not primitives within maps and lists, which
+will be their original types.
+
+[import-tfconfig-variables-default]: /sentinel/features/terraform/tfconfig-v1#value-default
+
+[import-tfconfig-variables]: /sentinel/features/terraform/tfconfig-v1#namespace-variables
+
+If a default was accepted for the particular variable, the default value will be
+populated here.
+
+As an example, given the following variable blocks:
+
+```hcl
+variable "foo" {
+ default = "bar"
+}
+
+variable "number" {
+ default = 42
+}
+
+variable "map" {
+ default = {
+ foo = "bar"
+ number = 42
+ }
+}
+```
+
+The following policy would evaluate to `true`, if no values were entered to
+change these variables:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+default_foo = rule { tfplan.variables.foo is "bar" }
+default_number = rule { tfplan.variables.number is "42" }
+default_map_string = rule { tfplan.variables.map["foo"] is "bar" }
+default_map_int = rule { tfplan.variables.map["number"] is 42 }
+
+main = rule { default_foo and default_number and default_map_string and default_map_int }
+```
+
+## Namespace: Module
+
+The **module namespace** can be loaded by calling
+[`module()`](#function-module-) for a particular module.
+
+It can be used to load the following child namespaces, in addition to the values
+documented below:
+
+* `data` - Loads the [resource namespace](#namespace-resources-data-sources),
+ filtered against data sources.
+* `resources` - Loads the [resource
+ namespace](#namespace-resources-data-sources), filtered against resources.
+
+### Root Namespace Aliases
+
+The root-level `data` and `resources` keys both alias to their corresponding
+namespaces within the module namespace, loaded for the root module. They are the
+equivalent of running `module([]).KEY`.
+
+### Value: `path`
+
+* **Value Type:** List of strings.
+
+The `path` value within the [module namespace](#namespace-module) contains the
+path of the module that the namespace represents. This is represented as a list
+of strings.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The following policy would evaluate to `true` _only_ if the diff had changes for
+that module:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.module(["foo"]).path contains "foo" }
+```
+
+## Namespace: Resources/Data Sources
+
+The **resource namespace** is a namespace _type_ that applies to both resources
+(accessed by using the `resources` namespace key) and data sources (accessed
+using the `data` namespace key).
+
+Accessing an individual resource or data source within each respective namespace
+can be accomplished by specifying the type, name, and resource number (as if the
+resource or data source had a `count` value in it) in the syntax
+`[resources|data].TYPE.NAME[NUMBER]`. Note that NUMBER is always needed, even if
+you did not use `count` in the resource.
+
+In addition, each of these namespace levels is a map, allowing you to filter
+based on type and name.
+
+-> The (somewhat strange) notation here of `TYPE.NAME[NUMBER]` may imply that
+the inner resource index map is actually a list, but it's not - using the square
+bracket notation over the dotted notation (`TYPE.NAME.NUMBER`) is required here
+as an identifier cannot start with a number.
+
+Some examples of multi-level access are below:
+
+* To fetch all `aws_instance.foo` resource instances within the root module, you
+ can specify `tfplan.resources.aws_instance.foo`. This would then be indexed by
+ resource count index (`0`, `1`, `2`, and so on). Note that as mentioned above,
+ these elements must be accessed using square-bracket map notation (so `[0]`,
+ `[1]`, `[2]`, and so on) instead of dotted notation.
+* To fetch all `aws_instance` resources within the root module, you can specify
+ `tfplan.resources.aws_instance`. This would be indexed from the names of
+ each resource (`foo`, `bar`, and so on), with each of those maps containing
+ instances indexed by resource count index as per above.
+* To fetch all resources within the root module, irrespective of type, use
+ `tfplan.resources`. This is indexed by type, as shown above with
+ `tfplan.resources.aws_instance`, with names being the next level down, and so
+ on.
+
+~> When [resource targeting](https://developer.hashicorp.com/terraform/cli/commands/plan#resource-targeting) is
+ in effect, `tfplan.resources` will only include the resources specified as
+ targets for the run. This may lead to unexpected outcomes if a policy expects
+ a resource to be present in the plan.
+
+Further explanation of the namespace will be in the context of resources. As
+mentioned, when operating on data sources, use the same syntax, except with
+`data` in place of `resources`.
+
+### Value: `applied`
+
+* **Value Type:** A string-keyed map of values.
+
+The `applied` value within the [resource
+namespace](#namespace-resources-data-sources) contains a "predicted"
+representation of the resource's state post-apply. It's created by merging the
+pending resource's diff on top of the existing data from the resource's state
+(if any). The map is a complex representation of these values with data going
+as far down as needed to represent any state values such as maps, lists, and
+sets.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if the resource was in the diff:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].applied.triggers.foo is "bar" }
+```
+
+-> Note that some values will not be available in the `applied` state because
+they cannot be known until the plan is actually applied. In Terraform 0.11 or
+earlier, these values are represented by a placeholder (the UUID value
+`74D93920-ED26-11E3-AC10-0800200C9A66`) and in Terraform 0.12 or later they
+are `undefined`. **In either case**, you should instead use the
+[`computed`](#value-computed) key within the [diff
+namespace](#namespace-resource-diff) to determine that a computed value will
+exist.
+
+-> If a resource is being destroyed, its `applied` value is omitted from the
+namespace and trying to fetch it will return undefined.
+
+### Value: `diff`
+
+* **Value Type:** A map of [diff namespaces](#namespace-resource-diff).
+
+The `diff` value within the [resource
+namespace](#namespace-resources-data-sources) contains the diff for a particular
+resource. Each key within the map links to a [diff
+namespace](#namespace-resource-diff) for that particular key.
+
+Note that unlike the [`applied`](#value-applied) value, this map is not complex;
+the map is only 1 level deep with each key possibly representing a diff for a
+particular complex value within the resource.
+
+See the below section for more details on the diff namespace, in addition to
+usage examples.
+
+### Value: `destroy`
+
+* **Value Type:** Boolean.
+
+The `destroy` value within the [resource
+namespace](#namespace-resources-data-sources) is `true` if a resource is being
+destroyed for _any_ reason, including cases where it's being deleted as part of
+a resource re-creation, in which case [`requires_new`](#value-requires_new) will
+also be set.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` when `null_resource.foo` is being
+destroyed:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].destroy }
+```
+
+### Value: `requires_new`
+
+* **Value Type:** Boolean.
+
+The `requires_new` value within the [resource
+namespace](#namespace-resources-data-sources) is `true` if the resource is still
+present in the configuration, but must be replaced to satisfy its current diff.
+Whenever `requires_new` is `true`, [`destroy`](#value-destroy) is also `true`.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if one of the `triggers` in
+`null_resource.foo` was being changed:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].requires_new }
+```
+
+## Namespace: Resource Diff
+
+The **diff namespace** is a namespace that represents the diff for a specific
+attribute within a resource. For details on reading a particular attribute,
+see the [`diff`](#value-diff) value in the [resource
+namespace](#namespace-resources-data-sources).
+
+### Value: `computed`
+
+* **Value Type:** Boolean.
+
+The `computed` value within the [diff namespace](#namespace-resource-diff) is
+`true` if the resource key in question depends on another value that isn't yet
+known. Typically, that means the value it depends on belongs to a resource that
+either doesn't exist yet, or is changing state in such a way as to affect the
+dependent value so that it can't be known until the apply is complete.
+
+-> Keep in mind that when using `computed` with complex structures such as maps,
+lists, and sets, it's sometimes necessary to test the count attribute for the
+structure, versus a key within it, depending on whether or not the diff has
+marked the whole structure as computed. This is demonstrated in the example
+below. Count keys are `%` for maps, and `#` for lists and sets. If you are
+having trouble determining the type of specific field within a resource, contact
+the support team.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+
+resource "null_resource" "bar" {
+ triggers = {
+ foo_id = "${null_resource.foo.id}"
+ }
+}
+```
+
+The following policy would evaluate to `true`, if the `id` of
+`null_resource.foo` was currently not known, such as when the resource is
+pending creation, or is being deleted and re-created:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.bar[0].diff["triggers.%"].computed }
+```
+
+### Value: `new`
+
+* **Value Type:** String.
+
+The `new` value within the [diff namespace](#namespace-resource-diff) contains
+the new value of a changing attribute, _if_ the value is known at plan time.
+
+-> `new` will be an empty string if the attribute's value is currently unknown.
+For more details on detecting unknown values, see [`computed`](#value-computed).
+
+Note that this value is _always_ a string, regardless of the actual type of the
+value changing. [Type conversion][ref-sentinel-type-conversion] within policy
+may be necessary to achieve the comparison needed.
+
+[ref-sentinel-type-conversion]: https://docs.hashicorp.com/sentinel/language/values#type-conversion
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`, if the resource was in the diff
+and each of the concerned keys were changing to new values:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].diff["triggers.foo"].new is "bar" }
+```
+
+### Value: `old`
+
+* **Value Type:** String.
+
+The `old` value within the [diff namespace](#namespace-resource-diff) contains
+the old value of a changing attribute.
+
+Note that this value is _always_ a string, regardless of the actual type of the
+value changing. [Type conversion][ref-sentinel-type-conversion] within policy
+may be necessary to achieve the comparison needed.
+
+If the value did not exist in the previous state, `old` will always be an empty
+string.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "baz"
+ }
+}
+```
+
+If that resource was previously in config as:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].diff["triggers.foo"].old is "bar" }
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/features/terraform/tfplan-v2.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/features/terraform/tfplan-v2.mdx
new file mode 100644
index 0000000000..61fb7b2bf2
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/features/terraform/tfplan-v2.mdx
@@ -0,0 +1,402 @@
+---
+page_title: tfplan/v2 - terraform - Features
+sidebar_current: docs-features-terraform-tfplan-v2
+description: >-
+ The tfplan import provides access to a Terraform plan. A Terraform plan is the
+ file created as a result of `terraform plan` and is the input to `terraform
+ apply`. The plan represents the changes that Terraform needs to make to
+ infrastructure to reach the desired state represented by the configuration.
+layout: docs
+---
+
+# Import: tfplan/v2
+
+The `tfplan/v2` import provides access to a Terraform plan.
+
+A Terraform plan is the file created as a result of `terraform plan` and is the
+input to `terraform apply`. The plan represents the changes that Terraform needs
+to make to infrastructure to reach the desired state represented by the
+configuration.
+
+In addition to the diff data available in the plan, there is a "planned state"
+that is available through this import, via the
+[`planned_values`](#the-planned_values-collection) collection. This collection
+presents the Terraform state as how it might look after the plan data is
+applied, but is not guaranteed to be the final state.
+
+The data in the `tfplan/v2` import is sourced from the JSON configuration file
+that is generated by the [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) command. For more information on
+the file format, see the [JSON Output Format](https://developer.hashicorp.com/terraform/internals/json-format)
+page.
+
+The entirety of the JSON output file is exposed as a Sentinel map via the
+[`raw`](#the-raw-collection) collection. This allows direct, low-level access to
+the JSON data, but should only be used in complex situations where the
+higher-level collections do not serve the purpose.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfplan/v2" {
+ config = {
+ "plan_path": "./path/to/plan.json"
+ }
+}
+```
+
+## Import Overview
+
+The `tfplan/v2` import is structured as a series of _collections_, keyed as a
+specific format depending on the collection.
+
+```
+tfplan/v2
+├── terraform_version (string)
+├── variables
+│ └── (indexed by name)
+│ ├── name (string)
+│ └── value (value)
+├── planned_values
+│ ├── outputs (tfstate/v2 outputs representation)
+│ └── resources (tfstate/v2 resources representation)
+├── resource_changes
+│ └── (indexed by address[:deposed])
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── index (float (number) or string)
+│ ├── provider_name (string)
+│ ├── deposed (string)
+│ └── change (change representation)
+├── resource_drift
+│ └── (indexed by address[:deposed])
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── index (float (number) or string)
+│ ├── provider_name (string)
+│ ├── deposed (string)
+│ └── change (change representation)
+├── output_changes
+│ └── (indexed by name)
+│ ├── name (string)
+│ └── change (change representation)
+└── raw (map)
+```
+
+The collections are:
+
+* [`variables`](#the-variables-collection) - The values of variables that have
+ been set in the plan itself. This collection only contains variables set in
+ the root module.
+* [`planned_values`](#the-planned_values-collection) - The state representation
+ of _planned values_, or an estimation of what the state will look like after
+ the plan is applied.
+* [`resource_changes`](#the-resource_changes-and-resource_drift-collections) - The set of change
+ operations for resources and data sources within this plan.
+* [`resource_drift`](#the-resource_changes-and-resource_drift-collections) - A description of the
+ changes Terraform detected when it compared the most recent state to the prior saved state.
+* [`output_changes`](#the-output_changes-collection) - The changes to outputs
+ within this plan. This collection only contains outputs set in the root
+ module.
+* [`raw`](#the-raw-collection) - Access to the raw plan data.
+
+These collections are specifically designed to be used with the
+[`filter`](/sentinel/language/collection-operations#filter-expression)
+quantifier expression in Sentinel, so that one can collect a list of resources
+to perform policy checks on without having to write complex discovery code. As
+an example, the following code will return all `aws_instance` resource changes,
+across all modules in the plan:
+
+```
+all_aws_instances = filter tfplan.resource_changes as _, rc {
+ rc.mode is "managed" and
+ rc.type is "aws_instance"
+}
+```
+
+You can add specific attributes to the filter to narrow the search, such as the
+module address, or the operation being performed. The following code would
+return resources in a module named `foo` only, and further narrow the search
+down to only resources that were being created:
+
+```
+all_aws_instances = filter tfplan.resource_changes as _, rc {
+ rc.module_address is "module.foo" and
+ rc.mode is "managed" and
+ rc.type is "aws_instance" and
+ rc.change.actions is ["create"]
+}
+```
+
+### Change Representation
+
+Certain collections in this import contain a _change representation_, an object
+with details about changes to a particular entity, such as a resource (within
+the [`resource_changes`](#the-resource_changes-collection) collection), or
+output (within the [`output_changes`](#the-output_changes-collection)
+collection).
+
+```
+(change representation)
+├── actions (list)
+├── before (value, or map)
+├── after (value, or map)
+└── after_unknown (boolean, or map of booleans)
+```
+
+This change representation contains the following fields:
+
+* `actions` - A list of actions being carried out for this change. The order is
+ important, for example a regular replace operation is denoted by `["delete",
+ "create"]`, but a
+ [`create_before_destroy`](https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle#create_before_destroy)
+ resource will have an operation order of `["create", "delete"]`.
+* `before` - The representation of the resource data object value before the
+ action. For create-only actions, this is unset. For no-op actions, this value
+ will be identical with `after`.
+* `after` - The representation of the resource data object value after the
+ action. For delete-only actions, this is unset. For no-op actions, this value
+ will be identical with `before`. Note that unknown values will not show up in
+ this field.
+* `after_unknown` - A deep object of booleans that denotes any values that are
+ unknown in a resource. These values were previously referred to as "computed"
+ values. If the value cannot be found in this map, then its value should be
+ available within `after`, so long as the operation supports it.
+
+#### Actions
+
+As mentioned above, actions show up within the `actions` field of a change
+representation and indicate the type of actions being performed as part of the
+change, and the order that they are being performed in.
+
+The current list of actions are as follows:
+
+* `create` - The action will create the associated entity. Depending on the
+ order this appears in, the entity may be created alongside a copy of the
+ entity before replacing it.
+* `read` - The action will read the associated entity. In practice, seeing this
+ change type should be rare, as reads generally happen before a plan is
+ executed (usually during a refresh).
+* `update` - The action will update the associated entity in a way that alters its state
+ in some way.
+* `delete` - The action will remove the associated entity, deleting any
+ applicable state and associated real resources or infrastructure.
+* `no-op` - No action will be performed on the associated entity.
+
+The `actions` field is a list, as some real-world actions are actually a
+composite of more than one primitive action. At this point in time, this
+is generally only applicable to resource replacement, in which the following
+action orders apply:
+
+* **Normal replacement:** `["delete", "create"]` - Applies to default lifecycle
+ configurations.
+* **Create-before-destroy:** `["create", "delete"]` - Applies when
+ [`create_before_destroy`](https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle#create_before_destroy)
+ is used in a lifecycle configuration.
+
+Note that, in most situations, the plan will list all "changes", including no-op
+changes. This makes filtering on change type crucial to the accurate selection
+of data if you are concerned with the state change of a particular resource.
+
+To filter on a change type, use exact list comparison. For example, the
+following example from the [Import Overview](#import-overview) filters on
+exactly the resources being created _only_:
+
+```
+all_aws_instances = filter tfplan.resource_changes as _, rc {
+ rc.module_address is "module.foo" and
+ rc.mode is "managed" and
+ rc.type is "aws_instance" and
+ rc.change.actions is ["create"]
+}
+```
+
+#### `before`, `after`, and `after_unknown`
+
+The exact attribute changes for a particular operation are outlined in the
+`before` and `after` attributes. Depending on the entity being operated on, this
+will either be a map (as with
+[`resource_changes`](#the-resource_changes-collection)) or a singular value (as
+with [`output_changes`](#the-output_changes-collection)).
+
+What you can expect in these fields varies depending on the operation:
+
+* For fresh create operations, `before` will generally be `null`, and `after`
+ will contain the data you can expect to see after the change.
+* For full delete operations, this will be reversed - `before` will contain
+ data, and `after` will be `null`.
+* Update or replace operations will have data in both fields relevant to their
+ states before and after the operation.
+* No-op operations should have identical data in `before` and `after`.
+
+For resources, if a field cannot be found in `after`, it generally means one of
+two things:
+
+* The attribute does not exist in the resource schema. Generally, known
+ attributes that do not have a value will show up as `null` or otherwise empty
+ in `after`.
+* The attribute is _unknown_, that is, it was unable to be determined at plan
+ time and will only be available after apply-time values have been able to be
+ calculated.
+
+In the latter case, there should be a value for the particular attribute in
+`after_unknown`, which can be checked to assert that the value is indeed
+unknown, versus invalid:
+
+```
+import "tfplan/v2" as tfplan
+
+no_unknown_amis = rule {
+ all filter tfplan.resource_changes as _, rc {
+ rc.module_address is "module.foo" and
+ rc.mode is "managed" and
+ rc.type is "aws_instance" and
+ rc.change.actions is ["create"]
+ } as _, rc {
+ rc.change.after_unknown.ami else false is false
+ }
+}
+```
+
+For output changes, `after_unknown` will simply be `true` if the value won't be
+known until the plan is applied.
+
+## The `terraform_version` Value
+
+The top-level `terraform_version` value in this import gives the Terraform
+version that made the plan. This can be used to do version validation.
+
+```
+import "tfplan/v2" as tfplan
+import "strings"
+
+v = strings.split(tfplan.terraform_version, ".")
+version_major = int(v[1])
+version_minor = int(v[2])
+
+main = rule {
+ version_major is 12 and version_minor >= 19
+}
+```
+
+## The `variables` Collection
+
+The `variables` collection is a collection of the variables set in the root
+module when creating the plan.
+
+This collection is indexed on the name of the variable.
+
+The valid values are:
+
+* `name` - The name of the variable, also used as the collection key.
+* `value` - The value of the variable assigned during the plan.
+
+## The `planned_values` Collection
+
+The `planned_values` collection is a special collection in that it contains two
+fields that alias to state collections with the _planned_ state set. This is the
+best prediction of what the state will look like after the plan is executed.
+
+The two fields are:
+
+* `outputs` - The prediction of what output values will look like after the
+ state is applied. For more details on the structure of this collection, see
+ the [`outputs`](/sentinel/features/terraform/tfstate-v2#the-outputs-collection) collection in the
+ [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2) documentation.
+* `resources` - The prediction of what resource values will look like after the
+ state is applied. For more details on the structure of this collection, see
+ the [`resources`](/sentinel/features/terraform/tfstate-v2#the-resources-collection) collection in the
+ [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2) documentation.
+
+-> **NOTE:** Unknown values are omitted from the `planned_values` state
+representations, regardless of whether or not they existed before. Use
+[`resource_changes`](#the-resource_changes-collection) if awareness of unknown
+data is important.
+
+## The `resource_changes` and `resource_drift` Collections
+
+The `resource_changes` and `resource_drift` collections are a set of change operations for resources
+and data sources within this plan.
+
+The `resource_drift` collection provides a description of the changes Terraform detected
+when it compared the most recent state to the prior saved state.
+
+The `resource_changes` collection includes all resources that have been found in the configuration and state,
+regardless of whether or not they are changing.
+
+~> When [resource targeting](/terraform/cli/commands/plan#resource-targeting) is in effect, the `resource_changes` collection will only include the resources specified as targets for the run. This may lead to unexpected outcomes if a policy expects a resource to be present in the plan. To prohibit targeted runs altogether, ensure [`tfrun.target_addrs`](/terraform/cloud-docs/policy-enforcement/sentinel/import/tfrun#value-target_addrs) is undefined or empty.
+
+This collection is indexed on the complete resource address as the key. If
+`deposed` is non-empty, it is appended to the end, and may look something like
+`aws_instance.foo:deposed-abc123`.
+
+An element contains the following fields:
+
+* `address` - The absolute resource address - also the key for the collection's
+ index, if `deposed` is empty.
+
+* `module_address` - The module portion of the absolute resource address.
+
+* `mode` - The resource mode, either `managed` (resources) or `data` (data
+ sources).
+
+* `type` - The resource type, example: `aws_instance` for `aws_instance.foo`.
+
+* `name` - The resource name, example: `foo` for `aws_instance.foo`.
+
+* `index` - The resource index. Can be either a number or a string.
+
+* `provider_name` - The name of the provider this resource belongs to. This
+ allows the provider to be interpreted unambiguously in the unusual situation
+ where a provider offers a resource type whose name does not start with its own
+ name, such as the `googlebeta` provider offering `google_compute_instance`.
+
+ -> **Note:** Starting with Terraform 0.13, the `provider_name` field contains the
+ _full_ source address to the provider in the Terraform Registry. Example:
+ `registry.terraform.io/hashicorp/null` for the null provider.
+
+* `deposed` - An identifier used during replacement operations, and can be used
+ to identify the exact resource being replaced in state.
+
+* `change` - The data describing the change that will be made to this resource.
+ For more details, see [Change Representation](#change-representation).
+
+## The `output_changes` Collection
+
+The `output_changes` collection is a collection of the change operations for
+outputs within this plan.
+
+Only outputs for the root module are included.
+
+This collection is indexed by the name of the output. The fields in a collection
+value are below:
+
+* `name` - The name of the output, also the index key.
+* `change` - The data describing the change that will be made to this output.
+ For more details, see [Change Representation](#change-representation).
+
+## The `raw` Collection
+
+The `raw` collection exposes the raw, unprocessed plan data.
+
+This is the same data that is produced by [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) on the plan file for the run this
+policy check is attached to.
+
+Use of this data is only recommended in expert situations where the data the
+collections present may not exactly serve the needs of the policy. For more
+information on the file format, see the [JSON Output
+Format](https://developer.hashicorp.com/terraform/internals/json-format) page.
+
+-> **NOTE:** Although designed to be relatively stable, the actual makeup for
+the JSON output format is a Terraform CLI concern and as such not managed by
+Sentinel. Use at your own risk, follow the [Terraform CLI
+project](https://github.com/hashicorp/terraform), and watch the file format
+documentation for any changes.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/features/terraform/tfstate-v1.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/features/terraform/tfstate-v1.mdx
new file mode 100644
index 0000000000..7ff2690947
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/features/terraform/tfstate-v1.mdx
@@ -0,0 +1,556 @@
+---
+page_title: tfstate/v1 - terraform - Features
+sidebar_current: docs-features-terraform-tfstate-v1
+description: The tfstate/v1 import provides access to a Terraform state.
+layout: docs
+---
+
+# Import: tfstate/v1
+
+~> **Warning:** The `tfstate/v1` import is deprecated and will be permanently removed in August 2025.
+Use the updated [tfstate/v2](/sentinel/docs/features/terraform/tfstate-v2) import as soon as possible to avoid disruptions.
+The `tfstate/v2` import offers improved functionality and is designed to better support your policy enforcement needs.
+
+The `tfstate/v1` import provides access to the Terraform state.
+
+The _state_ is the data that Terraform has recorded about a workspace at a
+particular point in its lifecycle, usually after an apply. You can read more
+general information about how Terraform uses state [here][ref-tf-state].
+
+[ref-tf-state]: https://developer.hashicorp.com/terraform/language/state
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfstate/v1" {
+ config = {
+ "path": "./path/to/plan.json"
+ }
+}
+```
+
+## Namespace Overview
+
+The following is a tree view of the import namespace. For more detail on a
+particular part of the namespace, see below.
+
+-> Note that the root-level alias keys shown here (`data`, `outputs`, `path`,
+and `resources`) are shortcuts to a [module namespace](#namespace-module) scoped
+to the root module. For more details, see the section on [root namespace
+aliases](#root-namespace-aliases).
+
+```
+tfstate/v1
+├── module() (function)
+│ └── (module namespace)
+│ ├── path ([]string)
+│ ├── data
+│ │ └── TYPE.NAME[NUMBER]
+│ │ ├── attr (map of keys)
+│ │ ├── depends_on ([]string)
+│ │ ├── id (string)
+│ │ └── tainted (boolean)
+│ ├── outputs (root module only in TF 0.12 or later)
+│ │ └── NAME
+│ │ ├── sensitive (bool)
+│ │ ├── type (string)
+│ │ └── value (value)
+│ └── resources
+│ └── TYPE.NAME[NUMBER]
+│ ├── attr (map of keys)
+│ ├── depends_on ([]string)
+│ ├── id (string)
+│ └── tainted (boolean)
+│
+├── module_paths ([][]string)
+├── terraform_version (string)
+│
+├── data (root module alias)
+├── outputs (root module alias)
+├── path (root module alias)
+└── resources (root module alias)
+```
+
+## Namespace: Root
+
+The root-level namespace consists of the values and functions documented below.
+
+In addition to this, the root-level `data`, `outputs`, `path`, and `resources`
+keys alias to their corresponding namespaces or values within the [module
+namespace](#namespace-module).
+
+### Function: `module()`
+
+```
+module = func(ADDR)
+```
+
+* **Return Type:** A [module namespace](#namespace-module).
+
+The `module()` function in the [root namespace](#namespace-root) returns the
+[module namespace](#namespace-module) for a particular module address.
+
+The address must be a list and is the module address, split on the period (`.`),
+excluding the root module.
+
+Hence, a module with an address of simply `foo` (or `root.foo`) would be
+`["foo"]`, and a module within that (so address `foo.bar`) would be read as
+`["foo", "bar"]`.
+
+[`null`][ref-null] is returned if a module address is invalid, or if the module
+is not present in the state.
+
+[ref-null]: /sentinel/language/spec#null
+
+As an example, given the following module block:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+If the module contained the following content:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if the resource was present in
+the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.module(["foo"]).resources.null_resource.foo[0].attr.triggers.foo is "bar" }
+```
+
+### Value: `module_paths`
+
+* **Value Type:** List of a list of strings.
+
+The `module_paths` value within the [root namespace](#namespace-root) is a list
+of all of the modules within the Terraform state at plan-time.
+
+Modules not present in the state will not be present here, even if they are
+present in the configuration or the diff.
+
+This data is represented as a list of a list of strings, with the inner list
+being the module address, split on the period (`.`).
+
+The root module is included in this list, represented as an empty inner list, as
+long as it is present in state.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The value of `module_paths` would be:
+
+```
+[
+ [],
+ ["foo"],
+]
+```
+
+And the following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.module_paths contains ["foo"] }
+```
+
+-> Note the above example only applies if the module is present in the state.
+
+#### Iterating Through Modules
+
+Iterating through all modules to find particular resources can be useful. This
+[example][iterate-over-modules] shows how to use `module_paths` with the
+[`module()` function](#function-module-) to find all resources of a
+particular type from all modules using the `tfplan` import. By changing `tfplan`
+in this function to `tfstate`, you could make a similar function find all
+resources of a specific type in the current state.
+
+[iterate-over-modules]: https://developer.hashicorp.com/terraform/cloud-docs/policy-enforcement/sentinel#sentinel-imports
+
+### Value: `terraform_version`
+
+* **Value Type:** String.
+
+The `terraform_version` value within the [root namespace](#namespace-root)
+represents the version of Terraform in use when the state was saved. This can be
+used to enforce a specific version of Terraform in a policy check.
+
+As an example, the following policy would evaluate to `true` as long as the
+state was made with a version of Terraform in the 0.11.x series, excluding any
+pre-release versions (example: `-beta1` or `-rc1`):
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.terraform_version matches "^0\\.11\\.\\d+$" }
+```
+
+-> **NOTE:** This value is also available via the [`tfplan`](/sentinel/features/terraform/tfplan-v1)
+import, which will be more current when a policy check is run against a plan.
+It's recommended you use the value in `tfplan` until HCP Terraform
+supports policy checks in other stages of the workspace lifecycle. See the
+[`terraform_version`][import-tfplan-terraform-version] reference within the
+`tfplan` import for more details.
+
+[import-tfplan-terraform-version]: /sentinel/features/terraform/tfplan-v1#value-terraform_version
+
+## Namespace: Module
+
+The **module namespace** can be loaded by calling
+[`module()`](#function-module-) for a particular module.
+
+It can be used to load the following child namespaces, in addition to the values
+documented below:
+
+* `data` - Loads the [resource namespace](#namespace-resources-data-sources),
+ filtered against data sources.
+* `outputs` - Loads the [output namespace](#namespace-outputs), which supply the
+ outputs present in this module's state. Note that with Terraform 0.12 or
+ later, this value is only available for the root namespace.
+* `resources` - Loads the [resource
+ namespace](#namespace-resources-data-sources), filtered against resources.
+
+### Root Namespace Aliases
+
+The root-level `data`, `outputs`, and `resources` keys both alias to their
+corresponding namespaces within the module namespace, loaded for the root
+module. They are the equivalent of running `module([]).KEY`.
+
+### Value: `path`
+
+* **Value Type:** List of strings.
+
+The `path` value within the [module namespace](#namespace-module) contains the
+path of the module that the namespace represents. This is represented as a list
+of strings.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The following policy would evaluate to `true`, _only_ if the module was present
+in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.module(["foo"]).path contains "foo" }
+```
+
+## Namespace: Resources/Data Sources
+
+The **resource namespace** is a namespace _type_ that applies to both resources
+(accessed by using the `resources` namespace key) and data sources (accessed
+using the `data` namespace key).
+
+Accessing an individual resource or data source within each respective namespace
+can be accomplished by specifying the type, name, and resource number (as if the
+resource or data source had a `count` value in it) in the syntax
+`[resources|data].TYPE.NAME[NUMBER]`. Note that NUMBER is always needed, even if
+you did not use `count` in the resource.
+
+In addition, each of these namespace levels is a map, allowing you to filter
+based on type and name.
+
+-> The (somewhat strange) notation here of `TYPE.NAME[NUMBER]` may imply that
+the inner resource index map is actually a list, but it's not - using the square
+bracket notation over the dotted notation (`TYPE.NAME.NUMBER`) is required here
+as an identifier cannot start with number.
+
+Some examples of multi-level access are below:
+
+* To fetch all `aws_instance.foo` resource instances within the root module, you
+ can specify `tfstate.resources.aws_instance.foo`. This would then be indexed
+ by resource count index (`0`, `1`, `2`, and so on). Note that as mentioned
+ above, these elements must be accessed using square-bracket map notation (so
+ `[0]`, `[1]`, `[2]`, and so on) instead of dotted notation.
+* To fetch all `aws_instance` resources within the root module, you can specify
+ `tfstate.resources.aws_instance`. This would be indexed from the names of
+ each resource (`foo`, `bar`, and so on), with each of those maps containing
+ instances indexed by resource count index as per above.
+* To fetch all resources within the root module, irrespective of type, use
+ `tfstate.resources`. This is indexed by type, as shown above with
+ `tfstate.resources.aws_instance`, with names being the next level down, and so
+ on.
+
+Further explanation of the namespace will be in the context of resources. As
+mentioned, when operating on data sources, use the same syntax, except with
+`data` in place of `resources`.
+
+### Value: `attr`
+
+* **Value Type:** A string-keyed map of values.
+
+The `attr` value within the [resource
+namespace](#namespace-resources-data-sources) is a direct mapping to the state
+of the resource.
+
+The map is a complex representation of these values with data going as far down
+as needed to represent any state values such as maps, lists, and sets.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if the resource was in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.resources.null_resource.foo[0].attr.triggers.foo is "bar" }
+```
+
+### Value: `depends_on`
+
+* **Value Type:** A list of strings.
+
+The `depends_on` value within the [resource
+namespace](#namespace-resources-data-sources) contains the dependencies for the
+resource.
+
+This is a list of full resource addresses, relative to the module (example:
+`null_resource.foo`).
+
+As an example, given the following resources:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+
+resource "null_resource" "bar" {
+ # ...
+
+ depends_on = [
+ "null_resource.foo",
+ ]
+}
+```
+
+The following policy would evaluate to `true` if the resource was in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.resources.null_resource.bar[0].depends_on contains "null_resource.foo" }
+```
+
+### Value: `id`
+
+* **Value Type:** String.
+
+The `id` value within the [resource
+namespace](#namespace-resources-data-sources) contains the id of the resource.
+
+-> **NOTE:** The example below uses a _data source_ here because the
+[`null_data_source`][ref-tf-null-data-source] data source gives a static ID,
+which makes documenting the example easier. As previously mentioned, data
+sources share the same namespace as resources, but need to be loaded with the
+`data` key. For more information, see the
+[synopsis](#namespace-resources-data-sources) for the namespace itself.
+
+[ref-tf-null-data-source]: https://registry.terraform.io/providers/hashicorp/null/latest/docs/data-sources/data_source
+
+As an example, given the following data source:
+
+```hcl
+data "null_data_source" "foo" {
+ # ...
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.data.null_data_source.foo[0].id is "static" }
+```
+
+### Value: `tainted`
+
+* **Value Type:** Boolean.
+
+The `tainted` value within the [resource
+namespace](#namespace-resources-data-sources) is `true` if the resource is
+marked as tainted in Terraform state.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`, if the resource was marked as
+tainted in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.resources.null_resource.foo[0].tainted }
+```
+
+## Namespace: Outputs
+
+The **output namespace** represents all of the outputs present within a
+[module](#namespace-module). Outputs are present in a state if they were saved
+during a previous apply, or if they were updated with known values during the
+pre-plan refresh.
+
+**With Terraform 0.11 or earlier** this can be used to fetch both the outputs
+of the root module, and the outputs of any module in the state below the root.
+This makes it possible to see outputs that have not been threaded to the root
+module.
+
+**With Terraform 0.12 or later** outputs are available in the top-level (root
+module) namespace only and not accessible within submodules.
+
+This namespace is indexed by output name.
+
+### Value: `sensitive`
+
+* **Value Type:** Boolean.
+
+The `sensitive` value within the [output namespace](#namespace-outputs) is
+`true` when the output has been [marked as sensitive][ref-tf-sensitive-outputs].
+
+[ref-tf-sensitive-outputs]: https://developer.hashicorp.com/terraform/language/values/outputs#sensitive-suppressing-values-in-cli-output
+
+As an example, given the following output:
+
+```hcl
+output "foo" {
+ sensitive = true
+ value = "bar"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.outputs.foo.sensitive }
+```
+
+### Value: `type`
+
+* **Value Type:** String.
+
+The `type` value within the [output namespace](#namespace-outputs) gives the
+output's type. This will be one of `string`, `list`, or `map`. These are
+currently the only types available for outputs in Terraform.
+
+As an example, given the following output:
+
+```hcl
+output "string" {
+ value = "foo"
+}
+
+output "list" {
+ value = [
+ "foo",
+ "bar",
+ ]
+}
+
+output "map" {
+ value = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+type_string = rule { tfstate.outputs.string.type is "string" }
+type_list = rule { tfstate.outputs.list.type is "list" }
+type_map = rule { tfstate.outputs.map.type is "map" }
+
+main = rule { type_string and type_list and type_map }
+```
+
+### Value: `value`
+
+* **Value Type:** String, list, or map.
+
+The `value` value within the [output namespace](#namespace-outputs) is the value
+of the output in question.
+
+Note that the only valid primitive output type in Terraform is currently a
+string, which means that any int, float, or boolean value will need to be
+converted before it can be used in comparison. This does not apply to primitives
+within maps and lists, which will be their original types.
+
+As an example, given the following output blocks:
+
+```hcl
+output "foo" {
+ value = "bar"
+}
+
+output "number" {
+ value = "42"
+}
+
+output "map" {
+ value = {
+ foo = "bar"
+ number = 42
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+value_foo = rule { tfstate.outputs.foo.value is "bar" }
+value_number = rule { int(tfstate.outputs.number.value) is 42 }
+value_map_string = rule { tfstate.outputs.map.value["foo"] is "bar" }
+value_map_int = rule { tfstate.outputs.map.value["number"] is 42 }
+
+main = rule { value_foo and value_number and value_map_string and value_map_int }
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/features/terraform/tfstate-v2.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/features/terraform/tfstate-v2.mdx
new file mode 100644
index 0000000000..9b29aa2c51
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/features/terraform/tfstate-v2.mdx
@@ -0,0 +1,176 @@
+---
+page_title: tfstate/v2 - terraform - Features
+sidebar_current: docs-features-terraform-tfstate-v2
+description: The tfstate/v2 import provides access to a Terraform state.
+layout: docs
+---
+
+# Import: tfstate/v2
+
+The `tfstate/v2` import provides access to a Terraform state.
+
+The _state_ is the data that Terraform has recorded about a workspace at a
+particular point in its lifecycle, usually after an apply. You can read more
+general information about how Terraform uses state
+[here](https://developer.hashicorp.com/terraform/language/state).
+
+The data in the `tfstate/v2` import is sourced from the JSON configuration file
+that is generated by the [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) command. For more information on
+the file format, see the [JSON Output Format](https://developer.hashicorp.com/terraform/internals/json-format)
+page.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfstate/v2" {
+ config = {
+ "path": "./path/to/plan.json"
+ }
+}
+```
+
+## Import Overview
+
+The `tfstate/v2` import is structured as currently two _collections_, keyed in
+resource address and output name, respectively.
+
+```
+(tfstate/v2)
+├── terraform_version (string)
+├── resources
+│ └── (indexed by address)
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── index (float (number) or string)
+│ ├── provider_name (string)
+│ ├── values (map)
+│ ├── depends_on (list of strings)
+│ ├── tainted (boolean)
+│ └── deposed_key (string)
+└── outputs
+ └── (indexed by name)
+ ├── name (string)
+ ├── sensitive (boolean)
+ └── value (value)
+```
+
+The collections are:
+
+* [`resources`](#the-resources-collection) - The state of all resources across
+ all modules in the state.
+* [`outputs`](#the-outputs-collection) - The state of all outputs from the root module in the state.
+
+These collections are specifically designed to be used with the
+[`filter`](/sentinel/language/collection-operations#filter-expression)
+quantifier expression in Sentinel, so that one can collect a list of resources
+to perform policy checks on without having to write complex module traversal. As
+an example, the following code will return all `aws_instance` resource types
+within the state, regardless of what module they are in:
+
+```
+all_aws_instances = filter tfstate.resources as _, r {
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+You can add specific attributes to the filter to narrow the search, such as the
+module address. The following code would return resources in a module named
+`foo` only:
+
+```
+all_aws_instances = filter tfstate.resources as _, r {
+ r.module_address is "module.foo" and
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+## The `terraform_version` Value
+
+The top-level `terraform_version` value in this import gives the Terraform
+version that recorded the state. This can be used to do version validation.
+
+```
+import "tfstate/v2" as tfstate
+import "strings"
+
+v = strings.split(tfstate.terraform_version, ".")
+version_major = int(v[1])
+version_minor = int(v[2])
+
+main = rule {
+ version_major is 12 and version_minor >= 19
+}
+```
+
+## The `resources` Collection
+
+The `resources` collection is a collection representing all of the resources in
+the state, across all modules.
+
+This collection is indexed on the complete resource address as the key.
+
+An element in the collection has the following values:
+
+* `address` - The absolute resource address - also the key for the collection's
+ index.
+
+* `module_address` - The address portion of the absolute resource address.
+
+* `mode` - The resource mode, either `managed` (resources) or `data` (data
+ sources).
+
+* `type` - The resource type, example: `aws_instance` for `aws_instance.foo`.
+
+* `name` - The resource name, example: `foo` for `aws_instance.foo`.
+
+* `index` - The resource index. Can be either a number or a string.
+
+* `provider_name` - The name of the provider this resource belongs to. This
+ allows the provider to be interpreted unambiguously in the unusual situation
+ where a provider offers a resource type whose name does not start with its own
+ name, such as the `googlebeta` provider offering `google_compute_instance`.
+
+ -> **Note:** Starting with Terraform 0.13, the `provider_name` field contains the
+ _full_ source address to the provider in the Terraform Registry. Example:
+ `registry.terraform.io/hashicorp/null` for the null provider.
+
+* `values` - An object (map) representation of the attribute values of the
+ resource, whose structure depends on the resource type schema. When accessing
+ proposed state through the [`planned_values`](/sentinel/features/terraform/tfplan-v2#the-planned_values-collection)
+ collection of the tfplan/v2 import, unknown values will be omitted.
+
+* `depends_on` - The addresses of the resources that this resource depends on.
+
+* `tainted` - `true` if the resource has been explicitly marked as
+ [tainted](https://developer.hashicorp.com/terraform/cli/commands/taint) in the state.
+
+* `deposed_key` - Set if the resource has been marked deposed and will be
+ destroyed on the next apply. This matches the deposed field in the
+ [`resource_changes`](/sentinel/features/terraform/tfplan-v2#the-resource_changes-collection)
+ collection in the [`tfplan/v2`](/sentinel/features/terraform/tfplan-v2) import.
+
+## The `outputs` Collection
+
+The `outputs` collection is a collection of outputs from the root module of the
+state.
+
+Note that no child modules are included in this output set, and there is no way
+to fetch child module output values. This is to encourage the correct flow of
+outputs to the recommended root consumption level.
+
+The collection is indexed on the output name, with the following fields:
+
+* `name`: The name of the output, also the collection key.
+* `sensitive`: Whether or not the value was marked as
+ [sensitive](https://developer.hashicorp.com/terraform/language/values/outputs#sensitive-suppressing-values-in-cli-output)
+ in
+ configuration.
+* `value`: The value of the output.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/functions/append.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/functions/append.mdx
new file mode 100644
index 0000000000..81395154f6
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/functions/append.mdx
@@ -0,0 +1,46 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Append'
+sidebar_current: docs-funcs-append
+description: The built-in function `append` appends a value to the end of a list.
+layout: docs
+---
+
+# Builtin Function: append
+
+The built-in function `append` appends a value to the end of a list. The list
+is modified in-place. The return value will always be [undefined](/sentinel/language/undefined).
+
+`append` called on any value other than a list or undefined will result
+in an immediate error.
+
+Examples:
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+### Why does this function return undefined?
+
+This function returns `undefined` to avoid unintended side effects of the function in the context
+of a [rule](/sentinel/language/rules).
+
+For example, if `append` returned the list value as a result, the following policy would depend
+on the order in which rules are referenced:
+
+```sentinel
+list = []
+a = rule { append(list, 1) contains 1 }
+b = rule { append(list, 2) contains 2 }
+
+// Scenario 1: list becomes [1, 2]
+main = rule { a and b }
+
+// Scenario 2: list becomes [2, 1]
+main = rule { b and a }
+```
+
+This sort of side effect behavior introduces complex and difficult to track logical errors in programs.
+As a result, functions like this return `undefined` and modify values in-place.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/functions/compare.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/functions/compare.mdx
new file mode 100644
index 0000000000..ed55775454
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/functions/compare.mdx
@@ -0,0 +1,42 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Compare'
+sidebar_current: docs-funcs-compare
+description: The built-in function `compare` compares two values.
+layout: docs
+---
+
+# Builtin Function: compare
+
+**_compare(value1, value2)_**
+
+The built-in function `compare` compares two values. The only valid types that
+can be provided are integers, floats or strings. Strings are compared according
+to lexicographic ordering; which is comparable to alphabetical
+ordering, but also takes into account symbols.
+
+The following table provides an overview of the possible return values:
+
+| Result | Description |
+|--------|-------------|
+| -1 | `value1` is less than `value2` |
+| 0 | `value1` is equal to `value2` |
+| +1 | `value1` is greater than `value2` |
+
+## Examples
+
+```sentinel
+// ints
+compare(1, 4) // -1
+compare(1, 4) // 0
+compare(4, 1) // +1
+
+// floats
+compare(1.0, 4.0) // -1
+compare(1.0, 4.0) // 0
+compare(4.0, 1.0) // +1
+
+// strings
+compare("apple", "banana") // -1
+compare("apple", "apple") // 0
+compare("banana", "apple") // +1
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/functions/delete.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/functions/delete.mdx
new file mode 100644
index 0000000000..3b60803997
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/functions/delete.mdx
@@ -0,0 +1,25 @@
+---
+page_title: 'Sentinel Language - Builtin Function: delete'
+sidebar_current: docs-funcs-delete
+description: The built-in function `delete` deletes an element from a map.
+layout: docs
+---
+
+# Builtin Function: delete
+
+The built-in function `delete` deletes an element from a map.
+
+If the element to delete does not exist, the map is left unchanged.
+Calling `delete` on a value that is not a map or
+[undefined](/sentinel/language/undefined)
+is an immediate error. The result of `delete` is always `undefined`.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/functions/error.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/functions/error.mdx
new file mode 100644
index 0000000000..96d253b662
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/functions/error.mdx
@@ -0,0 +1,15 @@
+---
+page_title: 'Sentinel Language - Builtin Function: error'
+sidebar_current: docs-funcs-error
+description: >-
+ The built-in function `error` is used to exit immediately with an error
+ message.
+layout: docs
+---
+
+# Builtin Function: error
+
+The built-in function `error` is used to exit immediately with an error message.
+
+Please see the [page on errors](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/functions/index.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/functions/index.mdx
new file mode 100644
index 0000000000..4e12143ad0
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/functions/index.mdx
@@ -0,0 +1,13 @@
+---
+page_title: Sentinel Language - Builtin Functions
+sidebar_current: docs-funcs
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Language: Builtin Functions
+
+Builtin functions are
+[functions](/sentinel/language/functions)
+that are available in all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in function.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/functions/keys.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/functions/keys.mdx
new file mode 100644
index 0000000000..a96cafe897
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/functions/keys.mdx
@@ -0,0 +1,22 @@
+---
+page_title: 'Sentinel Language - Builtin Function: keys'
+sidebar_current: docs-funcs-keys
+description: The built-in function `keys` returns the keys of a map.
+layout: docs
+---
+
+# Builtin Function: keys
+
+The built-in function `keys` returns the keys of a map.
+
+`keys` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned keys are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/functions/length.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/functions/length.mdx
new file mode 100644
index 0000000000..8b874b14c4
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/functions/length.mdx
@@ -0,0 +1,23 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Length'
+sidebar_current: docs-funcs-length
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Builtin Function: length
+
+The built-in function `length` returns the length of a collection or string.
+
+The length of a string is the number of bytes in the string. It is not
+necessarilly the number of characters. The length of a collection is the
+number of elements in that collection. The length of
+[undefined](/sentinel/language/undefined) is `undefined`.
+
+Examples:
+
+```sentinel
+length("foo") // 3
+length([1, 2, 3, 4]) // 4
+length({ "key": "value" }) // 1
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/functions/print.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/functions/print.mdx
new file mode 100644
index 0000000000..bcf441d09d
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/functions/print.mdx
@@ -0,0 +1,13 @@
+---
+page_title: 'Sentinel Language - Builtin Function: print'
+sidebar_current: docs-funcs-print
+description: The built-in function `print` is used for logging and debugging.
+layout: docs
+---
+
+# Builtin Function: print
+
+The built-in function `print` is used for logging and debugging.
+
+Please see the [page on logging](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/functions/range.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/functions/range.mdx
new file mode 100644
index 0000000000..0f86bfc402
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/functions/range.mdx
@@ -0,0 +1,49 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Range'
+sidebar_current: docs-funcs-range
+description: The built-in function `range` returns a list of numbers in a range.
+layout: docs
+---
+
+# Builtin Function: range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it
+defaults to `1`.
+
+Negative steps are permitted and count down from `start` towards `end`, instead
+of up.
+
+The range ends when the next value given by the sum of the last value and `step`
+would exceed `end`, regardless of if it has been reached.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4] - starts at 1 instead of 0
+range(1, 5, 2) // [1,3] - 3+2 does not satisfy r[-1] < end
+range(0, -3, -1) // [0,-1,-2] - example of negative range
+```
+
+Impossible ranges, or ranges where `start` will never reach `end` given `step`,
+yield an empty list.
+
+```
+range(1, 1) // [] - already starting at 1
+range(0, 1, -1) // [] - negative step will never reach 1
+```
+
+Supplying an undefined value to any argument of `range` yields an undefined
+value back.
+
+A range with a zero step is a runtime error.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/functions/values.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/functions/values.mdx
new file mode 100644
index 0000000000..8f98d3ab56
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/functions/values.mdx
@@ -0,0 +1,22 @@
+---
+page_title: 'Sentinel Language - Builtin Function: values'
+sidebar_current: docs-funcs-values
+description: The built-in function `values` returns the values of a map.
+layout: docs
+---
+
+# Builtin Function: values
+
+The built-in function `values` returns the values of a map.
+
+`values` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned values are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+values(data) // [3, 2]
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/imports/base64.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/imports/base64.mdx
new file mode 100644
index 0000000000..4e58429268
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/imports/base64.mdx
@@ -0,0 +1,46 @@
+---
+page_title: 'Import: base64'
+sidebar_current: docs-imports-base64
+description: The base64 import enables a Sentinel policy to encode and decode Base64 values.
+layout: docs
+---
+
+# Import: base64
+
+The base64 import enables a Sentinel policy to encode and decode Base64 values.
+
+### base64.encode(str)
+
+Encodes the string `str` into a Base64 encoded string, using the standard
+encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-4).
+
+```sentinel
+enc = base64.encode("foo") // "Zm9v"
+```
+
+### base64.decode(str)
+
+Decodes the Base64 encoded string `str`, returning the string result,
+using the standard encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-4).
+
+```sentinel
+dec = base64.decode("Zm9v") // "foo"
+```
+
+### base64.urlencode(str)
+
+Encodes the string `str` into a Base64 encoded string, using the alternate
+encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-5).
+
+```sentinel
+enc = base64.urlencode("foo") // "Zm9v"
+```
+
+### base64.urldecode(str)
+
+Decodes the Base64 encoded string `str`, returning the string result, using the
+alternate encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-5).
+
+```sentinel
+dec = base64.urldecode("Zm9v") // "foo"
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/imports/collection/index.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/imports/collection/index.mdx
new file mode 100644
index 0000000000..ae6db20bbb
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/imports/collection/index.mdx
@@ -0,0 +1,207 @@
+---
+page_title: 'Import: collection'
+sidebar_current: docs-imports-collection
+description: The collection import provides useful helpers for working with maps and lists.
+layout: docs
+---
+
+# Import: collection
+
+The `collection` import provides helpers for working with [maps](/sentinel/language/maps) and [lists](/sentinel/language/lists).
+
+## filter
+
+**_filter(items, predicate)_**
+
+Calls [predicate](#predicates) for each element in collection, returning a list of elements that the
+predicate __does__ return true for.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| items | the list or map to perform the filter against. |
+| predicate | a [predicate](#predicates) function to call for each element in the collection. A response of `true` will add the element to the result list. |
+
+### Examples
+
+Return all even numbers:
+
+```sentinel playground
+import "collection"
+
+items = [2, 3, 4, 5, 6, 7, 8]
+result = collection.filter(items, func(el) {
+ return el % 2 is 0
+})
+main = result is [2, 4, 6, 8]
+```
+
+## find
+
+**_find(items, predicate)_**
+
+Find and return an element within a collection according to the provided [predicate](#predicates). If nothing is found, returns [undefined](/sentinel/language/undefined).
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| items | the list or map to perform the find against. |
+| predicate | a [predicate](#predicates) function to call for each element in the collection. A response of `true` will return the element and complete the find. |
+
+### Examples
+
+Find an element in a collection based on its value:
+
+```sentinel playground
+import "collection"
+
+items = ["foo", "bar", "qux"]
+item = collection.find(items, func(el) {
+ return el is "bar"
+})
+main = item is "bar"
+```
+
+Find an element in a collection based on its key:
+
+```sentinel playground
+import "collection"
+
+items = {"foo": 2, "bar": 4}
+item = collection.find(items, func(el, key) {
+ return key is "bar"
+})
+main = item is 4
+```
+
+## matches
+
+**_matches(items, partial)_**
+
+Compare each element in a collection against a partial map using deep comparison. Returns
+the list of elements that returned true for the partial comparison. If no matches are found, returns an empty list.
+
+### Arguments
+
+| Name | Description |
+|---------|-------------|
+| items | the list or map to perform the match against. |
+| partial | the map used for partial deep comparison against each element in the collection. |
+
+### Examples
+
+Return all items that contain `{"foo": {"bar": "wip"}}`:
+
+```sentinel playground
+import "collection"
+
+items = [
+ # This item should match
+ {
+ "foo": { "bar": "wip"},
+ "baz": "qux",
+ },
+ # This item will not match
+ {
+ "foo": "bar",
+ "baz": "bar",
+ },
+]
+result = collection.matches(items, {"foo": {"bar": "wip"}})
+main = result is [{"foo": {"bar": "wip"}, "baz": "qux"}]
+```
+
+## reduce
+
+**_reduce(items, accumulator[, initial])_**
+
+Call an accumulator function for each element in collection, supplying the
+previous accumulated value as the accumulation parameter.
+
+### Arguments
+
+| Name | Description |
+|-------------|-------------|
+| items | the list or map to perform the reduce against. |
+| accumulator | a function that is called for each element in the collection, used to accumulate the value. The first argument is the current accumulated value, the remaining arguments use the same rules as [predicate](#predicates) functions. |
+| initial | the initial value to use. It is an optional argument, and if not provided the first element in the collection is used as the initial value. |
+
+### Examples
+
+Reduce the collection by adding each element together:
+
+```sentinel playground
+import "collection"
+
+items = [1, 2, 3, 4, 5]
+result = collection.reduce(items, func(acc, el) {
+ return acc + el
+}, 0)
+main = result is 15
+```
+
+## reject
+
+**_reject(items, predicate)_**
+
+Calls [predicate](#predicates) for each element in collection, returning a list of elements that the
+predicate __does not__ return true for.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| items | the list or map to perform the reject against. |
+| predicate | a [predicate](#predicates) function to call for each element in the collection. A response of `false` will add the element to the result list. |
+
+### Examples
+
+Return all odd numbers by rejecting even ones:
+
+```sentinel playground
+import "collection"
+
+items = [2, 3, 4, 5, 6, 7, 8]
+result = collection.reject(items, func(el) {
+ return el % 2 is 0
+})
+main = result is [3, 5, 7]
+```
+
+## Predicates
+
+Some helpers accept a predicate function, which has the purpose of making an
+assertion. Each predicate may accept differing arguments and return differing
+types. If the collection is a list, the parameters will be the element and index.
+If the collection is a Map, the parameters will be the value and the key.
+
+### Examples
+
+A predicate for a list of items:
+
+```sentinel
+// including index
+func(item, index) {
+ return true
+}
+
+// excluding index
+func(item) {
+ return true
+}
+```
+
+A predicate for a map of items:
+```sentinel
+// including key
+func(value, key) {
+ return true
+}
+
+// excluding key
+func(value) {
+ return true
+}
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/imports/collection/lists.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/imports/collection/lists.mdx
new file mode 100644
index 0000000000..cf258a3956
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/imports/collection/lists.mdx
@@ -0,0 +1,211 @@
+---
+page_title: 'Import: collection/lists'
+sidebar_current: docs-imports-collection-lists
+description: The collection/lists import provides useful helpers for working with lists.
+layout: docs
+---
+
+# Import: collection/lists
+
+The `collection/lists` import provides helpers for working with [lists](/sentinel/language/lists).
+
+## concat
+
+**_concat(items, others[, ...additional])_**
+
+Join multiple lists together, returning the resulting list. This helper must
+have at least two arguments supplied. Order is important,
+as the order of arguments is the order that lists will be appended.
+
+### Arguments
+
+| Name | Description |
+|------------|-------------|
+| items | the first list to add to the resulting value |
+| others | the second list to add to the resulting value |
+| additional | any number of additional lists to add to the resulting value |
+
+### Examples
+
+Concatenate two lists:
+
+```sentinel playground
+import "collection/lists" as lists
+
+result = lists.concat([1], [2, 3])
+main = result is [1, 2, 3]
+```
+
+Concatenate many lists:
+```sentinel playground
+import "collection/lists" as lists
+
+result = lists.concat([1, 2], [10, 20], [100, 200])
+main = result is [1, 2, 10, 20, 100, 200]
+```
+
+## difference
+
+**_difference(items[, ...additional])_**
+
+Difference returns a new list with all values that exist in the first list
+that are not present in all remaining provided lists.
+
+### Arguments
+
+| Name | Description |
+|------------|-------------|
+| items | the first list, used as the source for ordering |
+| additional | any number of additional lists used to subtract from the source list |
+
+### Aliases
+
+__diff__
+
+### Examples
+
+Difference between two lists:
+
+```sentinel playground
+import "collection/lists" as lists
+
+result = lists.difference([1, 2], [2, 3])
+main = result is [1]
+```
+
+Difference between many lists:
+```sentinel playground
+import "collection/lists" as lists
+
+result = lists.difference([1, 2, 3, 4, 5], [2, 5], [1])
+main = result is [3, 4]
+```
+
+## intersection
+
+**_intersection(items[, ...additional])_**
+
+Intersection returns a list consisting of unique values that are present in all
+of the provided lists.
+
+### Arguments
+
+| Name | Description |
+|------------|-------------|
+| items | the first list to use for intersection |
+| additional | any number of additional lists used to intersect |
+
+### Examples
+
+Intersection between three lists:
+
+```sentinel playground
+import "collection/lists" as lists
+
+result = lists.intersection([1, 2], [2, 3], [3, 2])
+main = result is [2]
+```
+
+## sort
+
+**_sort(items, sort_function)_**
+
+Sort will sort a list of elements according to the provided sort function, returning
+a new, sorted list.
+
+### Arguments
+
+| Name | Description |
+|------|-------------|
+| items | the list to sort |
+| sort_function | the function that is used to perform the sort |
+
+### Sort Function
+
+The provided sort function accepts two arguments, which can be considered as the
+`current` and `next` item. The function should return one of the following values:
+
+| Result | Description |
+|--------|-------------|
+| -1 | `current` is less than `next` |
+| 0 | `current` is equal to `next` |
+| +1 | `current` is greater than `next` |
+
+The [compare](/sentinel/functions/compare) built-in method provides a way of
+performing comparisons against integers, floats or strings to return a supported
+value.
+
+### Examples
+
+Sort a list of words:
+
+```sentinel playground
+import "collection/lists" as lists
+
+items = ["zebra", "bat", "horse"]
+result = lists.sort(items, func(x, y) {
+ return compare(x, y)
+})
+main = result is ["bat", "horse", "zebra"]
+```
+
+Sort a list of objects by a key:
+
+```sentinel playground
+import "collection/lists" as lists
+
+items = [{"foo": 8}, {"foo": 4}, {"foo": 100}]
+result = lists.sort(items, func(x, y) {
+ return compare(x.foo, y.foo)
+})
+main = result is [{"foo": 4}, {"foo": 8}, {"foo": 100}]
+```
+
+## sum
+
+**_sum(items)_**
+
+Sum will add all elements within the provided list. The
+list must only contain integers or floats. The return value will always be a float.
+
+### Arguments
+
+| Name | Description |
+|------|-------------|
+| items | the list to sum |
+
+### Examples
+
+Perform a sum on a list of numbers:
+
+```sentinel playground
+import "collection/lists" as lists
+
+items = [2, 5, 10, 500]
+main = lists.sum(items) is 517
+```
+
+## union
+
+**_union(items[, ...additional])_**
+
+Union will return a list that contains unique values from all provided lists.
+
+### Arguments
+
+| Name | Description |
+|------------|-------------|
+| items | the first list of values |
+| additional | any number of additional lists of values |
+
+### Examples
+
+Perform a union on multiple lists:
+
+```sentinel playground
+import "collection/lists" as lists
+
+items = lists.union([1, 3], [2, 3, 5], [6, 2, 1])
+
+main = items is [1, 3, 2, 5, 6]
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/imports/collection/maps.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/imports/collection/maps.mdx
new file mode 100644
index 0000000000..d96bb15639
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/imports/collection/maps.mdx
@@ -0,0 +1,360 @@
+---
+page_title: 'Import: collection/maps'
+sidebar_current: docs-imports-collection-maps
+description: The collection/maps import provides useful helpers for working with maps.
+layout: docs
+---
+
+# Import: collection/maps
+
+The `collection/maps` import provides helpers for working with [maps](/sentinel/language/maps).
+
+## get
+
+**_get(object, path[, default])_**
+
+Get the value from the provided object using the [path](#paths).
+When the path is invalid or the object doesn't contain a value at the path, then the default is returned. The default return value is undefined.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the get against. |
+| path | a [path](#paths) to a key within object to retreive the value |
+| default | an optional value that will return if the path does not exist or returns undefined |
+
+### Examples
+
+Get a value from a simple object:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+item = maps.get(object, "foo")
+main = item is "bar"
+```
+
+Get a nested value from a complex object:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [{"bar": {"baz": 4}}]}
+item = maps.get(object, "foo.0.bar.baz")
+main = item is 4
+```
+
+Get a list of values using [splat](#splat):
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [{"bar": 4}, {"bar": 8}, {"bar": 45}]}
+item = maps.get(object, "foo.*.bar")
+main = item is [4, 8, 45]
+```
+
+Get an invalid path, providing a default:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {}
+item = maps.get(object, "foo", "bar")
+main = item is "bar"
+```
+
+Get an invalid path, not providing a default:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {}
+item = maps.get(object, "foo")
+main = item is not defined
+```
+
+## has
+
+**_has(object, path)_**
+
+Return a boolean value if the object has the path within its structure.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the has against. |
+| path | a [path](#paths) to a key within object to determine if it exists |
+
+### Examples
+
+An object has a valid key:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+main = maps.has(object, "foo")
+```
+
+An object does not have a valid key:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {}
+main = maps.has(object, "foo") is false
+```
+
+## omit
+
+**_omit(object, paths)_**
+
+Return a map, removing each path from the paths provided from the source object.
+The original structure of the map is maintained, as seen in the below examples.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the omit against. |
+| paths | a list of [paths](#paths) to be removed from the source object |
+
+### Examples
+
+Omitting a single value:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+main = maps.omit(object, ["foo"]) is {}
+```
+
+Omitting multiple values:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar", "baz": "qux", "nit": "nat"}
+main = maps.omit(object, ["foo", "baz"]) is {"nit": "nat"}
+```
+
+Omitting deep values:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {
+ "foo": "bar",
+ "baz": [
+ { "qux": 4 },
+ { "qux": 10 }
+ ],
+ "nit": {
+ "nat": "pak"
+ }
+}
+main = maps.omit(object, ["baz.1.qux", "nit.nat"]) is {"foo": "bar", "baz": [{"qux": 4}]}
+```
+
+## pick
+
+**_pick(object, paths)_**
+
+Return a map consisting of each value found in object from the paths provided.
+The original structure of the map is maintained, as seen in the below examples.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the pick against. |
+| paths | a list of [paths](#paths), each used to select a value from the object. |
+
+### Examples
+
+Picking a single value:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+main = maps.pick(object, ["foo"]) is {"foo": "bar"}
+```
+
+Picking multiple values:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar", "baz": "qux", "nit": "nat"}
+main = maps.pick(object, ["foo", "baz"]) is {"foo": "bar", "baz": "qux"}
+```
+
+Picking deep values:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {
+ "foo": "bar",
+ "baz": [
+ { "qux": 4 },
+ { "qux": 10 }
+ ],
+ "nit": {
+ "nat": "pak"
+ }
+}
+main = maps.pick(object, ["baz.1.qux", "nit.nat"]) is {"baz": [{"qux": 10}], "nit": {"nat": "pak"}}
+```
+
+## set
+
+**_set(object, path, value)_**
+
+Return a new map, assigning the provided value to the provided path. It will
+not modify the provided map in place.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the set against. |
+| path | the [path](#paths) to assign value |
+| value | the value to be assigned |
+
+### Examples
+
+Set a value on a simple path:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+object = maps.set(object, "foo", "qux")
+main = object is {"foo": "qux"}
+```
+
+Set a value on a deep path:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {}
+object = maps.set(object, "foo.bar.baz", 10)
+main = object is {"foo": { "bar": { "baz": 10 } } }
+```
+
+Set a value on an index in a list:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [ 2, 5 ] }
+object = maps.set(object, "foo.0", 10)
+main = object is {"foo": [ 10, 5 ] }
+```
+
+Set a value on every key within a list key:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [ { "bar": true }, { "bar": false } ] }
+object = maps.set(object, "foo.*.bar", true)
+main = object is {"foo": [ { "bar": true }, { "bar": true } ] }
+```
+
+## unset
+
+**_unset(object, path)_**
+
+Return a new map, removing the provided path from the source object. It will
+not modify the provided map in place.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the unset against. |
+| path | the [path](#paths) to remove |
+
+### Examples
+
+Unset on a simple path:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+object = maps.unset(object, "foo")
+main = object is {}
+```
+
+Unset on a deep path:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": { "bar": { "baz": 10, "qux": 15 } } }
+object = maps.unset(object, "foo.bar.baz")
+main = object is {"foo": { "bar": { "qux": 15 } } }
+```
+
+Unset an index in a list:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [ 2, 5 ] }
+object = maps.unset(object, "foo.0")
+main = object is {"foo": [ 5 ] }
+```
+
+Unset a list:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [ { "bar": true }, { "bar": false } ] }
+object = maps.unset(object, "foo.*", true)
+main = object is {"foo": [] }
+```
+
+## Paths
+
+Map helpers often receive a **path** argument that allows for looking up a nested
+key. Generally speaking, a path is a series of keys separated by `.`, however there
+are some additional capabilites that need to be explained.
+
+### Lists
+
+When traversing a nested map that contains a list, a specific index can be retrieved
+by providing the index as the part of the path.
+
+In the following code sample, the path will first enter the key `"foo"` within the
+map, followed by entering the first index of the list.
+
+```sentinel
+path = "foo.0"
+object = {"foo": [1]}
+```
+
+### Splat
+
+To provide advanced capabilities when using paths, you can also use the splat (`*`)
+operator to iterate through **all** elements in a list, with all parts of the path
+following the splat occuring on each entry.
+
+In the following code sample, the path will first enter the key `"foo"` within the map.
+It will then enter each item in the list, entering the `"bar"` key for each nested object.
+
+```sentinel
+path = "foo.*.bar"
+object = {"foo": [{"bar": 1}, {"bar": 2}]}
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/imports/decimal.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/imports/decimal.mdx
new file mode 100644
index 0000000000..a6633a38e0
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/imports/decimal.mdx
@@ -0,0 +1,318 @@
+---
+page_title: 'Import: decimal'
+sidebar_current: docs-imports-decimal
+description: |-
+ The decimal import provides functions for operating on numbers represented
+ as decimals.
+layout: docs
+---
+
+# Import: decimal
+
+The decimal import provides functions for operating on numbers represented
+as decimals.
+
+Binary floating-point numbers provided by the `float` type are unable to
+fully represent numbers like `1.1` and `2.2`. The decimal import can
+represent these numbers exactly, and so should be used when exactness is
+required.
+
+Decimals are created through the `new` function, which takes any type that
+can represent a number. Arguments to the decimal operators can also take
+a value of any of these types. The full list is:
+
+- float
+- int
+- string
+- existing decimal value
+
+### decimal.infinity(sign)
+
+Creates an infinite-value decimal. Infinite-value decimals are always larger
+or smaller, depending on sign, than any non-infinite decimals.
+
+```sentinel
+decimal.infinity(1) // +Infinity
+decimal.infinity(-1) // -Infinity
+```
+
+Also available as `decimal.inf`.
+
+### decimal.nan
+
+A decimal representing a "not-a-number" value. NaNs are not equal to any
+other number, even themselves.
+
+```sentinel
+decimal.new(-1).sqrt() // NaN
+decimal.new(0).mul(decimal.inf(1)) // NaN
+decimal.nan.is(decimal.nan) // false
+```
+
+### decimal.is_infinite(d)
+
+Evaluates to true if the decimal is infinite.
+
+```sentinel
+decimal.is_infinite(100) // false
+decimal.is_infinite("Infinity") // true
+```
+
+Also available as `decimal.is_inf`, `decimal.isinf` and `decimal.isinfinite`.
+
+### decimal.is_nan(d)
+
+Evaluates to true if the decimal is not-a-number.
+
+```sentinel
+decimal.is_nan("NaN") // true
+```
+
+Also available as `decimal.isnan`.
+
+### decimal.new(v)
+
+Constructs a decimal from another value.
+
+```sentinel
+decimal.new("1.1") // Constructs a decimal from a string.
+decimal.new(2.2) // Constructs a decimal from a float.
+decimal.new(3) // Constructs a decimal from an integer.
+decimal.new("1.1234E+400") // Constructs a decimal from a string using E-notation.
+```
+
+## Type: number
+
+Numbers are represented internally by a sign, coefficient, and exponent.
+The value is calculated with the formula `sign * coefficient * 10^exponent`.
+Exponent is limited to 32 bits, and the coefficient is limited by the total
+precision.
+
+The maximum precision a number can represent is 100 digits. This includes
+both before and after the decimal place.
+
+### number.string
+
+A string representation of the decimal.
+
+```sentinel
+decimal.new(1.5).string // 1.5
+```
+
+### number.sign
+
+A number between `-1` and `+1` representing the sign of the decimal.
+
+```sentinel
+decimal.new(1.5).sign // 1
+```
+
+### number.coefficient
+
+The coefficient component of the decimal.
+
+```sentinel
+decimal.new(1.5).coefficient // 15
+```
+
+### number.exponent
+
+The exponent component of the decimal.
+
+```sentinel
+decimal.new(1.5).exponent // -1
+```
+
+### number.float
+
+A floating-point representation of the decimal.
+
+```sentinel
+decimal.new(1.5).float // 1.500000
+```
+
+### number.int
+
+An integer representation of the decimal. This always rounds down to the nearest integer.
+
+```sentinel
+decimal.new(1.5).int // 1
+```
+
+### number.is(v)
+
+Test for equality with another value.
+
+```sentinel
+decimal.new(1.5).is(1) // false
+```
+
+### number.is_not(v)
+
+Test for inequality with another value.
+
+```sentinel
+decimal.new(1.5).is_not(1) // true
+```
+
+### number.less_than(v)
+
+Test that the decimal is less than another value.
+Can also be called as `number.lt(v)`
+
+```sentinel
+decimal.new(1.5).less_than(1) // false
+```
+
+### number.less_than_or_equals(v)
+
+Test that the decimal is less than or equals to another value.
+Can also be called as `number.lte(v)`
+
+```sentinel
+decimal.new(1.5).less_than_or_equals(1) // false
+```
+
+### number.greater_than(v)
+
+Test that the decimal is greater than another value.
+Can also be called as `number.gt(v)`
+
+```sentinel
+decimal.new(1.5).greater_than(1) // true
+```
+
+### number.greater_than_or_equals(v)
+
+Test that the decimal is greater than or equals to another value.
+Can also be called as `number.gte(v)`
+
+```sentinel
+decimal.new(1.5).greater_than_or_equals(1) // true
+```
+
+### number.add(v)
+
+Add another number to the decimal.
+
+```sentinel
+decimal.new(1.5).add(1) // 2.5
+```
+
+### number.subtract(v)
+
+Subtract another number from the decimal.
+Can also be called as `number.sub(v)`
+
+```sentinel
+decimal.new(1.5).subtract(1) // 0.5
+```
+
+### number.multiply(v)
+
+Multiply the decimal by a number.
+Can also be called as `number.mul(v)`
+
+```sentinel
+decimal.new(1.5).multiply(2) // 3.0
+```
+
+### number.divide(v)
+
+Divide the decimal by a number.
+Can also be called as `number.div(v)`
+
+```sentinel
+decimal.new(3.0).divide(1.5) // 2
+```
+
+### number.modulo(v)
+
+Find the remainder after dividing the decimal by a number.
+Can also be called as `mod`, `remainder`, or `rem`.
+
+```sentinel
+decimal.new(1.5).modulo(1) // 0.5
+```
+
+### number.power(v)
+
+Raise the decimal to a power.
+Can also be called as `number.pow(v)`
+
+```sentinel
+decimal.new(1.5).power(2) // 2.25
+```
+
+### number.exp()
+
+The natural exponent of the decimal. This calculates `e^number`.
+
+```sentinel
+decimal.new(1.5).exp() // 4.4816...
+```
+
+### number.loge()
+
+The natural logarithm of the decimal.
+Can also be called as `number.ln()`
+
+```sentinel
+decimal.new(1.5).loge() // 0.4054...
+```
+
+### number.log10()
+
+The base-10 logarithm of the decimal.
+Can also be called as `number.log()`
+
+```sentinel
+decimal.new(1.5).log10() // 0.1760...
+```
+
+### number.square_root()
+
+The square root of the decimal.
+Can also be called as `number.sqrt()`
+
+```sentinel
+decimal.new(1.5).square_root() // 1.2247...
+```
+
+### number.ceiling()
+
+Round the decimal to the smallest integer greater or equal to itself.
+Can also be called as `number.ceil()`
+
+```sentinel
+decimal.new(1.5).ceiling() // 2
+```
+
+### number.floor()
+
+Round the decimal to the largest integer less than or equal to itself.
+
+```sentinel
+decimal.new(1.5).floor() // 1
+```
+
+### number.absolute()
+
+The absolute value of the decimal.
+Can also be called as `number.abs()`
+
+```sentinel
+decimal.new(1.5).absolute() // 1.5
+decimal.new(-1.5).absolute() // 1.5
+```
+
+### number.negate()
+
+The negated value of the decimal. A positive decimal will become negative,
+and a negative decimal will become positive.
+Can also be called as `number.neg()`
+
+```sentinel
+decimal.new(1.5).negate() // -1.5
+decimal.new(-1.5).negate() // 1.5
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/imports/http.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/imports/http.mdx
new file mode 100644
index 0000000000..f94ae98c27
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/imports/http.mdx
@@ -0,0 +1,370 @@
+---
+page_title: 'Import: http'
+sidebar_current: docs-imports-http
+description: The http import enables the use of HTTP-accessible data from outside the runtime in Sentinel policy rules.
+layout: docs
+---
+
+# Import: http
+
+The http import enables the use of HTTP-accessible data from outside
+the runtime in Sentinel policy rules.
+
+The simplest example of the import in use would be:
+
+```sentinel
+import "http"
+
+resp = http.get("https://example.hashicorp.com")
+main = rule { resp.body contains "something" }
+```
+
+By default, any request response that is not a successful "200 OK" HTTP
+response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information and
+how to customize this behavior.
+
+For more advanced requests which require setting request headers, use a
+[`request` type](#type-request):
+
+```sentinel
+import "http"
+import "json"
+
+param token
+
+req = http.request("https://example.hashicorp.com").with_header("Authorization", "token "+token)
+resp = json.unmarshal(http.get(req).body)
+main = rule { resp["some_key"] is true }
+```
+
+The `with_header` function above returns the `request` object with the
+`Authorization` HTTP header set to value of the variable `some_value`. Many
+of the methods within the http import API are designed around this concept
+of method chaining, allowing for complex building of HTTP requests
+encapsulated in functions. The following is an idiomatic example further
+demonstrating this pattern:
+
+```sentinel
+import "http"
+import "json"
+
+param github_user
+param github_token
+
+client = func(req) {
+ return http.with_timeout(5).with_retries(3)
+}
+
+github_request = func(path) {
+ full_url = "https://api.github.com" + path
+ return http.request(full_url).
+ with_basic_auth(github_user, github_token).
+ with_header("Accept", "application/vnd.github.v3+json").
+ with_header("Custom", "foo"),
+}
+
+default_response = client.get(github_request("/example"))
+
+# Override the Custom header for this single request
+custom_header_response = json.unmarshal(
+ client.get(
+ github_request("/example").with_header("Custom", "bar"),
+ ),
+)
+
+# Override the timeout value for a known slower request
+slow_response = json.unmarshal(
+ client.with_timeout(15).get(github_request("/slow-endpoint")),
+)
+
+some_key_is_true = rule { json.unmarshal(default_response.body)["some_key"] is true }
+body_contains_text = rule { custom_header_response.body contains "something" }
+response_is_ok = rule { slow_response.status_code is 200 }
+
+main = rule { some_key_is_true and body_contains_text and response_is_ok }
+```
+
+### http.client
+
+Retrieve the base HTTP client with default settings. The returned client may be
+configured by calling configuration functions on it; all of these configuration
+functions on are also aliased as top-level functions in this namespace. See
+[`client`](#type-client) below.
+
+### http.request(url)
+
+Create a new [`request`](#type-request) to the specified `url` (string). A
+`request` type enables customization of headers and other concerns before then
+providing the constructed `request` to [`get()`](#client-get-url_or_request),
+[`post()`](#client-post-url_or_request) or [`send()`](#client-send-request).
+
+```sentinel
+req = http.request("example.hashicorp.com/foo").with_header("Foo", "bar")
+resp = http.get(req)
+```
+
+### http.methodPost
+
+Returns a string containing the accepted verb for POST requests, `"POST"`.
+
+### http.methodGet
+
+Returns a string containing the accepted verb for GET requests, `"GET"`.
+
+## Type: client
+
+All of the following configuration functions on the default client are also
+aliased as top-level functions in the import. This allows a short-hand (and
+idiomatic) way of configuring a new client without having to directly access
+`http.client` each time:
+
+```sentinel
+# The following two lines are semantically the same:
+resp = http.client.with_timeout(5).get(url)
+resp = http.with_timeout(5).get(url)
+```
+
+### url_or_request
+
+The [`get()`](#client-get-url_or_request) and [`post()`](#client-post-url_or_request)
+functions accept either a URL string or a request object, noted as
+`url_or_request` throughout the documentation.
+
+When `url_or_request` is a string, a request is made with the URL
+specified by the string. To customize HTTP headers and other settings, pass
+a request. By default, a request has a timeout value of 10 seconds and 1
+retry. These settings can be adjusted with [`with_timeout()`](#clientwith_timeoutinteger) and
+[`with_retries()`](#clientwith_retriesinteger), respectively.
+
+### Redirects
+
+If the response is one of the following redirect codes, the client follows the
+redirect, up to a maximum of 10 redirects:
+
+- 301 (Moved Permanently)
+- 302 (Found)
+- 303 (See Other)
+- 307 (Temporary Redirect)
+- 308 (Permanent Redirect)
+
+If the redirect limit is exceeded, the result is a policy error.
+
+By default, after any redirects are followed (up to the maximum), any request
+response that is not a successful "200 OK" response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information
+and how to customize this behavior.
+
+### client.get(url_or_request)
+
+Issue a GET request with a URL string or a `request` type. Returns a `response`
+type.
+
+```sentinel
+import "http"
+
+resp = http.get("https://example.hashicorp.com")
+main = rule { resp.body contains "something" }
+```
+
+### client.post(url_or_request)
+
+Issue a POST request with a URL string or a `request` type. Returns a `response`
+type.
+
+```sentinel
+import "http"
+
+req = http.request("https://example.hashicorp.com")
+ .with_body(json.marshal({"foo": "bar"}))
+resp = http.post(req)
+main = rule { resp.body contains "something" }
+```
+
+### client.send(request)
+
+Make a request using the supplied `request` type. Returns a `response`.
+
+```sentinel
+import "http"
+
+req = http.request("https://example.hashicorp.com")
+resp = http.send(req)
+main = rule { resp.body contains "something" }
+```
+
+### client.accept_status_codes(list)
+
+Configures a client to not automatically cause a policy failure if
+the resulting response's status code is in `list`. Any status code received
+that is not present in this list will trigger a runtime error.
+
+By default, any request response that is not a successful "200 OK" response
+causes a policy error. This is for convenience, as the most common scenario
+by far is to receive a response and immediately signal a policy error if the
+status code is not 200. Use this scoping to specify a list of non-redirect
+HTTP codes that you wish to accept without error and evaluate the response
+yourself.
+
+Redirects are not considered final status codes in this context. That is,
+redirects are always followed up to the maximum allowed value (see [`Redirects`](#redirects))
+and the final response code in the redirect chain is validated against
+`list`.
+
+```sentinel
+resp = http.accept_status_codes([200, 404]).get(url)
+main = rule { resp.status_code is 404 }
+```
+
+### client.accepted_status_codes
+
+Returns a list of the client's accepted status codes.
+
+```sentinel
+client = http.accept_status_codes([200, 404])
+main = rule { client.accepted_status_codes contains 404 }
+```
+
+### client.with_retries(integer)
+
+Sets the maximum number of retries, specified by `integer`, that should be
+attempted on an unsuccessful request. An unsuccessful request is considered
+to be any 5xx response except `501 (Not Implemented)` or a request timeout.
+By default, one retry is attempted.
+
+The maximum number of retries is 10, for a total of 11 request attempts.
+When a request exceeds the maximum number of retries without a response, the
+result is a policy error. Specifying a number larger than this will result
+in a runtime error.
+
+Between each retry, an exponentially increasing delay is observed, allowing
+time for the server to recover. This delay starts at 1 second for the first
+retry and increases each attempt thereafter. The maximum delay for a single
+attempt is 30 seconds.
+
+Retries can be disabled by specifying 0.
+
+### client.retries
+
+Returns the client's configured number of retries as an integer.
+
+### client.with_timeout(integer)
+
+Sets the request timeout to the number of seconds indicated by `integer`.
+
+Each individual request performed by the HTTP client will be limited by the
+given request timeout. This timeout includes connection time, any redirects,
+and reading the response body.
+
+A maximum of 30 seconds is allowed. Specifying a number larger than this
+will result in a runtime error.
+
+By default, requests will time out after 10 seconds.
+
+### client.timeout
+
+Returns the client's configured timeout in whole seconds as an integer.
+
+### client.without_certificate_verification()
+
+Skips verification of TLS certificates during secure HTTP operations,
+allowing for requests to `https://` endpoints which are secured with a TLS
+certificate signed by an untrusted certificate authority. Takes no arguments.
+
+By default, the HTTP client will trigger a runtime error if a TLS certificate
+presented by a server is from an untrusted certificate authority.
+
+Do not change this setting unless you are aware of the risks involved.
+Without verification, TLS accepts any certificate presented by the server
+and any host name in that certificate. In this mode, TLS is susceptible to
+man-in-the-middle attacks. This option should only be used for testing or in
+trusted networks with self-signed certificates.
+
+```sentinel
+http.without_certificate_verification().get(url)
+```
+
+### client.certificate_verification
+
+Returns true if the client requires TLS certificates to be verifiable.
+
+## Type: request
+
+### request.url
+
+Returns the request URL as a string.
+
+### request.headers
+
+Returns the configured request headers as a string-keyed map of strings.
+
+### request.body
+
+Returns the configured body as a string.
+
+### request.with_header(key, value)
+
+Returns a `request` with the header `key` (string) set to `value` (string).
+
+Values are overridden on subsequent calls to the same `key`. To specify
+multiple values, use the existing value when setting the new one.
+
+```sentinel
+original = http.request("http://example.hashicorp.com").with_header("Foo", "bar")
+overridden = original.with_header("Foo", "baz")
+multi_value = original.with_header("Foo", original.headers["Foo"] + ",baz")
+
+main = rule {
+ original.headers["Foo"] is "bar" and
+ overridden.headers["Foo"] is "baz" and
+ multi_value.headers["Foo"] is "bar,baz"
+}
+```
+
+### request.with_basic_auth(username, password)
+
+Returns a `request` with the `Authorization` header set to use HTTP Basic
+Authentication with the provided username and password.
+
+Note that with HTTP Basic Authentication the provided username and password
+are encoded, but not encrypted.
+
+### request.with_body(body)
+
+Returns a `request` with the `body` set. This value will then be sent as a body
+as part of the request. Commonly used along with the [`post()`](#client-post-url_or_request) function.
+
+```sentinel
+req = http.request("http://example.hashicorp.com")
+ .with_header("Content-Type", "application/json")
+ .with_body(json.marshal({ "foo": "bar" }))
+
+resp = http.post(req)
+```
+
+## Type: response
+
+### response.status_code
+
+The response status code as an integer, e.g. `200`.
+
+### response.headers
+
+The response headers as a map.
+
+### response.body
+
+The response body as a string. Callers should unmarshal the content to a useful
+representation as necessary based on the content type. For example, if the
+response is in JSON:
+
+```sentinel
+import "http"
+import "json"
+
+# This example endpoint returns {"some_key": true}
+req = http.request("https://example.hashicorp.com/some-json")
+
+resp = json.unmarshal(http.get(req).body)
+main = rule { keys(resp) contains "some_key" and resp["some_key"] is true }
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/imports/index.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/imports/index.mdx
new file mode 100644
index 0000000000..e45bf81b81
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/imports/index.mdx
@@ -0,0 +1,18 @@
+---
+page_title: Sentinel Language - Standard Imports
+sidebar_current: docs-imports
+description: >-
+ Standard Imports are the built-in imports that are available to all Sentinel
+ policies.
+layout: docs
+---
+
+# Standard Imports
+
+Standard Imports are the built-in [imports](/sentinel/language/imports)
+that are available to all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in import.
+
+Sentinel-embedded applications can choose to allow or deny certain
+standard imports. Please reference the documentation for the Sentinel-enabled
+application you're using to determine if all standard imports are available.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/imports/json.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/imports/json.mdx
new file mode 100644
index 0000000000..3355458f14
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/imports/json.mdx
@@ -0,0 +1,31 @@
+---
+page_title: 'Import: json'
+sidebar_current: docs-imports-json
+description: The json import enables a Sentinel policy to parse and access a JSON document.
+layout: docs
+---
+
+# Import: json
+
+The json import enables a Sentinel policy to parse and access a JSON
+document.
+
+### json.unmarshal(obj)
+
+Unmarshals the JSON object `obj` into a native Sentinel structure.
+
+All native JSON types can be represented perfectly as Sentinel native types.
+
+```sentinel
+// Typically the input for this would come from an external source.
+config = json.unmarshal("{ \"foo\": 42 }")
+config.foo // 42
+config.bar // undefined (as usual for accessing a non-existent map key)
+```
+
+### json.marshal(obj)
+
+Marshals the Sentinel object `obj` into a JSON object encoded as a string.
+
+Functions and `undefined` cannot be natively encoded as JSON. If values of
+either type are found in the structure, this will return undefined.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/imports/runtime.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/imports/runtime.mdx
new file mode 100644
index 0000000000..6493bbbb22
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/imports/runtime.mdx
@@ -0,0 +1,16 @@
+---
+page_title: 'Import: runtime'
+sidebar_current: docs-imports-runtime
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: runtime
+
+The runtime import contains various information about the Sentinel runtime. It
+can be used to get information about the runtime as it's embedded in the CLI or
+a specific integration, such as validating a specific runtime version.
+
+### runtime.version
+
+Returns the version of Sentinel within this implementation.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/imports/sockaddr.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/imports/sockaddr.mdx
new file mode 100644
index 0000000000..a6842ba5f0
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/imports/sockaddr.mdx
@@ -0,0 +1,41 @@
+---
+page_title: 'Import: sockaddr'
+sidebar_current: docs-imports-sockaddr
+description: The sockaddr import makes working with IP addresses easy.
+layout: docs
+---
+
+# Import: sockaddr
+
+The sockaddr import makes working with IP addresses easy.
+
+### sockaddr.is_contained(outer, inner)
+
+The is_contained function returns true if `inner` is an address that
+is contained within `outer`.
+
+```sentinel
+sockaddr.is_contained("192.168.0.0/24", "192.168.0.32") // true
+sockaddr.is_contained("192.168.0.0/24", "192.174.1.1") // false
+```
+
+### sockaddr.is_equal(addr1, addr2)
+
+The is_equal function returns true if addr1 is equal to addr2.
+
+```sentinel
+sockaddr.is_equal("192.168.12.24", "192.168.12.24") // true
+```
+
+### sockaddr.is_ipv4(addr)
+
+The is_ipv4 function returns true if addr is an IPv4 address.
+
+```sentinel
+sockaddr.is_ipv4("192.168.12.24") // true
+sockaddr.is_ipv4("2001:0db8:85a3:0000:0000:8a2e:0370:7334") // false
+```
+
+### sockaddr.is_ipv6(addr)
+
+The is_ipv6 function returns true if addr is an IPv6 address.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/imports/strings.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/imports/strings.mdx
new file mode 100644
index 0000000000..e1ba9a9e2a
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/imports/strings.mdx
@@ -0,0 +1,150 @@
+---
+page_title: 'Import: strings'
+sidebar_current: docs-imports-strings
+description: The strings import exposes common string operations.
+layout: docs
+---
+
+# Import: strings
+
+The strings import exposes common string operations.
+
+### strings.has_prefix(s, prefix)
+
+Returns true if `s` starts with `prefix`.
+
+```sentinel
+strings.has_prefix("billing-id", "billing-") // true
+strings.has_prefix("bill-id", "billing-") // false
+```
+
+### strings.has_suffix(s, suffix)
+
+Returns true if `s` ends with `suffix`.
+
+```sentinel
+strings.has_suffix("billing-id", "id") // true
+strings.has_suffix("billing-name", "id") // false
+```
+
+### strings.join(a, sep)
+
+Joins a list with the string supplied by `sep`.
+
+Multi-dimensional lists are supported, and non-string primitive
+types will be converted to their string equivalents. Maps and
+other complex non-list types are not supported.
+
+```sentinel
+strings.join(["foo", "bar", "baz"], ".") // "foo.bar.baz"
+strings.join([["foo", "bar"], "baz"], ".") // "foo.bar.baz"
+strings.join(["a", 1, 1.01, true], ",") // "a,1,1.01,true"
+```
+
+### strings.replace(s, old, new, times)
+
+Replace the substring `old` with the substring `new` in the string `s`
+by how many times the int parameter `times` is specified.
+If the string `s` doesn't the substrings or if `times` is zero,
+then the string is returned without any changes applied.
+
+```
+strings.replace("foobar", "foo", "bar") // "barbar"
+strings.replace("foofoofoobar", "foo", "bar", 1) // "barfoofoobar"
+strings.replace("foofoofoobar", "foo", "bar", 2) // "barbarfoobar"
+strings.replace("foobar", "test", "bar") // "foobar"
+strings.replace("foobar", "foo", "bar", 0) // "foobar"
+```
+
+### strings.trim(s, cutset)
+
+Trim the leading and trailing characters in `cutset` from the string `s`.
+If the string doesn't have the cutset, then the string is returned
+unmodified.
+
+```
+strings.trim("iiifoobariii", "i") // "foobar"
+strings.trim("!1!bar foo!!1", "!1") // "bar foo"
+```
+
+### strings.trim_left(s, cutset)
+
+Trim the leading characters contained in `cutset` from the string `s`.
+If the string doesn't have the cutset, then the string is returned
+unmodified.
+
+```sentinel
+strings.trim_left("aaaaaaaafoo", "a") // "foo"
+strings.trim_left("!!!bar!!!", "!") // "bar!!!"
+```
+
+### strings.trim_right(s, cutset)
+
+Trim the trailing characters contained in the `cutset` from the
+string `s`. If the string doesn't have the cutset, then the string
+is returned unmodified.
+
+```sentinel
+strings.trim_right("foo_bar...", ".") // "foo_bar"
+strings.trim_right("billing---","-") // "billing"
+```
+
+### strings.trim_space(s)
+
+Trim leading and trailing white space from the string `s`. If the
+string doesn't have any surrounding white space, then the string is
+returned unmodified.
+
+```sentinel
+strings.trim_space(" foo ") // "foo"
+strings.trim_space(" bar foo ") // "bar foo"
+```
+
+### strings.trim_prefix(s, prefix)
+
+Trim the prefix from the string `s`. If the string doesn't have the
+prefix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_prefix("billing-id", "billing-") // "id"
+strings.trim_prefix("bill-id", "billing-") // "bill-id"
+```
+
+### strings.trim_suffix(s, suffix)
+
+Trim the suffix from the string `s`. If the string doesn't have the
+suffix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_suffix("billing-id", "-id") // "billing"
+strings.trim_suffix("bill-id", "-foo") // "bill-id"
+```
+
+### strings.to_lower(s)
+
+Lowercase the string s.
+
+```sentinel
+strings.to_lower("FoO") // "foo"
+```
+
+### strings.to_upper(s)
+
+Uppercase the string s.
+
+```sentinel
+strings.to_upper("FoO") // "FOO"
+```
+
+### strings.split(s, sep)
+
+Split the string 's' via a substring 'sep'. If 'sep' is not contained in
+'s', the return value will be a single-element list containing only 's'.
+As a special-case, if the input is already a list, it will be returned
+as-is. This allows calling the split function when not knowing if the input
+is already a list or not.
+
+```sentinel
+strings.split("foo,bar,baz", ",") // ["foo", "bar", "baz"]
+strings.split(["foo", "bar", "baz"], ",") // ["foo", "bar", "baz"]
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/imports/time.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/imports/time.mdx
new file mode 100644
index 0000000000..3a675e2bbd
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/imports/time.mdx
@@ -0,0 +1,257 @@
+---
+page_title: 'Import: time'
+sidebar_current: docs-imports-time
+description: The time import provides access to the execution time and time functions.
+layout: docs
+---
+
+# Import: time
+
+The time import provides access to the execution time and time functions.
+
+During the execution of a policy the time is fixed to the moment of
+execution. This gives the illusion that a policy instantly executes at a
+single moment of time.
+
+The import uses Coordinated Universal Time (UTC).
+
+As an example of this, if a policy accessed `time.now.second` multiple times,
+for each access the same value would be returned even if they are several seconds apart.
+This is the execution `timespace`, which is mapped to `time.now`.
+
+It is also possible to run functions on a custom time by using the
+`time.load()` function to create a new timespace from the specified value.
+See the documentation for available actions on timespaces.
+
+Times specified as the reference time or via the `time.load()` function can
+be specified in a `timeish` fashion. This is either one of:
+
+- The number of seconds since the Unix epoch (Thursday, 1 January 1970,
+ 00:00:00 UTC),
+- A timestamp matching the format outlined in RFC3339, either in the
+ format `2006-01-02T15:04:05+07:00`, which includes a timezone offset, or
+ `2006-01-02T15:04:05Z`, which indicates UTC.
+
+### time.now
+
+Create a `timespace` locked either to execution time or, if set, the
+reference time. In a given execution, this will always produce the same
+value.
+
+```sentinel
+time.now // {"day": 17, "hour": 23, "minute": 19, "month": 11 ... }
+```
+
+### time.load(timeish)
+
+Create a timespace using the given value. Please see the import
+documentation for valid values for "timeish".
+
+```sentinel
+time.load("2019-11-16T01:20:30Z") // {"day": 16, "hour": 1, "minute": 20, "month": 11 ... }
+```
+
+### time.nanosecond
+
+A nanosecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.nanosecond * 1) // 2019-11-16T01:20:30.000000001Z
+```
+
+### time.microsecond
+
+A microsecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.microsecond * 1) // 2019-11-16T01:20:30.000001Z
+```
+
+### time.millisecond
+
+A millisecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.millisecond * 1) // 2019-11-16T01:20:30.001Z
+```
+
+### time.second
+
+A second duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.second * 1) // 2019-11-16T01:20:31Z
+```
+
+### time.minute
+
+A minute for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.minute * 1) // 2019-11-16T01:21:30Z
+```
+
+### time.hour
+
+An hour duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+## Type: timespace
+
+### timespace.hour
+
+The hour from 0 to 23.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").hour // 1
+```
+
+### timespace.minute
+
+The minute from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").minute // 20
+```
+
+### timespace.second
+
+The second from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").second // 30
+```
+
+### timespace.day
+
+The day of the month, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").day // 16
+```
+
+### timespace.month
+
+The month of the year as an integer, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month // 11
+```
+
+### timespace.month_name
+
+The name of the month of the year, in English. Example: `January`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month_name // November
+```
+
+### timespace.weekday
+
+The weekday as an integer, where 0 is Sunday and 6 is Saturday.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday // 6
+```
+
+### timespace.weekday_name
+
+The name of the day of the week, in English. Example: `Sunday`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday_name // Saturday
+```
+
+### timespace.year
+
+The year as an integer.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").year // 2019
+```
+
+### timespace.unix
+
+The number of seconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix // 1573867230
+```
+
+### timespace.unix_nano
+
+The number of nanoseconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix_nano // 1573867230000000000
+```
+
+### timespace.zone
+
+The timezone offset, in seconds. This can be a negative number to indicate
+time west of UTC.
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone // -28800
+```
+
+### timespace.zone_string
+
+The timezone offset, in the format `+HH:MM` (example: `-08:00` for PST).
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone_string // -08:00
+```
+
+### timespace.before(timeish_or_timespace)
+
+Check if the time in the timespace is before the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").before("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.after(timeish_or_timespace)
+
+Check if the time in the timespace is after the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").after("2019-11-15T01:20:30Z") // true
+```
+
+### timespace.equal(timeish_or_timespace)
+
+Check if two times are equivalent
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").equal("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.add(duration)
+
+Add a duration in nanoseconds to the time in the timespace and return a new timespace. The
+duration can be negative. The duration should be specified as an integer
+multiplied with the correct unit.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+### timespace.sub(timeish_or_timespace)
+
+Subtract a time from the time in the timespace and return a duration in nanoseconds.
+
+```sentinel
+// Gives a duration of 3 hours.
+duration = time.load(
+ "2019-11-16T01:20:30Z",
+).sub("2019-11-16T04:20:30Z")
+
+// Returns 2019-11-16T01:20:30Z
+time.load(
+ "2019-11-16T04:20:30Z",
+).add(duration)
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/imports/types.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/imports/types.mdx
new file mode 100644
index 0000000000..fcfdbd2ae5
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/imports/types.mdx
@@ -0,0 +1,35 @@
+---
+page_title: 'Import: types'
+sidebar_current: docs-imports-types
+description: The types import enables a Sentinel policy to parse an object's type.
+layout: docs
+---
+
+# Import: types
+
+The types import enables a Sentinel policy to parse an object's type.
+
+### types.type_of(obj)
+
+Returns the object's type as a string
+
+```sentinel playground
+import "types"
+
+// Typically the inputs would come from an external source.
+is_boolean = rule { types.type_of(true) is "bool" }
+is_string = rule { types.type_of("Hello!") is "string" }
+is_integer = rule { types.type_of(42) is "int" }
+is_float = rule { types.type_of(42.123) is "float" }
+is_null = rule { types.type_of(null) is "null" }
+is_undefined = rule { types.type_of(undefined) is "undefined" }
+
+main = rule {
+ is_boolean and
+ is_string and
+ is_integer and
+ is_float and
+ is_null and
+ is_undefined
+}
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/imports/units.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/imports/units.mdx
new file mode 100644
index 0000000000..e2ad3442f9
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/imports/units.mdx
@@ -0,0 +1,39 @@
+---
+page_title: 'Import: units'
+sidebar_current: docs-imports-units
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: units
+
+The units import provides access to quick calculations for various byte
+units.
+
+Note that this import uses base-2 terminology:
+
+- One kilobyte is 1024 bytes
+- One megabyte is 1024^2 = 1048576 bytes
+- One gigabyte is 1024^3 = 1073741824 bytes
+- One terabyte is 1024^4 = 1099511627776 bytes
+- One petabyte is 1024^5 = 1125899906842624 bytes
+
+### units.kilobyte
+
+The base-2 representation of a kilobyte (1024 bytes).
+
+### units.megabyte
+
+The base-2 representation of a megabyte (1048576 bytes).
+
+### units.gigabyte
+
+The base-2 representation of a gigabyte (1073741824 bytes).
+
+### units.terabyte
+
+The base-2 representation of a terabyte (1099511627776 bytes).
+
+### units.petabyte
+
+The base-2 representation of a petabyte (1125899906842624 bytes).
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/imports/version.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/imports/version.mdx
new file mode 100644
index 0000000000..59366c1087
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/imports/version.mdx
@@ -0,0 +1,151 @@
+---
+page_title: 'Import: version'
+sidebar_current: docs-imports-version
+description: |-
+ The version import provides functions for parsing versions and version constraints,
+ and verifying versions against a set of constraints.
+layout: docs
+---
+
+# Import: version
+
+The version import provides functions for parsing versions and version constraints,
+and verifying versions against a set of constraints.
+
+Versions are created through the `new` function, which accepts a string type in dotted-tri format (i.e. 1.0.0-alpha.1+001) that can represent a version.
+
+### version.new(v)
+
+Constructs a version from string value.
+
+```sentinel
+version.new("1.0.0-alpha.1+001")
+```
+
+### version.major
+
+An integer representation of the Major number for a given version .
+
+```sentinel
+version.new("1.0.0-alpha.1+001").major // 1
+```
+
+### version.minor
+
+An integer representation of the Minor number for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").minor // 0
+```
+
+### version.patch
+
+An integer representation of the Patch number for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").patch // 0
+```
+
+### version.prerelease
+
+A string representation of the Prerelease for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").prerelease // "alpha.1"
+```
+
+### version.metadata
+
+A string representation of the Metadata for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").metadata // "001"
+```
+
+### version.version
+
+A string representation of the version including pre-release and metadata information.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").version // "1.0.0-alpha.1+001"
+```
+
+### version.greater_than(v)
+
+Tests that the version is greater than a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").greater_than("0.12.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").gt("1.0.1-alpha.1+001") // False
+```
+
+### version.greater_than_or_equals(v)
+
+Tests that the version is greater than or equal to a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").greater_than_or_equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").gte("1.0.1-alpha.1+001") // False
+```
+
+### version.less_than(v)
+
+Tests that the version is less than a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").less_than("1.0.1-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").lt("0.12.0-alpha.1+001") // False
+```
+
+### version.less_than_or_equals(v)
+
+Tests that the version is less than or equal to a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").less_than_or_equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").lte("0.12.0-alpha.1+001") // False
+```
+
+### version.less_than_or_equals(v)
+
+Tests that the version equals a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").eq("0.12.0-alpha.1+001") // False
+```
+
+### version.compare(v)
+
+Compares one version with a given version. Compare returns -1, 0, or 1 if the version is less, equal, or greater than the other version, respectively.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").compare("0.12.0-alpha.1+001") // -1
+version.new("1.0.0-alpha.1+001").compare("1.0.0-alpha.1+001") // 0
+version.new("0.12.0-alpha.1+001").cmp("1.0.0-alpha.1+001") // 1
+```
+
+### version.satisfies(v, x)
+
+Tests that a version adheres to one or more version constraints. Satisfies supports the following restriction operators:
+
+- =
+- !=
+- \>
+- <
+- \>=
+- <=
+- ~>
+
+Satisfies supports the following usage scenarios:
+
+- A constraint with a pre-release can only match a pre-release version that contains the same major, minor and patch identifiers.
+- A constraint without a pre-release can only match a version without a pre-release.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").satisfies("> 1.0.0-alpha.1+001") // false
+version.new("1.0.0-alpha.1+001").satisfies(">= 1.0.0-alpha.1+001") // true
+version.new("1.0.0-alpha.1+001").satisfies(">= 1.0.0-alpha.1+001, < 1.0.0-beta+001") // true
+version.new("1.0.0").sat("= 1.0.0") // true
+version.new("0.12.7").sat("~> 0.12.0") // true
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/index.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/index.mdx
new file mode 100644
index 0000000000..c68ccc265b
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/index.mdx
@@ -0,0 +1,31 @@
+---
+layout: 'docs'
+page_title: 'Documentation'
+sidebar_current: 'docs-home'
+description: |-
+ Sentinel is a language and framework for policy built to be embedded in existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+["Why Sentinel?"](/sentinel/why) to understand what Sentinel is, how it
+compares to other software, and more.
+
+
\ No newline at end of file
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/intro.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/intro.mdx
new file mode 100644
index 0000000000..b9a2675f2b
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/intro.mdx
@@ -0,0 +1,75 @@
+---
+page_title: Introduction to Sentinel
+sidebar_current: docs-intro
+description: >-
+ Sentinel is a language and framework for policy built to be embedded in
+ existing software to enable fine-grained, logic-based policy decisions.
+layout: docs
+---
+
+# Introduction to Sentinel
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+["Why Sentinel?"](/sentinel/docs/why) to understand what Sentinel is, how it
+compares to other software, and more.
+
+## What is Sentinel?
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+Sentinel provides a language for writing policy and a workflow for developing
+and testing policies independent of the software they'll be written to. We
+also provide a framework for developers to build plugins that allow a
+Sentinel-enabled system to access external information to make policy
+decisions.
+
+Most systems today have some degree of access control. You are able to define
+identities and what they have access to. These ACL systems solve an immediate
+and necessary problem of locking down a system in very broad strokes. Sentinel
+is a reusable system for more advanced software policy decisions. Sentinel
+enables:
+
+- **Fine-Grained Policy**: Most ACL systems only enable coarse-grained
+ behaviors: "read", "write", etc. Sentinel enables fine-grained behavior such
+ as disallowing a certain API call when specific parameters are present.
+
+- **Logic-Based Policy**: You can write policy using full
+ conditional logic. For example, you may only allow a certain application behavior
+ on Monday to Thursday unless there is a manager override.
+
+- **Accessing External Information**: Sentinel can source external information
+ to be used in policy decisions. For example, a policy that restricts the size
+ of a payload may read data from [Consul](https://www.consul.io) to determine
+ the payload size limit.
+
+- **Enforcement levels**: Sentinel allows policies to be defined along
+ with an "enforcement level" that dictates the pass/fail behavior of a policy.
+ Advisory policies warn if they fail, soft mandatory policies can have their
+ failures overridden, and hard mandatory policies must pass under all
+ circumstances. Having this as a built-in concept enables you to model
+ policy more accurately and completely for your organization.
+
+
+
+
+## Next steps
+
+Refer the page on [Why Sentinel?](/sentinel/why) to learn more about the origins of Sentinel.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/language/arithmetic.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/language/arithmetic.mdx
new file mode 100644
index 0000000000..c49f2fd0ce
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/language/arithmetic.mdx
@@ -0,0 +1,67 @@
+---
+page_title: Sentinel Language - Arithmetic
+sidebar_current: docs-language-arith
+description: >-
+ Sentinel supports arithmetic operators for integers and floats. Sentinel
+ supports sum, difference, product, quotient, and remainder.
+layout: docs
+---
+
+# Language: Arithmetic
+
+Sentinel supports arithmetic operators for integers and floats. Sentinel
+supports sum, difference, product, quotient, and remainder.
+
+```plaintext
++ sum
+- difference
+* product
+/ quotient
+% remainder
+```
+
+These operators work in a typical infix style:
+
+```sentinel
+4 + 8 // 12
+8 * 2 // 16
+8 / 4 // 2
+8 / 5 // 1
+8 % 5 // 3
+```
+
+## Order of Operations
+
+Arithmetic follows a standard mathematical order of operations.
+Grouping with parentheses can be used to affect ordering.
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+(4 + 5) * 2 // 18
+```
+
+A full table of operator precendence can be found on the
+[boolean expressions page](/sentinel/language/boolexpr). This
+shows how arithmetic operators relate to other operators.
+
+## Integer Division
+
+Integer division with a remainder rounds down to the nearest integer.
+Example: `8 / 3 is 2`.
+
+If the divisor is zero, an error occurs.
+
+## Mixed Numeric Operations
+
+Mixed numeric operations between integer and floating-point values are
+permitted. The result is a floating-point operation with the integer converted
+to a floating-point value for purposes of calculation.
+
+```sentinel
+import "types"
+
+a = 1.1 + 1 // 2.1
+types.type_of(a) // "float"
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/language/boolexpr.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/language/boolexpr.mdx
new file mode 100644
index 0000000000..f1c518c3bf
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/language/boolexpr.mdx
@@ -0,0 +1,225 @@
+---
+page_title: Sentinel Language - Boolean Expressions
+sidebar_current: docs-language-boolexpr
+description: >-
+ Boolean expressions are expressions that evaluate to a boolean value from the
+ result of a combination of one or more comparisons and logical operators.
+layout: docs
+---
+
+# Language: Boolean Expressions
+
+Boolean expressions are expressions that evaluate to a boolean value
+from the result of a combination of one or more comparisons and logical
+operators. Boolean expressions form the basis of policy since policy can be broken
+down to a set of logical decisions that turn into true or false.
+
+A single boolean expression is the body of a [rule](/sentinel/language/rules).
+Additionally, boolean expressions are used with [conditionals](/sentinel/language/conditionals),
+and more.
+
+## Order of Operations
+
+The precedence of operators is shown below. Operators with higher
+precedence values (larger numbers) are evaluated first. Operators with
+the same precedence value are evaluated left-to-right.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Examples of this precedence are shown below:
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+```
+
+## Logical Operators
+
+Logical operators are used to change or combine logical expressions.
+There are three binary operators `and`, `or`, and `xor`. There is
+a single unary operator `not` (which can also be specified as `!`).
+
+The binary operators require two boolean operands and the unary
+operator requires a single boolean operand. In both case, the operands
+are values or expressions that are boolean types.
+
+Below is a basic explanation of the logical operators directly from
+the specification:
+
+```sentinel
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+Using parentheses to group boolean expressions, you can combine
+boolean expressions to become more complex or affect ordering:
+
+```sentinel
+(p and q) or r
+p and (q or r)
+```
+
+## Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+For any comparison, the two operands must be equivalent types. The one
+exception are integers and floats. When an integer is compared with
+a float, the integer is promoted to a floating point value.
+
+The available comparison operators are:
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+The behavior of `is` with `==` and `is not` with `!=` is identical.
+
+Example comparisons:
+
+```sentinel
+name is "Mitchell"
+idnumber > 42
+idnumber <= 12
+```
+
+Using the logical operators, these can be combined:
+
+```sentinel
+name is "Mitchell" and idnumber > 42
+```
+
+The language specification provides more detail on exactly
+[how comparison behaves](/sentinel/language/spec#comparison-operators).
+
+## Set Operators
+
+The set operators `contains` and `in` test for inclusion in a collection
+(a list or map).
+
+Set operators may 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.
+
+`contains` tests if the left-hand collection contains the right-hand value.
+`in` tests if the right-hand collection contains the left-hand value. For
+maps, "contains" looks at the keys, not the values.
+
+Examples:
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+```
+
+## Matches Operator
+
+The `matches` operator tests if a string matches a regular expression.
+The `matches` operator can be negated by prefixing the operator with
+`not`, such as `not matches`.
+
+The left-hand value is the string to test. The right-hand value is
+a string value representing a regular expression. The syntax of the regular
+expression is the same general syntax used by Python, Ruby, and other languages.
+The precise syntax accepted is the syntax accepted by RE2.
+
+Regular expressions are not anchored by default; any anchoring must be
+explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+## Any, All Expressions
+
+`any` and `all` expressions allow testing that any or all elements of a
+collection match some boolean expression. The result of an `any` and `all`
+expression is a boolean.
+
+`any` returns the boolean value `true` if any value in the collection expression
+results in the body expression evaluating to `true`. If the body expression
+evalutes to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression
+result in the body expression evaluating to `true`. If any value in the
+collection expression result in the body expression evaluating to `false`,
+the `all` expression returns `false`.
+
+For empty collections, `any` returns `false` and `all` returns `true`.
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" }
+any group.tasks as t { t.driver is "vmware" }
+```
+
+Since `any` and `all` expressions are themselves boolean expressions, they
+can be combined with other operators:
+
+```sentinel
+any ["a", "b"] as char { char is "a" } or
+other_value is "another"
+```
+
+## Emptiness Comparison
+
+The expressions `is empty` and `is not empty` provide a convenience
+method for determining the emptiness of a value. It supports the same types
+as the [built-in length](/sentinel/functions/length), collections and strings.
+
+```sentinel
+[] is empty // true
+[] is not empty // false
+["foo"] is empty // false
+["foo"] is not empty // true
+undefined is empty // undefined
+undefined is not empty // undefined
+```
+
+## Defined Comparison
+
+The expressions `is defined` and `is not defined` provide a convenience
+method for determining if a value has been defined. In other words, any value
+other than `undefined` can be considered as `defined`.
+
+```sentinel
+[] is defined // true
+4 is defined // true
+true is defined // true
+{} is defined // true
+undefined is defined // false
+[] is not defined // false
+4 is not defined // false
+true is not defined // false
+undefined is not defined // true
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/language/collection-operations.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/language/collection-operations.mdx
new file mode 100644
index 0000000000..41da8f3584
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/language/collection-operations.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Sentinel Language - Collection Operations
+sidebar_current: docs-language-collection-operations
+description: |-
+ Operations for interacting with collections (list, map).
+layout: docs
+---
+
+# Language: Collection Operations
+
+Collection operations are expressions that are performed on a list or map to
+return a variation of the initial data.
+
+At the moment, `filter` is the only collection operation available.
+
+## Filter Expression
+
+`filter` is a [quantifier
+expression](/sentinel/language/spec#quantifier-expressions-any-all-filter-map) that
+returns a subset of the provided collection. Only elements whose filter body
+returns true will be returned. If any of the elements filter body returns
+undefined, the final result will be undefined.
+
+Filter uses the same syntax as the `any` and `all` [boolean
+expressions](/sentinel/language/boolexpr#any-all-expressions):
+
+```sentinel
+filter list as value { condition } // Single-iterator, list
+filter list as idx, value { condition } // Double-iterator, list
+
+filter map as key { condition } // Single-iterator, map
+filter map as key, value { condition } // Double-iterator, map
+```
+
+Examples:
+
+```sentinel
+l = [1, 1, 2, 3, 5, 8]
+evens = filter l as v { v % 2 is 0 } // [2, 8]
+
+m = { "a": "foo", "b": "bar" }
+matched_foo = filter m as _, v { v is "foo" } // { "a": "foo" }
+```
+
+## Map Expression
+
+`map` is a [quantifier
+expression](/sentinel/language/spec#quantifier-expressions-any-all-filter-map) that
+returns a list, regardless of the input collection type. Each element within the
+input collection is evaluted according to the map expression body and appended
+to the result.
+
+```sentinel
+l = [1, 2]
+r = map l as v { v % 2 } // [false, true]
+
+m = { "a": "foo", "b": "bar" }
+r = map m as k, v { v } // ["foo", "bar"]
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/language/conditionals.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/language/conditionals.mdx
new file mode 100644
index 0000000000..1736795dbb
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/language/conditionals.mdx
@@ -0,0 +1,161 @@
+---
+page_title: Sentinel Language - Conditionals
+sidebar_current: docs-language-conditional
+description: |-
+ Conditional statements allow your policy to behave differently depending on a condition.
+layout: docs
+---
+
+# Language: Conditionals
+
+Conditional statements allow your policy to behave differently depending
+on a condition.
+
+Conditional statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## If Statements
+
+`if` statements only execute their bodies if a condition is met.
+The syntax of an `if` statement is:
+
+```sentinel
+if condition {
+ // ... this is executed if condition is true
+}
+```
+
+The `condition` must result in a boolean, such as by calling a function
+or evaluating a [boolean expression](/sentinel/language/boolexpr). If
+the `condition` is `true`, the body (within the `{}`) is executed. Otherwise,
+the body is skipped.
+
+Examples:
+
+```sentinel
+// This would execute the body
+value = 12
+if value is 18 {
+ print("condition met")
+}
+
+// Direct boolean values can be used
+value = true
+if value {
+ print("condition met")
+}
+
+// This would not execute the body since the boolean expression will
+// result in undefined.
+value = {}
+if value["key"] > 12 {
+ print("condition met")
+}
+```
+
+### Else, Else If
+
+An `else` clause can be given to an `if` statement to execute a body
+in the case the condition is _not met_. By putting another `if` statement
+directly after the `else`, multiple conditions can be tested for.
+The syntax is:
+
+```sentinel
+if condition {
+ // ...
+} else {
+ // ...
+}
+
+if condition {
+ // ...
+} else if other_condition {
+ // ...
+} else {
+ // ...
+}
+```
+
+### Scoping
+
+The body of an `if` statement does not create a new
+[scope](/sentinel/language/scope). Any variables assigned within the body
+of an if statement will modify the scope that the `if` statement itself
+is in.
+
+Example:
+
+```sentinel
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+```sentinel
+a = 18
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+## Case Statements
+
+`case` statements are a selection control mechanism that execute a clause based
+on matching expressions. It is worth noting that the expression for `case` is
+optional. When no expression is provided, it defaults the expression to `true`.
+Additionally, the order of clauses is important, as they are evaluated from top
+to bottom, executing the first match.
+The syntax of a case statement is:
+
+```sentinel
+case expression {
+ when clause_expression:
+ // executed when clause_expression and expression are equal
+ else:
+ // executed if no clause matches expression
+}
+```
+
+### When Clause
+
+Any clause that has an expression for comparison must use the `when` keyword.
+It accepts a list of expressions, seperated by a `,`.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+}
+```
+
+```sentinel
+case {
+ when x > 40:
+ return true
+}
+```
+
+### Else Clause
+
+The `else` keyword allows for capturing any expressions that have no matching
+`when` clause.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+ else:
+ return false
+}
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/language/functions.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/language/functions.mdx
new file mode 100644
index 0000000000..8214078b6b
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/language/functions.mdx
@@ -0,0 +1,229 @@
+---
+page_title: Sentinel Language - Functions
+sidebar_current: docs-language-functions
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Functions
+
+Functions allow you to create reusable code to perform computations.
+
+Below is a trivial example function that doubles a number and shows
+the main rule using the function:
+
+```sentinel playground
+double = func(x) {
+ return x * 2
+}
+
+main = rule { double(12) is 24 }
+```
+
+Functions may contain any expressions,
+[conditionals](/sentinel/language/conditionals),
+and [loops](/sentinel/language/loops). They must return a value
+at the end of their execution. If no reasonable return value exists,
+the function should return [undefined](/sentinel/language/undefined).
+
+## Function Types
+
+### Named Functions
+
+-> **NOTE:** Named functions must be created within the [package scope](/sentinel/language/scope#package-scope).
+
+Named functions are declared using the `func` keyword as its own statement.
+They provide a safe method of creating functions and have additional
+restrictions that do not apply to anonymous functions.
+
+Firstly, named functions cannot be re-assigned, and also cannot use a name
+that is already used elsewhere. For instance, the below example will error due
+to the attempt to reassign the named function identifier to a new value:
+
+```sentinel
+func sum(a, b) {
+ return a + b
+}
+
+sum = 4
+```
+
+Additionally, the following will error due to the named function attempting
+to make use of an already assigned identifier:
+
+```sentinel
+sum = 4
+
+func sum(a, b) {
+ return a + b
+}
+```
+
+Named functions are helpful for policy authors to declare critical functions
+whose value or implementation should not be changed.
+
+### Anonymous Functions
+
+An anonymous function is created by assigning a variable to a `func`. The
+variable can be reassigned at any time including to different value types.
+Anonymous functions are helpful for use cases like closures, where a function
+can return another function.
+
+```sentinel
+func makeAdder(a) {
+ return func(b) {
+ return a + b
+ }
+}
+```
+
+## Creating a Function
+
+A function can have zero or more parameters. These parameters are specified
+within parentheses `()` separated by commas. If a function has no parameters,
+the parentheses must still be present.
+
+A function must terminate with a `return` statement to return a value back to
+the caller. Only a single value can be returned. The type of a return can
+vary between return statements.
+
+Anonymous function example:
+
+```sentinel
+add1 = func(x) { return x + 1 }
+```
+
+Named function example:
+
+```sentinel
+func add1(x) {
+ return x + 1
+}
+```
+
+Both examples create a function that adds 1 to the parameter `x`.
+
+## Calling a Function
+
+Functions are called by accessing their name followed by parentheses with
+a list of comma-separated values for the parameters. If the function takes no
+parameters, an empty set of parentheses must still be used to denote a
+function call.
+
+To call the function in the example above:
+
+```sentinel
+x = 1
+y = add1(x)
+```
+
+## Scoping
+
+The body of a `func` creates a new [scope](/sentinel/language/scope).
+
+The parent scope is the scope in which the function is created. This allows
+for the creation of functions within functions that can access their outer
+scopes.
+
+Example:
+
+```sentinel
+f = func() {
+ a = 42
+ print(a) // 42
+ return undefined
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+f = func() {
+ a = 42
+ return undefined
+}
+
+print(a) // 18
+```
+
+And below is an example of creating a function in a function which uses
+outer values:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double()
+}
+
+print(f()) // 84
+```
+
+A more complex example below shows how scoping works when passing
+functions around as arguments or results:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double
+}
+
+double = f()
+print(double()) // 84
+```
+
+## Pass By Value
+
+The parameters to a function are passed by value, but not deep copied.
+This means that elements of collections can still be modified but the
+root value cannot be changed.
+
+Example:
+
+```sentinel
+f = func(x) {
+ x = "value"
+ return x
+}
+
+x = "outside"
+f(x)
+print(x) // "outside"
+```
+
+```sentinel
+f = func(x) {
+ append(x, "value")
+ return x
+}
+
+x = []
+f(x)
+print(x) // ["value"]
+```
+
+## Recursion
+
+A function is allowed to call other functions, including itself.
+This can be used to implement recursion. For example, the fibonacci sequence
+is implemented below:
+
+```sentinel
+fib = func(x) {
+ if x <= 0 {
+ return undefined
+ }
+
+ if x == 1 {
+ return 1
+ } else {
+ return x + fib(x - 1)
+ }
+}
+```
+
+Note that this example also shows using
+[undefined](/sentinel/language/undefined)
+as a return value in cases where a function has undefined behavior.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/language/imports.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/language/imports.mdx
new file mode 100644
index 0000000000..0ed1541b85
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/language/imports.mdx
@@ -0,0 +1,116 @@
+---
+page_title: Sentinel Language - Imports
+sidebar_current: docs-language-imports
+description: >-
+ Imports enable a Sentinel policy to access reusable libraries and external
+ data and functions.
+layout: docs
+---
+
+# Language: Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external
+data and functions. The exact list of available imports is dependent on the
+host system. Host systems may also make the available imports configurable
+via plugins.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that
+the import `calendar` allows access to engineer calendars. This import
+only exists for the purpose of this example and at the time of writing is
+not a real available import.
+
+```sentinel
+import "calendar"
+
+// Get the calendar for Bob for today
+bob_calendar = calendar.for("bob").today
+
+// Allow this policy to pass if Bob is not on vacation.
+main = rule { not bob_calendar.has_event("vacation") }
+```
+
+This example shows an import being used to deny a behavior if Bob is on
+vacation. Hopefully real policies do not depend on a single person being
+in the office, but the example shows the power of imports.
+
+In addition to consuming imports, you can also
+[extend Sentinel by writing custom imports](/sentinel/extending)
+This allows you to add any arbitrary behavior to your Sentinel-protected
+systems.
+
+## Importing
+
+Whether accessing a built-in library or an external plugin, the syntax
+for an import is the same:
+
+```sentinel
+import "name"
+```
+
+This adds the import with the name `name`. It can be referenced using
+the identifier `name`. For example, if the import above exposed first
+and last name data, you could access it via `name.first` or `name.last`.
+
+You can shadow imports by assigning a variable. In the example below,
+the import `name` becomes unavailable within the function because it is
+shadowed by a variable of the same name.
+
+Shadowing an import is not the same as overwriting a variable. Imports
+are not part of the [scope](/sentinel/language/scope), meaning that
+the shadow only exists for the scope it is created within. For everything
+else, the import works even after it is shadowed. The example below shows that
+as well.
+
+```sentinel
+import "name"
+
+f = func() {
+ name = "jane"
+ return name
+}
+
+print(f()) // "jane"
+print(name.first) // "bob"
+```
+
+## Aliases
+
+An import can be aliased to another name using the syntax below:
+
+```sentinel
+import "name" as other
+```
+
+This would make the import "name" available as `other`.
+
+An import may only be imported once, regardless of aliases. The following
+would be **invalid** and would result in an error:
+
+```sentinel
+import "name" as one
+import "name" as two
+```
+
+## Accessing Import Data
+
+Imports are accessed with dot-separated identifiers, such as `name.first` or
+`name.stage.first`. These are called _selector expressions_. An import may
+return nested data that further can be accessed via selector expressions.
+
+In the first example on this page with the "calendar" import, we see this:
+
+```sentinel
+import "calendar"
+
+a = calendar.for("bob") // selector expression calendar.for
+b = a.today // selector expression on the result
+c = b.vacation // accessing further nested data
+```
+
+The exact structure of an import is dependent on the import. Please reference
+the documentation for an import to learn more.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/language/index.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/language/index.mdx
new file mode 100644
index 0000000000..7f588c94cd
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/language/index.mdx
@@ -0,0 +1,114 @@
+---
+page_title: Sentinel Language
+sidebar_current: docs-language-basics
+description: |-
+ Sentinel policies are written using the Sentinel language. This language
+ is easy to learn and easy to write. You can learn the Sentinel language
+ and be productive within an hour. Learning Sentinel doesn't require any
+ formal programming experience.
+layout: docs
+---
+
+# Sentinel Language
+
+Sentinel policies are written using the Sentinel language. This language
+is easy to learn and easy to write. You can learn the Sentinel language
+and be productive within an hour. Learning Sentinel doesn't require any
+formal programming experience.
+
+This language guide will contain many details that are not necessary to
+be productive with Sentinel or are targeted to those who are looking for
+exact information about the language (such as what types of line endings are
+allowed). You can safely ignore these sentences.
+
+You may also view the
+[official language specification](/sentinel/language/spec)
+This is a specific and detailed document on the syntax and behavior of
+the language primarily intended for implementation creators and to disambiguate
+the language.
+
+## Simplest Example
+
+The example below is about the simplest practical example of Sentinel.
+It is reasonable to imagine this as a realistic policy. This shows that
+in most cases, Sentinel will be extremely simple:
+
+```sentinel
+main = rule { request.method is "GET" and request.headers contains "X-Key" }
+```
+
+## Files
+
+Sentinel policies are single files that end in the `.sentinel` file extension.
+There is currently no built-in mechanism to Sentinel for merging multiple
+files. This is purposefully done to make Sentinel policies easy to submit
+to systems that support Sentinel policies.
+
+Sentinel policy files must be UTF-8 encoded and can end in both
+Unix (LF) or Windows (CRLF) line breaks. When running the
+[auto-formatter](#),
+line endings will always use Unix line breaks.
+
+## Ordering
+
+Sentinel policies are executed top-down. For example:
+
+```sentinel
+a = 1 // a = 1 here
+b = a + 1 // b = 2 here
+a = 3 // a = 3, b = 2
+```
+
+In this example, the value of `a` and `b` is shown at each line. Since Sentinel
+executes values top-down, the final value of `a` is 3 and `b` is 2. `b` _does
+not_ become 4.
+
+## Main
+
+Sentinel expects there to be a `main` [rule](/sentinel/language/rules).
+The value of this rule is the result of the entire policy.
+
+The result of the policy depends on the evaluated contents of the `main` rule.
+For booleans, a policy passes on a `true` value, and fails on a `false` value.
+Other types generally follow a zero or zero-length pattern for determining
+success.
+
+| Type | Passing Condition | Failing Condition |
+| ------- | ----------------- | -------------------------- |
+| Boolean | `true` | `false` |
+| String | `""` | Any non-zero length string |
+| Integer | `0` | Any non-zero value |
+| Float | `0.0` | Any non-zero value |
+| List | `[]` | Any non-zero length list |
+| Map | `{}` | Any non-zero length map |
+
+A value of main that falls outside of the above types will result in a policy
+error. As a special case, if `main` evaluates to an undefined value, the error
+message will indicate as such, with a reference to where the undefined value was
+encountered.
+
+## More Complex Example
+
+The simple example above is a full working example. In our experience with
+Sentinel, many policies can be representing using this simple form. However,
+to show more features of the language, a more complex example is shown below.
+This example is also a realistic example of what Sentinel may be used for.
+
+```sentinel
+import "units"
+
+memory = func(job) {
+ result = 0
+ for job.groups as g {
+ for g.tasks as t {
+ result += t.resources.memory else 0
+ }
+ }
+
+ return result
+}
+
+main = rule {
+ memory(job) < 1 * units.gigabyte
+}
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/language/lists.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/language/lists.mdx
new file mode 100644
index 0000000000..bc5269d8eb
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/language/lists.mdx
@@ -0,0 +1,135 @@
+---
+page_title: Sentinel Language - Lists
+sidebar_current: docs-language-lists
+description: Lists are a collection of zero or more values.
+layout: docs
+---
+
+# Language: Lists
+
+Lists are a collection of zero or more values.
+
+Lists can be created using by wrapping values in `[]` and separating them
+by commas. An optional trailing comma is allowed. List elements can be
+differing types. Examples:
+
+```sentinel
+[] // An empty list
+["foo"] // Single element list
+["foo", 1, 2, true] // Multi element list with different types
+["foo", [1, 2]] // List containing another list
+```
+
+A list can be [sliced](/sentinel/language/slices) to create sublists.
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for value inclusion in a list.
+
+## Accessing Elements
+
+List elements can be accessed with the syntax `name[index]` where
+`index` is zero-indexed.
+
+A negative index accesses the list in reverse. It is the same as
+reversing a list and then using a positive index. Similar to a positive
+index, it is bounded by the length of the list as a negative value.
+
+Accessing beyond the length of the list results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+a = ["foo", 1, true, [1, 2]]
+
+a[0] // "foo"
+a[2] // true
+a[4] // undefined
+a[-2] // true
+a[-4] // "foo"
+a[-5] // undefined
+a[3][1] // 2
+```
+
+## List Append
+
+Values can be appended to a list using the built-in
+[append](/sentinel/functions/append) function.
+
+This modifies the list in-place and returns
+[undefined](/sentinel/language/undefined). For more information on
+why `append` behaves this way, please read the full documentation for the
+[append](/sentinel/functions/append) function.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append([1,2], "foo") // [1, 2, "foo"]
+append([1,2], [3]) // [1, 2, [3]]
+append(1, 3) // error()
+```
+
+## List Concatenation
+
+Two lists can be concatenated using the `+` operator or the shorthand
+`+=` assignment operator. For the `+` operator, a new list is returned.
+For `+=`, the left-hand list is modified in place.
+
+Examples:
+
+```sentinel
+[1] + [2] // [1, 2]
+[1] + [[1]] // [1, [1]]
+[1] + 1 // error
+
+a = [1]
+a += [2] // a = [1, 2]
+a += 3 // error
+```
+
+## List Length
+
+The length of a list can be retrieved using the
+[length](/sentinel/functions/length) function.
+
+Examples:
+
+```sentinel
+length([]) // 0
+length(["foo"]) // 1
+```
+
+## Removing Items From a List
+
+You can use a combination of [list concatenation](#list-concatenation) and
+[slicing](/sentinel/language/slices) to remove elements from a list.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+a = a[:2] + a[3:] // [1, 2, 4, 5]
+```
+
+The shorthand shown here is effectively the same as `a[0:2] + a[3:length(a)]`,
+which creates a new list out of the concatenation two sub-lists composed of the
+first two elements, and the rest of the list starting at index 3. This
+effectively removes the 3rd element from the list (index 2).
+
+## List Comparison
+
+Lists may be [compared](/sentinel/language/boolexpr#comparison-operators)
+for equality. Lists are equal if they are of equal length and their
+corresponding elements are comparable and equal. Lists with the same elements
+but in a different order are not equal.
+
+```sentinel
+[1, 2] is [1, 2] // true
+[1, 2] is [2, 1] // false
+["a"] is ["a", "b"] // false
+["a", ["b", "c"]] is ["a", ["b", "c"]] // true
+```
+
+List comparison speed is O(N), meaning that the speed of the comparison is
+_linearly_ proportional to the number of elements in the list. The more
+elements, the more iterations that are necessary to verify equality.
+
+-> The N-value quoted above should account for the sum of the elements of _all_
+lists in the subjects of comparison, as list comparison will recurse into these
+lists to check for equality.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/language/logging-errors.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/language/logging-errors.mdx
new file mode 100644
index 0000000000..705a72cbd4
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/language/logging-errors.mdx
@@ -0,0 +1,52 @@
+---
+page_title: Sentinel Language - Logging and Errors
+sidebar_current: docs-language-log
+description: The built-in functions `print` and `error` can be used for logging and errors.
+layout: docs
+---
+
+# Language: Logging and Errors
+
+The built-in functions `print` and `error` can be used for logging
+and errors. Logging is helpful to understand how a policy is executing
+in development. And errors are useful to halting execution immediately in
+certain scenarios.
+
+## Print
+
+The `print` function is used to output debug information. The exact location
+where this print statements go is dependent on the host application and
+the Sentinel runtime itself.
+
+The `print` function takes zero or more Sentinel values as arguments.
+Each value is formatted for human-friendly output and space-separated.
+Example:
+
+```sentinel
+value = 42
+print("the value is", value) // the value is 42
+
+map = { "foo": false }
+print(map) // { "foo": false }
+
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // false
+```
+
+## Errors
+
+Certain actions in Sentinel, such as accessing an undefined variable,
+attempting to add two incompatible types, etc. result in _runtime errors_.
+These errors halt the execution of a policy immediately. The pass/fail result
+of a policy is considered fail.
+
+Runtime errors can be manually triggered using the `error` function.
+The `error` function behaves exactly like the `print` function except that
+execution is halted immediately.
+
+This should only be used in scenarios where the policy _must fail_ due to
+some unexpected or invalid condition. The line between `error` and
+[undefined](/sentinel/language/undefined) can sometimes be unclear. Undefined
+is recommended when a policy can conceivably recover or safely ignore the
+undefined behavior. An error should be used when the policy should fail and
+notify someone regardless.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/language/loops.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/language/loops.mdx
new file mode 100644
index 0000000000..622732bd35
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/language/loops.mdx
@@ -0,0 +1,92 @@
+---
+page_title: Sentinel Language - Loops
+sidebar_current: docs-language-loops
+description: >-
+ Loop statements allow you to execute a body of code for each element in a
+ collection or for some fixed number of times.
+layout: docs
+---
+
+# Language: Loops
+
+Loop statements allow you to execute a body of code for each element in
+a collection or for some fixed number of times.
+
+Loop statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## For Statements
+
+`for` statements allow repeated execution of a block for each
+element in a collection.
+
+Example:
+
+```sentinel
+// A basic sum
+count = 0
+for [1, 2, 3] as num {
+ count += num
+}
+```
+
+The syntax is `for COLLECTION as value`. This will iterate over the
+collection, assigning each element to `value`. In the example above,
+each element is assigned to `num`. The body is executed for each element.
+In the example above, the body adds `num` to the `count` variable. This
+creates a basic sum of all values in the collection.
+
+For a map, the assigned element is the key in the map. In the example
+below, `name` would be assigned map keys.
+
+```sentinel
+list = []
+for { "a": 1, "b": 2 } as name {
+ append(list, name)
+}
+
+print(list) // ["a" "b"]
+```
+
+An alternate syntax is `for COLLECTION as key, value`. This will assign
+both the key and value to a variable. For a list, the key is the element
+index. For a map, it is the key and value is assigned the element value.
+Example:
+
+```sentinel
+count = 0
+for { "a": 1, "b": 2 } as name, num {
+ count += num
+}
+
+print(count) // 3
+```
+
+## Scoping
+
+The body of a `for` statement creates a new
+[scope](/sentinel/language/scope). If a variable is assigned within
+the body of a for statement that isn't assigned in a parent scope,
+that variable will only exist for the duration of the body execution.
+
+Example:
+
+```sentinel
+for list as value {
+ a = 42
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+for list as value {
+ a = 42
+}
+
+print(a) // 18
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/language/maps.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/language/maps.mdx
new file mode 100644
index 0000000000..298bf9a567
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/language/maps.mdx
@@ -0,0 +1,121 @@
+---
+page_title: Sentinel Language - Maps
+sidebar_current: docs-language-maps
+description: >-
+ Maps are a collection of zero or more key/value pairs. These are useful for
+ storing values that are looked up by a unique key.
+layout: docs
+---
+
+# Language: Maps
+
+Maps are a collection of zero or more key/value pairs. These are useful
+for storing values that are looked up by a unique key.
+
+Maps can be created using `{}` and specifying key/value pairs. Keys and
+values can be differing types. An optional trailing comma is allowed.
+Examples:
+
+```sentinel
+// Empty map
+{}
+
+// Map with a single value on one line
+{ "key": "value" }
+
+// Map with multiple values with differing types on multiple lines
+{
+ "key": "value",
+ 42: true,
+}
+```
+
+Maps are **unordered**. When
+[looping](/sentinel/language/loops)
+over a map, the key/value pairs can be returned in any order.
+
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for key inclusion in a map.
+
+## Accessing Elements
+
+Map elements can be accessed with the syntax `name[key]`. This looks up
+a value by a key.
+
+Accessing a key that doesn't exist results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+map = { "key": "value", 42: true, }
+
+map["key"] // "value"
+map[42] // true
+map[0] // undefined
+```
+
+## Modifying or Adding Elements
+
+Elements can be added or modified in a map by assigning to `name[key]`.
+If the key doesn't exist, the value is added. If the key already exists,
+the value is overridden.
+
+```sentinel
+map = { "key": "value" }
+
+map[42] = true // Add a new key/value
+map["key"] = 12 // Modify the value of "key"
+```
+
+## Deleting Elements
+
+An element can be deleted from a map using the
+[delete](/sentinel/functions/delete) function.
+
+Examples:
+
+```sentinel
+map = { "key": "value" }
+delete(map, "key") // map is now empty
+delete(map, "other") // no effect for non-existent key
+```
+
+## Keys and Values
+
+The keys and values of a map can be retrieved as
+[lists](/sentinel/language/lists) using the
+[keys](/sentinel/functions/keys) and
+[values](/sentinel/functions/values) functions.
+
+Because maps are unordered, the keys and values are returned in an
+unspecified order. It should not be assumed that keys and values will be
+returned in a consistent order.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+## Map Comparison
+
+Maps may be [compared](/sentinel/language/boolexpr#comparison-operators) for
+equality. Maps are equal if they are of equal length and both their
+corresponding keys and values are comparable and equal.
+
+```sentinel
+{"foo": "bar"} is {"foo": "bar"} // true
+{"foo": "bar"} is {"baz": "bar"} // false
+{"foo": "bar"} is {"foo": "baz"} // false
+{"foo": "bar"} is {"foo": "bar", "baz": "qux"} // false
+{1: "a"} is {1.0: "a"} // true (int/float comparable)
+
+// also true (maps are not ordered):
+{"m": {"a": "b"}, "l": ["a"]} is {"l": ["a"], "m": {"a": " b"}}
+```
+
+-> The current worst-case comparison speed for maps is O(N²), or _quadratic
+time_. This is the square of the cumulative elements or the parent and all child
+maps contained within the map. Consider this when making comparisons on larger,
+more complex maps. This may be optimized in the future.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/language/parameters.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/language/parameters.mdx
new file mode 100644
index 0000000000..2e008d469b
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/language/parameters.mdx
@@ -0,0 +1,146 @@
+---
+page_title: Sentinel Language - Parameters
+sidebar_current: docs-language-parameters
+description: >-
+ Parameteres allow a Sentinel policy to describe variables that are expected
+ to be supplied by the calling application, in addition to any default value.
+layout: docs
+---
+
+# Language: Parameters
+
+Sentinel allows a policy author to supply parameters to help facilitate policy
+reuse and ensure sensitive values do not need to be hard-coded in a policy.
+
+Parameters are supplied by using the `param` keyword, followed by an identifier.
+A default value can also be supplied by using the `default` keyword.
+
+```sentinel
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+```
+
+Once declared, parameters can be used like any other variable, including being
+re-assigned.
+
+```sentinel
+param foo default 1 // 1 (default)
+
+foo = foo + 1 // 2
+```
+
+## Variable Descriptions
+
+You can supply a description to a parameter by adding a comment at the top of
+it. This value can be communicated to a specific implementation of Sentinel to
+provide information about what the parameter is for during configuration.
+
+```sentinel
+// An example parameter. Must be supplied or the policy will fail.
+param foo
+```
+
+## Supplying Parameter Values Using the Sentinel CLI
+
+In a production implementation, supplying parameters to a policy is an
+implementation-specific detail - see the documentation for your particular
+implementation for details.
+
+Using the [Sentinel CLI](/sentinel/commands), you can supply parameters one of
+four ways.
+
+### Supplying Parameter Values Using the Configuration File
+
+You can supply parameters using the
+[`param`](/sentinel/configuration#parameters) section of the
+[configuration file](/sentinel/configuration).
+
+```hcl
+param "foo" {
+ value = "bar"
+}
+```
+
+This method works for both `sentinel apply` and `sentinel test`.
+
+In addition to the above, you can supply targeted parameters to each
+[policy block](/sentinel/configuration#policies) in the configuration file.
+
+```hcl
+policy "foo" {
+ source = "foo.sentinel"
+ enforcement_level = "hard-mandatory"
+ params = {
+ "name" = "Sample"
+ }
+}
+```
+
+### Supplying Parameter Values Using the Environment
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can supply a value using environment variables - prefix the parameter with
+`SENTINEL_PARAM_`, using the name of the parameter to supply.
+
+```plaintext
+SENTINEL_PARAM_foo=bar sentinel apply policy.sentinel
+```
+
+### Supplying Parameter Values Using CLI Arguments
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can also use the `-param` CLI argument to supply parameter in a `key=value`
+pair.
+
+```plaintext
+sentinel apply -param foo=bar policy.sentinel
+```
+
+### Interactive CLI Prompting
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+If a required value has not been supplied when a policy is run with `sentinel apply`, it will be prompted for, along with its description:
+
+
+
+ $ sentinel apply policy.sentinel
+
+ policy.sentinel:2:7: requires value for parameter foo
+
+ An example parameter. Must be supplied or the policy will fail.
+
+
+ Values can be strings, floats, or JSON array or object values. To
+ force
+
+ strings, use quotes.
+
+
+ Enter a value: bar
+
+
+
+ Pass
+
+
+
+
+### CLI Value Format
+
+-> **NOTE:** This section contains details for the parameter features supported by `sentinel apply`.
+
+The CLI takes either strings, or JSON numbers, arrays, or maps. If you need a
+literal string value, quote the value.
+
+```sentinel
+foo // string
+42 // number (float)
+"42" // string ("42", without quotes)
+[1, 2] // array (list)
+{"a": "b"} // object (map)
+```
+
+-> **NOTE:** Boolean values are not supported by this method.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/language/rules.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/language/rules.mdx
new file mode 100644
index 0000000000..e31924d414
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/language/rules.mdx
@@ -0,0 +1,204 @@
+---
+page_title: Sentinel Language - Rules
+sidebar_current: docs-language-rules
+description: >-
+ Rules form the basis of a policy by representing behavior that is either
+ passing or failing (true or false).
+layout: docs
+---
+
+# Language: Rules
+
+Rules form the basis of a policy by representing behavior that is either passing
+or failing. A policy can be broken down into a set of rules. Breaking down a
+policy into a set of rules can make it more understandable and aids with
+testing.
+
+An example is shown below:
+
+```sentinel playground
+weather = "sunny"
+day = "wednesday"
+is_sunny = rule { weather is "sunny" }
+is_wednesday = rule { day is "wednesday" }
+
+main = rule {
+ is_sunny and
+ is_wednesday
+}
+```
+
+A rule contains a single expression. This can be a simple [boolean
+expression](/sentinel/language/boolexpr), or an expression representing the
+discovery of a set of violations using more in-depth expressions like [`filter`
+and `map`](/sentinel/language/collection-operations). You can split expressions
+into multiple lines for readability, as you would with any other expression
+within Sentinel.
+
+A rule is also _lazy_ and _memoized_. _Lazy_ means that the rule is only
+evaluated when it is actually required, not at the point that it is
+created. This is covered in more detail in the [lazy](#lazy-and-memoized) section.
+_Memoized_ means that the value is only computed once and then saved. Once
+a rule is evaluated, the result of it is reused anytime the rule is referenced
+again.
+
+## Making rules easier to understand
+
+Rules are an abstraction that make policies much more understandable
+by making it easier for humans to see what the policy is trying to do.
+
+For example, consider the policy before which doesn't abstract into rules:
+
+```sentinel
+main = rule {
+ ((day is "saturday" or day is "sunday") and homework is "") or
+ (day in ["monday", "tuesday", "wednesday", "thursday", "friday"] and
+ not school_today and homework is "")
+}
+```
+
+Assume that `day`, `homework`, and `school_day` are available.
+
+In plain English, this policy is trying to say: you can play with your friends
+only on the weekend as long as there is no homework, or during the week if
+there is no school and no homework.
+
+Despite the relatively simple nature of this policy (a few lines, about 5
+logical expressions), it is difficult to read and understand, especially if
+you've not seen it before.
+
+The same policy using rules as an abstraction:
+
+```sentinel
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+is_valid_weekend = rule { is_weekend and homework is "" }
+is_valid_weekday = rule { not is_weekend and not school_today and homework is "" }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+By reading the names of the rules, its much clearer to see what each individual
+part of the policy is trying to achieve. The readability is improved even
+further when adding comments to the rules to explain them further, which is
+a recommended practice:
+
+```sentinel
+// A weekend is Sat or Sun
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+// A valid weekend is a weekend without homework
+is_valid_weekend = rule { is_weekend and homework is "" }
+
+// A valid weekday is a weekday without school
+is_valid_weekday = rule { not is_weekend and not school_today and homework is "" }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+## "When" Predicates
+
+A rule may have an optional `when` predicate attached to it. When this is
+present, the rule is only evaluated when the predicate results in `true`.
+Otherwise, the rule is not evaluated and the result of the rule is always
+`true`.
+
+"When" predicates should be used to define the context in which the rule
+is semantically meaningful. It is common when translating human language
+policy into Sentinel to have scenarios such as "when the key has a prefix
+of `/account/`, the remainder must be numeric." In that example, when the
+key _does not_ have the specified prefix, the policy doesn't apply.
+
+Assume you have rules `is_prefix` and `is_numeric` to check both the
+conditions in the example above. An example is shown with and without
+the "when" predicate:
+
+```sentinel
+example_no_when = rule { (is_prefix and is_numeric) or not is_prefix }
+
+example_when = rule when is_prefix { is_numeric }
+```
+
+The rules are equivalent in behavior for all values of `is_prefix` and
+`is_numeric`, but the second rule is more succint and easier to understand.
+
+## Testing
+
+To verify a policy works correct, the
+[built-in Sentinel test framework](/sentinel/writing/testing)
+uses rules as the point where you can assert behavior. You say that
+you expect certain rules to be true or false. By doing so, you ensure that
+the policy results in the value you expect using the rule flow that you
+expect.
+
+Therefore, in addition to readability, we recommend splitting policies into
+rules to aid testability.
+
+## Non-Boolean Values
+
+Sentinel supports non-boolean values in rules. This can be useful for passing
+more detail along to a calling integration when more detail is required than a
+boolean value would be able to communicate. This data shows up in the [policy
+trace](/sentinel/writing/tracing) and can be utilized in different ways
+depending on the integration you are using Sentinel with.
+
+Consider a different take on our policy above:
+
+```sentinel
+// A policy to check a set of scheduled days to determine what days you can't
+// come out and play, based on the day not being a weekend day and there being
+// homework to do.
+
+param days
+
+main = rule {
+ filter days as d {
+ d.day not in ["saturday", "sunday"] and
+ d.homework is not ""
+ }
+}
+```
+
+When given a value in the set of `days` that has a `day` that is a weekday, and
+`homework` present, the policy will fail. Additional, when tracing was enabled,
+the days that were detected as violating the policy would be given in the trace
+for the `main` rule.
+
+Generally, for non-boolean values, a policy fails on non-zero data. See the
+details on [the `main` rule](/sentinel/language#main) for more details.
+
+The result of a rule must be either a boolean, string, integer, float, list, or
+map. All other types will result in a runtime error.
+
+## Lazy and Memoized
+
+A rule is _lazy_ and _memoized_.
+
+_Lazy_ means that the rule is only evaluated when it is actually required,
+not at the point that it is created. And _memoized_ means that the value is
+only computed once and then saved. Once a rule is evaluated, the result of it
+is reused anytime the rule is referenced again.
+
+Both of these properties have important implications for Sentinel
+policies:
+
+**Performance:** Rules are a way to improve the performance of a Sentinel
+policy. If you have a boolean expression that is reused a lot, a rule will
+only be evaluated once. In the example shown above, `is_weekend` is used
+multiple times, but will only have to be evaluated once.
+
+**Behavior:** Rules accessing variables see the value of those variables
+at _the time they are evaluated_. This can lead to surprising behavior
+in some cases. For example:
+
+```sentinel playground
+a = 1
+b = rule { a == 1 }
+a = 2
+main = b
+```
+
+In this example, `main` will actually result to `false`. It is evaluated
+when it is needed, which is when the policy executes `main`. At this point,
+`a` is now 2 and `b` has not been evaluated yet. Therefore, `b` becomes `false`.
+All future references to `b` will return `false` since a rule is memoized.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/language/scope.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/language/scope.mdx
new file mode 100644
index 0000000000..f498e26dc2
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/language/scope.mdx
@@ -0,0 +1,47 @@
+---
+page_title: Sentinel Language - Scope
+sidebar_current: docs-language-scope
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Scope
+
+Scope is the context in which variables are created and accessed. If a
+variable exists in a parent scope, it is accessed and can be modified.
+Otherwise, the variable is created in your current scope.
+
+Scopes are created with _blocks_. A block is a possibly empty sequence
+of statements within matching brace brackets `{}`. You may nest blocks
+to create nested scopes.
+
+## Package Scope
+
+The package scope is the top level scope within a policy file and encapsulates
+the entire file contents. Imports, parameters and named functions must be
+declared within the package scope.
+
+## Implicit Scopes
+
+Each `any`, `all`, and `for` statement is considered to be in its own block.
+Note that `if` statements _do not_ create their own block.
+
+## Examples
+
+The example policy below shows various effects of scopes:
+
+```sentinel
+a = 1
+print(a) // 1
+
+f = func() {
+ print(a) // 1
+ a = 12
+ b = 1
+ return undefined
+}
+f()
+
+print(a) // 12
+print(b) // undefined
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/language/slices.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/language/slices.mdx
new file mode 100644
index 0000000000..82dc7f4c18
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/language/slices.mdx
@@ -0,0 +1,45 @@
+---
+page_title: Sentinel Language - Slices
+sidebar_current: docs-language-slices
+description: >-
+ Slices are an efficient way to create a substring or sublist from an existing
+ string or list, respectively.
+layout: docs
+---
+
+# Language: Slices
+
+Slices are an efficient way to create a substring or sublist from an
+existing string or list, respectively.
+
+The syntax for creating a slice is:
+
+```sentinel
+a[low : high]
+```
+
+`low` is the lowest index to slice from. The resulting slice includes
+the value at `low`. `high` is the index to slice to. The resulting slice
+will not include the value at `high`. The length of the resulting list
+or string is always `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+
+a = "hello"
+b = a[1:4] // "ell"
+```
+
+## Convenience Shorthands
+
+Some convenience shorthands are available by omitting the value for either the
+low or high index in the slice expression: omitting `low` implies a low index of
+0, and omitting `high` implies a high index of the length of the list.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+
+b = a[:2] // [1, 2] (same as a[0:2])
+b = a[2:] // [3, 4, 5] (same as a[2:length(a)])
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/language/spec.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/language/spec.mdx
new file mode 100644
index 0000000000..d8efb378cd
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/language/spec.mdx
@@ -0,0 +1,1333 @@
+---
+page_title: Sentinel Language Specification
+sidebar_current: docs-language-spec
+description: This is the specification for the Sentinel policy language.
+layout: docs
+---
+
+# Sentinel Language Specification
+
+This is the specification for the Sentinel policy language.
+
+The Sentinel language is designed with policy enforcement in mind. It is dynamically typed and garbage collected and has explicit support for _rule_ construction representing boolean logic.
+
+The language is designed to be easy to learn and use by non-programmers. It is expected to be embedded within applications.
+
+## Table of Contents
+
+
+
+
+- [Source code representation](#source-code-representation)
+- [Declarations and Scope](#declarations-and-scope)
+- [Blocks](#blocks)
+- [Lexical Elements](#lexical-elements)
+ - [Comments](#comments)
+ - [Identifiers](#identifiers)
+ - [Keywords](#keywords)
+ - [Operators and Delimiters](#operators-and-delimiters)
+ - [Integer Literals](#integer-literals)
+ - [Floating-point Literals](#floating-point-literals)
+ - [String Literals](#string-literals)
+ - [Implicit Line Joining](#implicit-line-joining)
+ - [Whitespace](#whitespace)
+ - [Semicolons](#semicolons)
+- [Variables](#variables)
+- [Undefined](#undefined)
+- [Expressions](#expressions)
+ - [Operand](#operand)
+ - [Primary Expressions](#primary-expressions)
+ - [Null](#null)
+ - [Booleans](#booleans)
+ - [Boolean Literals](#boolean-literals)
+ - [Boolean Expressions](#boolean-expressions)
+ - [List Literals](#list-literals)
+ - [Map Literals](#map-literals)
+ - [Function Literals](#function-literals)
+ - [Rule Expressions](#rule-expressions)
+ - [Index Expressions](#index-expressions)
+ - [Selectors](#selectors)
+ - [Slice Expressions](#slice-expressions)
+ - [Calls](#calls)
+ - [Operators](#operators)
+ - [Operator Precedence](#operator-precedence)
+ - [Arithmetic Operators](#arithmetic-operators)
+ - [Integer operators](#integer-operators)
+ - [Integer overflow](#integer-overflow)
+ - [Floating-point operators](#floating-point-operators)
+ - [String Concatenation](#string-concatenation)
+ - [List Concatenation](#list-concatenation)
+ - [Comparison Operators](#comparison-operators)
+ - [Logical Operators](#logical-operators)
+ - [Set Operators](#set-operators)
+ - [Matches Operator](#matches-operator)
+ - [Else Operator](#else-operator)
+ - [Quantifier Expressions (any, all, filter, map)](#quantifier-expressions-any-all-filter-map)
+- [Statements](#statements)
+ - [Expression Statements](#expression-statements)
+ - [Assignments](#assignments)
+ - [If Statements](#if-statements)
+ - [Case Statements](#case-statements)
+ - [For Statements](#for-statements)
+ - [Break Statements](#break-statements)
+ - [Continue Statements](#continue-statements)
+ - [Return Statements](#return-statements)
+- [Imports](#imports)
+- [Parameters](#parameters)
+- [Built-in Functions](#built-in-functions)
+ - [Length](#length)
+ - [Collections](#collections)
+ - [List Append](#list-append)
+ - [Map Delete](#map-delete)
+ - [Keys and Values](#keys-and-values)
+ - [Range](#range)
+ - [Type Conversion](#type-conversion)
+ - [Printing](#printing)
+ - [Errors](#errors)
+- [Program Execution](#program-execution)
+
+
+
+## Source code representation
+
+Source code is Unicode text encoded in UTF-8. A "character" referenced in this document refers to a Unicode code point. Each code point is distinct: upper and lower case letters are different characters.
+
+The underscore character \_ (U+005F) is considered a "letter".
+
+```ebnf
+letter = unicode_letter | "_" .
+decimal_digit = "0" … "9" .
+octal_digit = "0" … "7" .
+hex_digit = "0" … "9" | "A" … "F" | "a" … "f" .
+```
+
+## Declarations and Scope
+
+A declaration binds an identifier to a value. An identifier is declared at the point it is first assigned. An assignment is considered a first assignment if the identifier hasn't already been previously declared in the current scope or any parent scopes.
+
+The scope of an identifier is the extent of source text in which the identifier denotes the specified value. Sentinel is lexically scoped using blocks.
+
+An identifier declared in a block may be redeclared in an inner block. While the identifier of the inner declaration is in scope, it denotes the value assigned in the inner declaration.
+
+## Blocks
+
+A block is a possibly empty sequence of statements within matching brace brackets.
+
+```ebnf
+Block = "{" StatementList "}" .
+StatementList = { Statement ";" } .
+```
+
+Blocks nest and affect scoping.
+
+In addition to explicit blocks in the source code, there are implicit blocks:
+
+1. The universe block encompasses all source text.
+2. Each program has a program block containing all source text for that program.
+3. Each "any", "all", and "for" statements is considered to be in its own implicit block. "if" does not create an implicit block.
+
+## Lexical Elements
+
+### Comments
+
+Comments are sections of source text used for documentation.
+
+```plaintext
+# Single line comment
+// Single line comment
+/* multi
+line
+ comment */
+```
+
+Three forms of comments are supported: two single-line forms and one multi-line form. A single-line comment begins with the token `//` or `#`. Everything between the starting token and the end of the line is ignored.
+
+A multi-line comment begins with the token `/*` and ends with the token `*/`. Everything between `/*` and `*/` is ignored.
+
+A comment may not start inside a string literal or inside another comment.
+
+A multi-line comment containing no newlines acts like a space.
+
+### Identifiers
+
+Identifiers name program entities such as rules, variables, and functions. An identifier is a sequence of one or more letters and digits. The first character in an identifier must be a letter.
+
+```ebnf
+identifier = letter { letter | unicode_digit } .
+```
+
+```sentinel
+a
+_a
+_A
+αβ
+```
+
+#### Pre-Declared Identifiers
+
+The following identifiers are implicitly declared in the [universe block](#blocks):
+
+```plaintext
+Constants:
+ false null true undefined
+
+Functions:
+ append bool delete error float int keys length print range string values
+```
+
+### Keywords
+
+The following keywords are reserved and may not be used as identifiers:
+
+```plaintext
+all
+any
+as
+break
+case
+continue
+default
+else
+empty
+filter
+for
+func
+if
+import
+map
+param
+return
+rule
+when
+```
+
+### Operators and Delimiters
+
+The following character sequences represent operators, delimiters, and other special tokens:
+
+```plaintext
++
+-
+*
+/
+%
++=
+-=
+*=
+/=
+%=
+==
+<
+>
+=
+!
+!=
+<=
+>=
+( )
+[ ]
+{ }
+,
+.
+:
+;
+and
+contains
+else
+in
+is
+matches
+not
+or
+xor
+```
+
+As a special case, `else` is both a keyword and operator, depending on the context. See [Else Operator](#else-operator) for more details.
+
+### Integer Literals
+
+An integer literal is a sequence of digits representing an integer constant. An optional prefix sets a non-decimal base: `0` for octal, `0x` or `0X` for hexadecimal. In hexadecimal literals, letters `a-f` and `A-F` represents values 10 through 15.
+
+Integers are signed 64-bit values (-9223372036854775808 to 9223372036854775807).
+
+```ebnf
+int_lit = decimal_lit | octal_lit | hex_lit .
+decimal_lit = ( "1" … "9" ) { decimal_digit } .
+octal_lit = "0" { octal_digit } .
+hex_lit = "0" ( "x" | "X" ) hex_digit { hex_digit } .
+```
+
+```
+42
+0600
+0xBadFace
+170141183460469231731687303715884105727
+```
+
+### Floating-point Literals
+
+A floating-point literal is a decimal representation of a floating-point constant. It has an integer part, a decimal point, a fractional part, and an exponent part. The integer and fractional part comprise decimal digits; the exponent part is an e or E followed by an optionally signed decimal exponent. One of the integer part or the fractional part may be elided; one of the decimal point or the exponent may be elided.
+
+Floating-point numbers are IEEE-754 64-bit floating numbers.
+
+```ebnf
+float_lit = decimals "." [ decimals ] [ exponent ] |
+ decimals exponent |
+ "." decimals [ exponent ] .
+decimals = decimal_digit { decimal_digit } .
+exponent = ( "e" | "E" ) [ "+" | "-" ] decimals .
+```
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+### String Literals
+
+String literals are character sequences between double quotes, as in "bar". Within the quotes, any character may appear except newline and unescaped double quote. The text between the quotes forms the value of the literal.
+
+A multi-character sequence beginning with a backslash encode values in various formats.
+
+Backslash escapes allow arbitrary values to be encoded as ASCII text. There are four ways to represent the integer value as a numeric constant: `\x` followed by exactly two hexadecimal digits; `\u` followed by exactly four hexadecimal digits; `\U` followed by exactly eight hexadecimal digits, and a plain backslash `\` followed by exactly three octal digits. In each case the value of the literal is the value represented by the digits in the corresponding base.
+
+After a backslash, certain single-character escapes represent special values:
+
+```plaintext
+\a U+0007 alert or bell
+\b U+0008 backspace
+\f U+000C form feed
+\n U+000A line feed or newline
+\r U+000D carriage return
+\t U+0009 horizontal tab
+\v U+000b vertical tab
+\\ U+005c backslash
+\" U+0022 double quote
+```
+
+The three-digit octal (\nnn) and two-digit hexadecimal (\xnn) escapes represent individual bytes of the resulting string; all other escapes represent the (possibly multi-byte) UTF-8 encoding of individual characters. Thus inside a string literal \377 and \xFF represent a single byte of value 0xFF=255, while ÿ, \u00FF, \U000000FF and \xc3\xbf represent the two bytes 0xc3 0xbf of the UTF-8 encoding of character U+00FF.
+
+A string value is a (possibly empty) sequence of bytes. Strings are immutable: once created, it is impossible to change the contents of a string.
+
+The length of a string s (its size in bytes) can be discovered using the built-in function `length`. An individual character (of type string) can be accessed by integer indices 0 through `length(s)-1`.
+
+```ebnf
+string_lit = `"` { unicode_value | byte_value } `"` .
+unicode_value = unicode_char | little_u_value | big_u_value | escaped_char .
+byte_value = octal_byte_value | hex_byte_value .
+octal_byte_value = `\` octal_digit octal_digit octal_digit .
+hex_byte_value = `\` "x" hex_digit hex_digit .
+little_u_value = `\` "u" hex_digit hex_digit hex_digit hex_digit .
+big_u_value = `\` "U" hex_digit hex_digit hex_digit hex_digit
+ hex_digit hex_digit hex_digit hex_digit .
+escaped_char = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | `"` ) .
+```
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+### Implicit Line Joining
+
+Expressions can be split over more than one line. For example:
+
+```sentinel
+a or
+b or # This is a comment
+c
+```
+
+Implicitly continued lines can have trailing comments. Blank continued lines are allowed.
+
+### Whitespace
+
+Whitespace is needed to separate tokens, but no distinction is made between the number and combination of whitespace characters. Whitespace characters can be space (U+0020), horizontal tabs (U+0009), carriage returns (U+000D), and newlines (U+000A).
+
+```sentinel
+a or b
+
+a or b
+
+a or
+ b
+
+rule { a or b }
+
+rule {
+ a or b }
+
+rule {
+ a or
+ b
+}
+```
+
+### Semicolons
+
+The formal grammar uses semicolons ";" as terminators in a number of productions.
+It is idiomatic Sentinel source to omit semicolons in most cases.
+
+A semicolon is automatically inserted into the token stream immediately after
+a line's final token if that token is:
+
+- An identifier
+- An integer, float, or string literal
+- The keyword `break`, `continue`, or `return`
+- The delimiter `)`, `]`, or `}`
+
+## Variables
+
+A variable is a storage location for holding a value.
+
+A variable has a dynamic type, which is the concrete type of the value assigned to the variable at run time. The dynamic type may vary during execution and change as a result of further assignments.
+
+A variable's value is retrieved by referring to the variable in an expression; it is the most recent value assigned to the variable. If a variable has not yet been declared (initially assigned), it is an error.
+
+```sentinel
+x = 7 // x is type int
+x = "foo" // x is now type string
+
+x = y // error if y is not previously assigned
+```
+
+## Undefined
+
+The value denoted by the keyword `undefined` represents undefined behavior or values. It can be created directly using the keyword `undefined`. It is also returned as a result of expressions in specified cases.
+
+`undefined` is a valid operand for any operations. Only `undefined or true` will result in true. All other operations result in `undefined`. An exception is if `undefined` is not reached as a result of short-circuit operations.
+
+```sentinel
+undefined OR true = true
+undefined OR false = undefined
+undefined OR undefined = undefined
+undefined AND true = undefined
+undefined AND false = undefined
+undefined AND undefined = undefined
+undefined XOR true = undefined
+undefined XOR false = undefined
+undefined XOR undefined = undefined
+
+// Short-circuit examples
+false OR true OR undefined = true
+false OR undefined OR true = true
+true AND false AND undefined = false
+true AND undefined AND false = undefined
+
+// Non-logical operators
+undefined + 5 = undefined
+-undefined = undefined
+!undefined = undefined
+```
+
+If the result of the main rule is `undefined`, it is treated as `false` but is indicative of erroneous logic.
+
+## Expressions
+
+### Operand
+
+Operands denote the elementary values in an expression. An operand may be a literal, an identifier denoting a variable, rule, or function, or a parenthesized expression.
+
+```ebnf
+Operand = Literal | OperandName | MethodExpr | "(" Expression ")" .
+Literal = BasicLit | MapLit | ListLit | FunctionLit | RuleLit .
+BasicLit = int_lit | float_lit | string_lit .
+OperandName = identifier .
+```
+
+### Primary Expressions
+
+Primary expressions are the operands for unary and binary expressions.
+
+```ebnf
+PrimaryExpr =
+ Operand |
+ PrimaryExpr Selector |
+ PrimaryExpr Index |
+ PrimaryExpr Slice |
+ PrimaryExpr Arguments .
+
+Selector = "." identifier .
+Index = "[" Expression "]" .
+Slice = "[" [ Expression ] ":" [ Expression ] "]" .
+Arguments = "(" [ Expression { "," Expression } ] ")" .
+```
+
+```sentinel
+x
+2
+(s + ".txt")
+f(3.1415, true)
+m["foo"]
+s[i : j + 1]
+obj.color
+f.p[i].x()
+x is empty
+x is not empty
+```
+
+### Null
+
+The reserved word `null` denote the singleton value `null`. Null represents the explicit absence of a value. Behavior of `null` within expressions is specified explicitly for each expression.
+
+### Booleans
+
+### Boolean Literals
+
+The reserved words `true` and `false` denote objects that represent the boolean values `true` and `false`, respectively. These are boolean literals.
+
+```ebnf
+BoolLit = "true" | "false" .
+```
+
+#### Boolean Expressions
+
+Boolean expressions are expressions that must result in a boolean value. Any other value type becomes the `undefined` value.
+
+```ebnf
+BoolExpr = Expression .
+```
+
+### List Literals
+
+A list literal denotes a list, which is an integer indexed collection of values.
+
+```ebnf
+ListLit = "[" [ ElementList [ "," ] ] "]" .
+ElementList = Element { "," Element } .
+Element = Expression | LiteralValue .
+```
+
+A list may contain zero or more values. The number of values in a list is its length.
+
+A list has a set of indices. An empty list has an empty set of indices. A non-empty list has the index set `{0...n - 1}` where `n` is the length of the list. Attempting to access a list using an index that is not a member of its set of indices results in the `undefined` value.
+
+### Map Literals
+
+A map literal denotes a map, which is an unordered group of elements indexed by a set of unique keys.
+
+```ebnf
+MapLit = "{" [ KeyedElementList [ "," ] ] "}" .
+KeyedElementList = KeyedElement { "," KeyedElement } .
+KeyedElement = Element ":" Element .
+```
+
+Keys can only be a boolean, numeric, or string type.
+
+The value of a non-existent key is the `undefined` value.
+
+### Function Literals
+
+A function literal represents a function.
+
+```ebnf
+FunctionLit = "func" Function .
+Function = Parameters FunctionBody .
+FunctionBody = Block .
+Parameters = "(" [ IdentifierList [ "," ] ] ")" .
+IdentifierList = identifier { "," identifier } .
+```
+
+```sentinel
+func(a, b) { return b }
+```
+
+Function literals are only allowed in the file scope. They may refer to variables defined in surrounding blocks.
+
+A function must terminate with a return statement. If a function has no meaningful return value, it should return `undefined`.
+
+### Rule Expressions
+
+A rule is an expression that is evaluated lazily and the result is memoized.
+
+If the optional "when" predicate is present, the rule is evaluated only when
+the "when" boolean expression results in true. If the predicate is false,
+the rule is not evaluated and returns true. The predicate is evaluated when
+the rule would be evaluated; it is also lazy and memoized in the same way.
+
+```ebnf
+RuleExpr = "rule" [ "when" BoolExpr ] "{" Expr "}" .
+```
+
+```sentinel
+rule { x is y }
+
+rule {
+ x == "value" or
+ y == "other"
+}
+
+rule when x is y { y > 42 }
+
+rule { map ["a", "b", "c"] as _, id {
+ { "id": id }
+}}
+```
+
+### Index Expressions
+
+A primary expression of the form `a[x]` denotes the element of a list or map indexed by x. The value x is called the index or key.
+
+For `a` of type map:
+
+- If `a` contains a key `x`, `a[x]` is the map value with key `x`.
+- If `a` does not contain key `x`, `a[x]` is the `undefined` value.
+
+For `a` of type list:
+
+- `x` must be an integer
+- `x` must be in the range `[-1 * length(a), length(a)-1]`
+- If `x` is contained in the set of indices of `a`, `a[x]` is the list element at index `x`. If `x` is negative, `a[x]` is equivalent to `a[length(a)+x]`.
+- If `x` is not contained in the set of indices of `a`, `a[x]` is the `undefined` value.
+
+For `a` of value `null`:
+
+- `a[x]` is the `undefined` value.
+
+Otherwise `a[x]` is an error.
+
+### Selectors
+
+For a primary expression x, the selector expression `x.f` denotes the field `f` of the value `x`. The identifier `f` is called the selector. The type of the selector expression is the type of the selector.
+
+As a special case, selectors can be [reserved words](#keywords) and keyword [operators](#operators-and-delimiters), but cannot be any other non-identifier element.
+
+Selectors are used to access data from [imports](#imports). The first primary expression `x` in `x.f` denotes the import name. The field `f` is the selector to access data from the import.
+
+Selectors may also be used to access map data with static keys. They are syntactic sugar over index expressions. `math.pi` is equivalent to `math["pi"]` and exhibit the same limitations and behavior as an index expression. Selectors cannot, however, be used to assign values to map data.
+
+Selectors on undefined result in undefined.
+
+```sentinel
+math.pi
+time.pst.hour
+```
+
+### Slice Expressions
+
+Slice expressions construct a substring or list from a string or list.
+
+The primary expression
+
+```sentinel
+a[low : high]
+```
+
+constructs a substring or list. The indices `low` and `high` select which elements of operand `a` appear in the result. The result has indices starting at 0 and length equal to `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+```
+
+For convenience, any of the indices may be omitted. A missing `low` index defaults to zero; a missing `high` index defaults to the length of the sliced operand:
+
+```sentinel
+a[2:] // same as a[2 : length(a)]
+a[:3] // same as a[0 : 3]
+a[:] // same as a[0 : length(a)]
+```
+
+The indices are in range if `0 <= low <= high <= length(a)`, otherwise they are out of range. If the indices are out of range at run time, the result is the `undefined` value.
+
+If `a` is the value `null`, the result is the `undefined` value.
+
+If `a` is any other value type, it is an error.
+
+### Calls
+
+```ebnf
+CallExpr = identifier Arguments .
+```
+
+Given an expression `f` where `f` is a function value:
+
+```sentinel
+f(a1, a2, … an)
+```
+
+calls `f` with arguments `a1, a2, ... an`. The type of the expression is the result of `f`. The arguments are evaluated left to right. Arguments are passed by value.
+
+### Operators
+
+```ebnf
+Expression = UnaryExpr | Expression binary_op Expression .
+UnaryExpr = PrimaryExpr | unary_op UnaryExpr .
+
+binary_op = logical_op | set_op | rel_op | add_op | mul_op | else_op .
+logical_op = "and" | "or" | "xor" .
+set_op = ["not"] ( "contains" | "in" ).
+rel_op = "==" | "!=" | "<" | "<=" | ">" | ">=" |
+ "is" | "is not" | "matches" | "not matches" .
+add_op = "+" | "-" .
+mul_op = "*" | "/" | "%" .
+else_op = "else" .
+unary_op = "+" | "-" | "!" | "not" | empty_op | defined_op .
+empty_op = "is empty" | "is not empty" .
+defined_op = "is defined" | "is not defined" .
+```
+
+#### Operator Precedence
+
+Unary operators have the highest precedence.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Binary operators of the same precedence associate from left to right. For instance, x / y _ z is the same as (x / y) _ z.
+
+### Arithmetic Operators
+
+Arithmetic operators apply to numeric values and yield a result of the same type when both operands are the same.
+
+Arithmetic operations between integer and floating-point types result in a floating-point value with the integer operand treated as a floating-point value for purposes of calculation.
+
+Arithmetic operations between numeric values (integer, floating-point) and non-numeric values (strings, lists) are not permitted.
+
+All five supported arithmetic operators (+, -, \*, /, %) apply to both integer and floating-point types; + also applies to strings.
+
+```plaintext
++ sum integers, floats, strings, lists
+- difference integers, floats
+* product integers, floats
+/ quotient integers, floats
+% remainder integers, floats
+```
+
+#### Integer operators
+
+For two integer values x and y, the integer quotient `q = x / y` and remainder `r = x % y` satisfy the following relationships:
+
+```sentinel
+x = q*y + r and |r| < |y|
+```
+
+with x / y truncated towards zero.
+
+```plaintext
+ x y x / y x % y
+ 5 3 1 2
+-5 3 -1 -2
+ 5 -3 -1 2
+-5 -3 1 -2
+```
+
+As an exception to this rule, if the dividend x is the most negative value for the int type of x (-9223372036854775808), the quotient `q = x / -1` is equal to x (and r = 0).
+
+If the divisor is a constant, it must not be zero. If the divisor is zero at run time, an error occurs.
+
+#### Integer overflow
+
+For signed integers, the operations `+`, `-`, and `*` may legally overflow and the resulting value exists and is deterministically defined by the signed integer representation, the operation, and its operands. No exception is raised as a result of overflow.
+
+#### Floating-point operators
+
+For floating-point numbers, +x is the same as x, while -x is the negation of x. The result of a floating-point division by zero is not specified beyond the IEEE-754 standard.
+
+#### String Concatenation
+
+Strings can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = "hi"
+y = "hello"
+x = x + ", " + y // "hi, hello"
+x += " and good bye" // "hi, hello and good bye"
+```
+
+String addition creates a new string by concatenating the operands.
+
+#### List Concatenation
+
+Lists can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = [1, 2]
+x = x + [2, 3] // [1, 2, 2, 3]
+x += [4] // [1, 2, 2, 3, 4]
+```
+
+List addition creates a new list by concatenating the operands.
+
+### Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+In any comparison, the two operands must be equivalent types. The only exception are integers and floats, which are considered numeric and may be compared. Any comparison between other non-matching types results in the `undefined` value.
+
+The equality operators `==`, `!=`, `is`, and `is not` apply to operands that are comparable. The ordering operators `<`, `<=`, `>`, and `>=` apply to operands that are ordered. The behavior of `is` with `==` and `is not` with `!=` is identical. These terms and the result of the comparisons are defined as follows:
+
+- Boolean values are comparable. Two boolean values are equal if they are either both true or both false.
+- Integer values are comparable and ordered, in the usual way.
+- Floating point values are comparable and ordered, as defined by the IEEE-754 standard.
+- An integer compared with floating point value treats the integer as the converted floating point value.
+- String values are comparable and ordered, lexically byte-wise.
+- Lists are comparable. Lists are equal if they are of equal length and their
+ corresponding elements are comparable and equal.
+- Maps are comparable. Maps are equal if they are of equal length and both their
+ corresponding keys and values are comparable and equal.
+
+If either operand is the `undefined` value, the result of the expression is the `undefined` value.
+
+### Emptiness Comparisons
+
+Checking the emptiness of an object can be achieved by using one of the emptiness expressions, `is empty` or `is not empty`. Although similar to [Comparison Operators](#comparison-operators) in that they yield a boolean value and read as though a comparison is taking place, both `is empty` and `is not empty` are evaluated as a multi-word expression.
+
+An emptiness comparison can only be performed against collections, strings or `undefined`, with the same rules as the [built-in length](/sentinel/functions/length) function.
+
+```sentinel
+"" is empty // true
+"foo" is empty // false
+[] is empty // true
+[1] is empty // false
+{} is empty // true
+{"a": "b"} is empty // false
+
+"" is not empty // false
+"foo" is not empty // true
+[] is not empty // false
+[1] is not empty // true
+{} is not empty // false
+{"a": "b"} is not empty // true
+
+undefined is empty // undefined
+undefined is not empty // undefined
+```
+
+### Logical Operators
+
+Logical operators apply to boolean values and yield a boolean result.
+
+Logical operators are evaluated left-to-right and perform short-circuit logic. The right-hand side is not guaranteed to be evaluated if a short-circuit can be performed.
+
+```plaintext
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+### Set Operators
+
+The set operators `contains` and `in` test for set inclusion for lists
+and maps, and substring inclusion for strings.
+
+Set operators may be negated by prefixing the operator with `not`: `not contains` and `not in`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+`contains` tests if the left-hand collection contains the right-hand value. For lists, this tests equality of any value. For maps, this tests equality of any key. For strings, this tests for substring existence.
+
+`in` tests if the right-hand collection contains the left-hand value. The behavior is equivalent to `contains`.
+
+The collection must be a list, map, or string. If it is the `undefined` value, the result is the `undefined` value. For any other value, the result is an error.
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+
+"test" contains "est" // true
+"test" contains "best" // false
+"test" in "testing" // true
+"best" in "testing" // false
+```
+
+### Matches Operator
+
+The matches operator tests if a string matches a regular expression.
+
+The matches operators may be negated by prefixing the operator with `not`: `not matches`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+The left-hand value is the string to test. The right-hand value is a string value representing a regular expression.
+
+The syntax of the regular expression is the same general syntax used by Python, Ruby, and other languages. More precisely, it is the syntax accepted by RE2. The regular expression is not anchored by default; any anchoring must be explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+If either operand is `undefined`, the result is the `undefined` value. For any other non-string value, the result is an error.
+
+### Else Operator
+
+Else operators can be used to provide a default value for an expression that may evaluate to the `undefined` value. Else operators return their left-hand value unless it is `undefined`, otherwise they return their right-hand value.
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else null
+```
+
+### Quantifier Expressions (any, all, filter, map)
+
+Quantifiers are expressions that apply a predicate to a collection (list or map). The purpose of the quantifier is to produce a result based on its particular type.
+
+`any` and `all` expressions are existential and universal quantifiers, respectively. `any` expressions are equivalent to a chain of `or` and `all` expressions are equivalent to a chain of `and`. Both expressions implement short-circuiting equivalent to logical operators.
+
+The `map` expression is an apply-to-all quantifier and returns a new list for all values in the input collection. The output list will be the same length as the input collection.
+
+The `filter` expression is a subset quantifier and returns the subset of all values in the input collection for which the supplied predicate applies. This will be zero or more elements, up to the length of the input.
+
+The body of a quantifier expression is a boolean expression.
+
+`any` returns the boolean value `true` if any value in the collection expression results in the body expression evaluating to `true`. If the body expression evaluates to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression result in the body expression evaluating to `true`. If any value in the collection expression result in the body expression evaluating to `false`, the all expression returns `false`.
+
+For empty collections, `any` returns false and `all` returns `true`.
+
+`map` always returns a list, regardless of if the input collection is a list itself; maps (as in the data type) also return lists when processed through `map`. Each element is the result of the supplied expression body.
+
+`filter` returns a list or map, the same type as its input collection. Only the elements for which the expression body evaluated to `true` will be returned. If an iteration of the expression body results in `undefined`, the entire result set is `undefined`.
+
+When the collection is a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When the collection is a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+QuantExpr = QuantOp Expression "as" [ identifier "," ] identifier "{" BoolExpr "}" .
+QuantOp = "all" | "any" | "filter" | "map" .
+```
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" } // true if every iterations is true
+any group.tasks as t { t.driver is "vmware" } // true if any iteration is true
+map group.tasks as t { { "driver": t.driver } } // [{"driver": "vmware"}, ...]
+filter group.tasks as t { t.driver is "vmware" } // subset of tasks that is true
+```
+
+## Statements
+
+### Expression Statements
+
+Function call expressions may exist in the statement context.
+
+```ebnf
+ExpressionStmt = Expression .
+```
+
+### Assignments
+
+Assignments set the value of an expression to the value of another expression.
+
+```ebnf
+Assignment = AssignExpr assign_op Expression .
+AssignExpr = identifier | IndexExpr .
+assign_op = [ add_op | mul_op ] "=" .
+```
+
+The assignment `x op= y` is equivalent to `x = x op (y)` for supported values of `op`.
+
+```sentinel
+a = 1
+b[12] = 42
+c["key"] = "value"
+
+a *= 12
+b[12] += 8
+```
+
+Assignments to lists and maps can be carried out using [index expressions](#index-expressions), with the operands of the index expression being evaluated alongside the right hand side expression in a right-to-left (RHS, LHS) order.
+
+In addition to the rules detailed in the section describing their use, the following rules apply when assigning to maps or lists using index expressions:
+
+- The [variable](#variables) must exist and be a list or map. Attempts to use an index assignment on an unknown variable, or a variable that not a list or map, results in a runtime error.
+- Assigning to a list or map index that already exists overwrites that index's data with evaluated right hand side's value.
+- Assigning to an unknown key in a map creates a key for that map with the evaluated right hand side's value assigned to that key.
+- Attempting to assign a value to a list index that is out of range results in a runtime error.
+
+### If Statements
+
+If statements specify the conditional execution of two branches according to the value of a boolean expression. If the expression evaluates to true, the "if" branch is executed, otherwise, if present, the "else" branch is executed.
+
+```ebnf
+IfStmt = "if" BoolExpr Block [ "else" ( IfStmt | Block ) ] .
+```
+
+```sentinel
+if x < y {
+ return x
+} else if x > z {
+ return z
+} else {
+ return y
+}
+```
+
+### Case Statements
+
+Case statements specify a selection control mechanism to conditionally execute a branch based on expression equality.
+
+Each clause that wishes to provide an expression must use the "when" keyword. Multiple expressions can be provided, separated with a comma (",").
+
+There can be one, optional, "else" clause within a `case` statement. The "else" clause is executed if all other clauses failed to run.
+
+The clauses are evaluated in the order they are listed, with the first successful evaluation being executed.
+
+A `case` statement can optionally provide an expression. If no expression is provided, it is the equivalent of writing `case true {`.
+
+```ebnf
+CaseStmt = "case" [ Expression ] "{" { CaseWhenClause } "}" .
+CaseWhenClause = CaseWhenCase ":" StatementList .
+CaseWhenCase = "when" Expression { "," Expression } | "else" .
+```
+
+```sentinel
+case x {
+when y, z:
+ return true
+else:
+ return false
+}
+
+case {
+when x > 42:
+ return true
+else:
+ return false
+}
+```
+
+### For Statements
+
+A `for` statement specifies repeated execution of a block.
+
+The expression must evaluate to a list or a map.
+
+When iterating over a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When iterating over a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+ForStmt = "for" Expressions "as" [ identifier "," ] identifier Block .
+```
+
+```sentinel
+for [1, 2, 3] as v { count += v } // basic sum
+
+for [1, 2, 3] as idx, v {
+ if idx > 1 { count += v }
+}
+
+data = { "a": 12, "b": 32 }
+for data as k { count += data[k] } // sum map values
+for data as k, v { count += v }
+```
+
+### Break Statements
+
+A `break` statement terminates execution of the innermost `for` statement
+within the same function.
+
+```ebnf
+BreakStmt = "break" .
+```
+
+```sentinel
+// Will only print "1"
+for [1, 2, 3] as v {
+ print(v)
+ break
+}
+```
+
+### Continue Statements
+
+A `continue` statement begins the next iteration of the innermost `for` loop.
+The `for` loop must be within the same function.
+
+```ebnf
+ContinueStmt = "continue" .
+```
+
+```sentinel
+// Will print 1, 3
+for [1, 2, 3] as v {
+ if v == 2 {
+ continue
+ }
+
+ print(v)
+ break
+}
+```
+
+### Return Statements
+
+A return statement in a function F terminates the execution of F and provides the result value.
+
+```ebnf
+ReturnStmt = "return" Expression .
+```
+
+A function must terminate with a return statement.
+
+The return statement can be invoked at any time during a function to terminate execution.
+
+## Imports
+
+An import adds an assignment to the global scope to external definitions.
+
+The import declarations must only appear at the top of the source file, before any other statements. Comments may appear before imports.
+
+```ebnf
+ImportDecl = "import" Name ["as" identifier] .
+Name = string_lit .
+```
+
+An import name and identifier must be distinct. Two names may not repeated even if they're declared with different identifiers.
+
+If an identifier is not specified, the identifier is the name of the import itself.
+
+An import is not a valid value. The identifier representing the import may not be used as an expression, such as in assignment statements or call expressions.
+
+Values in imports are not assignable directly via their selectors. Function calls may be used to alter the state of the external data represented by the import. Whether or not this is supported is specific to the import implementation. The exception to this assignment restriction is the memoized data returned by a call to an import (see below).
+
+Data returned by imports can be memoized, meaning that data is returned by the import in the form of a map which is then used as much as possible without having to return to the import for more data. For example, `t = time.now` returns a map so that `t.hour` will be able to be looked up without having to go back to the import for that data.
+
+Data returned by imports may be callable, meaning that methods may be called on the memoized data returned by an import. For example, given `t = time.now`, `t.after(some_previous_time)` is valid, with the receiver data being set to the local memoized data stored in `t`. It is a valid case to accept modifications to the receiver data (example: assignment to the memoized data via index expressions), as long as all data in the receiver continues to be string-keyed. It is an invalid case to accept receiver data with non-string-typed keys. Methods may also alter the contents of receiver data so that it is different after the call is finished.
+
+As with methods, imports may also have callable keys where the data may not be memoized. Example: in the event of `v = foo.bar`, if `v.baz` is not included in the memoized data, a call will be made to the import to fetch it. The same rules and restrictions to receiver data apply to callable keys as do to method calls.
+
+The support for callable methods and non-memoized keys on data returned by imports is specific to the import implementation.
+
+The handling of import loading is specific to the runtime implementation.
+
+Implicit imports may be added by the runtime, for example for standard library definitions. An explicit import of the same name should override a matching implicit import. For example, if "time" is an implicit import and the program specifies `import "time" as cal`, then only `cal` is defined.
+
+```sentinel
+import "time"
+import "math" as m
+```
+
+## Parameters
+
+```ebnf
+ParamDecl = "param" identifier [ "default" Expression ]
+```
+
+A parameter is a way for a policy to describe variables that are expected to be supplied by the calling application, in addition to any default value.
+
+Parameter declarations must appear at the top of the source file, following imports.
+
+Like imports, comments may appear before parameters; this is mainly pertinent when the policy is not importing anything, which would make parameters the first declarations that appear in a policy.
+
+A parameter declaration describes a valid variable that is added to the scope of a policy at runtime. This variable has the same properties and rules as other variables assigned during the course of policy evaluation as defined by the specification. This includes having a dynamic type and being able to be re-assigned during execution.
+
+Parameters may only be strings, integers, floating point numbers, booleans, lists, or maps. More complex types such as rules or functions are not allowed.
+
+A parameter can specify a default value by adding one in the declaration via use of the "default" keyword, and supplying a literal for the default value. The literal for a default is a very limited expression, comprising of:
+
+- For strings, a simple string literal;
+- For integers and floating-point numbers, either a literal denoting the value, or a unary expression with the literal, and the operators - or +;
+- For booleans, the pre-declared identifiers true or false;
+- For lists and maps, their respective literals composed of values and keys (for maps) of the above values only.
+
+Any other type of expression or identifier is not allowed.
+
+A parameter that does not have a default value defined is required by the policy. Evaluating a policy with a required parameter that has not been supplied at execution results in a runtime error.
+
+Parameters must not conflict with imports or any other identifiers currently defined in the global scope, including any reserved or pre-declared identifiers. Attempting to assign a parameter to an existing value results in a runtime error.
+
+```sentinel
+import "time"
+
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+param time default "11:00" // error, conflicts with "time" import
+param undefined // error, reserved identifier
+```
+
+## Built-in Functions
+
+Built-in functions are predeclared. They are called like any other function.
+
+### Length
+
+The built-in function `length` returns the length of a collection of string.
+
+The length of a string is the number of bytes in the string. It is not necessarilly the number of characters.
+
+The length of a collection is the number of elements in that collection.
+
+The length of `undefined` is `undefined`.
+
+### Collections
+
+#### List Append
+
+The built-in function `append` appends a value to the end of a list in-place.
+
+Appending to a non-list value results in an immediate `fail`.
+
+The return value of `append` is always the `undefined` value.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+#### Map Delete
+
+The built-in function `delete` deletes elements from a map by key. The map is modified in-place.
+
+Deleting a key that does not exist does nothing.
+
+Calling delete for a non-map value results in an immediate `fail`.
+
+The return value of `delete` is always the `undefined` value.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
+
+#### Keys and Values
+
+The built-in function `keys` and `values` return the keys and values of a map, respectively. The return value is a list. Both functions return values in an unspecified order.
+
+The `keys` or `values` of `undefined` is `undefined`.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+### Range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it defaults to `1`.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4]
+range(1, 5, 2) // [1,3]
+range(0, -3, -1) // [0,-1,-2]
+```
+
+### Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a value of that type according to the rules below.
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
+
+### Printing
+
+The built-in function `print` can be used to output formatted debug information. The runtime may omit print function calls depending on its execution mode. The runtime may choose where the output of a print function goes.
+
+`print` is a variadic function, accepting one or more values to be printed; values are separated by a single whitespace character (`" "`).
+
+The return value of `print` is always `true` to allow it to be used in boolean expressions.
+
+```sentinel
+print("hello") // "hello"
+print("hello", "world") // "hello world"
+print("The", "number", "is", 42) // "The number is 42"
+print([1, 2, 3]) // "[1, 2, 3]"
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // "false"
+```
+
+### Errors
+
+The built-in function `error` is equivalent to `print` but immediately causes execution to halt and the main rule to evaluate to `false`. The program execution is considered to have failed.
+
+## Program Execution
+
+Program execution begins by evaluating the source top to bottom. If evaluation is successful, the `main` rule is evaluated and its result is returned. Execution can be interrupted at any time by an error, which can be called explicitly or implicitly through illegal or undefined behavior.
+
+---
+
+> The content of this page is licensed under the [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
+>
+> Based on [The Go Programming Language Specification](https://golang.org/ref/spec) licensed under [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/language/undefined.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/language/undefined.mdx
new file mode 100644
index 0000000000..dc51531783
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/language/undefined.mdx
@@ -0,0 +1,97 @@
+---
+page_title: Sentinel Language - Undefined
+sidebar_current: docs-language-undefined
+description: >-
+ The value `undefined` is a special value representing undefined behavior or an
+ unknown value. Any operation on an `undefined` value results in `undefined`.
+layout: docs
+---
+
+# Language: Undefined
+
+The value `undefined` is a special value representing undefined behavior
+or an unknown value.
+
+Undefined is not an error because there are situations where the undefined
+value can be safely ignored and the language also provides construts for
+recovering from an undefined value.
+
+The undefined value can be created manually with `undefined`. In most cases,
+undefined is created by accessing a non-existent list element or value.
+The undefined value is created in a number of other circumstances. The documentation
+will note when an undefined value may be created.
+
+Almost any operation with `undefined` results in `undefined`. For example,
+performing math on undefined, attempting to call a function that is undefined,
+etc.
+
+## Main and Undefined
+
+If the value `undefined` is returned from the top-level `main` rule, then
+the policy is considered `false`. However, the runtime provides mechanisms
+for the host application to determine the policy failure was caused by
+an undefined value and report that to the user.
+
+The `main` rule returning the undefined value should be considered a logical
+error in most cases and should probably be corrected by the policy writer.
+
+## Debugging Undefined
+
+When an `undefined` value is first created (either explicitly or implicitly),
+the runtime will record the position where the undefined was created. This
+location can be used to find logic that could be erroneous.
+
+## Boolean Logic and Undefined
+
+Boolean logic is one way to safely recover from `undefined`. If boolean
+logic can safely determine a result despite an undefined value, that result
+will be returned.
+
+For example: `undefined or true` will result in `true`, because no matter
+what actual value `undefined` could've been if the policy behaved properly,
+the boolean expression would still be true.
+
+Another scenario where `undefined` can be safely ignored is short-circuit
+logic with `and`. `false and undefined` will result in `false`.
+
+The full table of logical operations on `undefined` is below:
+
+```sentinel
+undefined or true = true
+undefined or false = undefined
+undefined or undefined = undefined
+undefined and true = undefined
+undefined and false = undefined
+undefined and undefined = undefined
+undefined xor true = undefined
+undefined xor false = undefined
+undefined xor undefined = undefined
+
+// Short-circuit examples
+false or true or undefined = true
+false or undefined or true = true
+true and false and undefined = false
+true and undefined and false = undefined
+```
+
+## Else Expressions
+
+The `else` expression allows explicit recovery from undefined and is useful
+for setting default values.
+
+`else` is an operator where the left and right hand side are values. If the
+left-hand value is `undefined`, the right-hand value is returned. Otherwise,
+the left-hand value is returned.
+
+Examples:
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else "default"
+
+// In more complex scenarios
+
+foo() else 42 == 12
+a = config.value else "default"
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/language/values.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/language/values.mdx
new file mode 100644
index 0000000000..f932480340
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/language/values.mdx
@@ -0,0 +1,210 @@
+---
+page_title: Sentinel Language - Values
+sidebar_current: docs-language-values
+description: Values are the data that Sentinel policies operate on.
+layout: docs
+---
+
+# Language: Values
+
+Values are the _data_ that Sentinel policies operate on. You can create this
+data yourself, for example by just typing the number `42`, or you may access
+this data from an external source to make policy decisions.
+
+Values have a _type_, which determines what kind of operations can be performed
+on the data. Example types are booleans (true and false), numbers,
+and strings (text).
+
+This page documents all the available value types. You can also
+[convert between types](#type-conversion).
+
+## Boolean
+
+A boolean is a value that is either true or false.
+
+A boolean value is created literally with `true` or `false`.
+
+As a policy language, booleans are central to the behavior of Sentinel.
+Booleans are used as conditions in [if statements](/sentinel/language/conditionals),
+are the result of [rules](/sentinel/language/rules), and more. The ultimate
+result of a Sentinel policy is true or false.
+
+## Integer
+
+An integer is a whole number.
+
+An integer is a 64-bit value. This means it can represent numbers from
+-9,223,372,036,854,775,808 to 9,223,372,036,854,775,808.
+
+Integers are created by typing them out literally with no
+separators (such as a comma). An optional prefix can set a non-decimal base:
+`0` for octal, and `0x` for hexadecimal. In hexadecimal literals, letters
+`a-f` and `A-F` represent values 10 to 15.
+
+Example integers are shown below:
+
+```sentinel
+42
+0600
+0xBadFace
+170141183460469
+```
+
+Integers are used for math and numerical comparison.
+
+## Float
+
+A float is a number with a decimal point. It has an integer part, a decimal
+point, a fractional part, and optionally an exponent part. Example
+floats are shown below:
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+Floats are IEEE-754 64-bit floating point numbers.
+
+## String
+
+Strings are text values.
+
+Strings are created by wrapping text in double quotes, such as `"hello"`.
+Within the quotes, any character may appear except newline and an unescaped
+double quote. The text between the quotes is the value.
+
+Because Sentinel policies must be UTF-8 encoded text, strings themselves are
+UTF-8 encoded text. This means you can put any value UTF-8 character into
+a string, such as `"日本語"`.
+
+You can have a string with a literal double-quote by _escaping_ it with
+a backslash. For example: `"they said \"hello\""` turns into the value
+`they said "hello"`.
+
+Backslash escapes can be used for much more than only escaping a double quote.
+Newlines are `\n`, a literal backslash is `\\`, and many more. The full list
+of available backslash escapes can be found
+[in the language specification](/sentinel/language/spec#string-literals).
+
+Example strings:
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+Strings do not support indexing, however it is possible to achieve a similar
+result through the combination of the [strings](/sentinel/imports/strings)
+standard import and list indexing.
+
+```sentinel playground
+import "strings"
+
+asciistr = "Hello, world!"
+utf8str = "日本語"
+
+utf8split = rule {
+ strings.split(utf8str, "")[2] == "語"
+}
+
+asciisplit = rule {
+ strings.split(asciistr, "")[5] == ","
+}
+
+main = rule { utf8split and asciisplit }
+```
+
+Strings support [slicing](/sentinel/language/slices) to efficiently create
+substrings.
+
+## List
+
+See [Lists](/sentinel/language/lists).
+
+## Map
+
+See [Maps](/sentinel/language/maps).
+
+## Rule
+
+See [Rules](/sentinel/language/rules).
+
+## Function
+
+See [Functions](/sentinel/language/functions).
+
+## Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a
+value of that type. Some examples are shown below followed by a list of exact
+rules for type conversion.
+
+```sentinel
+int(42) // 42
+int("42") // 42
+int(42.8) // 42
+int(true) // 1
+
+float(1.2) // 1.2
+float(1) // 1.0
+float("4.2") // 4.2
+float(true) // 1.0
+
+string("foo") // "foo"
+string(88) // "88"
+string(0xF) // "15"
+string(true) // "true"
+
+bool("true") // true
+bool(1) // true
+bool(-1) // true
+bool(0.1) // true
+bool("false") // false
+bool(0) // false
+```
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/language/variables.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/language/variables.mdx
new file mode 100644
index 0000000000..7516890212
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/language/variables.mdx
@@ -0,0 +1,99 @@
+---
+page_title: Sentinel Language - Variables
+sidebar_current: docs-language-vars
+description: Variables store values that can be used later.
+layout: docs
+---
+
+# Language: Variables
+
+Variables store values that can be used later.
+
+Most notably, a variable assignment of `main` is required for all
+Sentinel policies. This is the main [rule](/sentinel/language/rules) that
+is executed to determine the result of a policy. The `main` rule may
+use other variables that are assigned in the policy.
+
+Example:
+
+```sentinel
+a = 1
+b = a + 1
+```
+
+In this example, two variables are assigned: `a` and `b`. `a` is given
+the value of "1" and `b` uses the `a` to calculate its own value.
+
+## Syntax
+
+The syntax for assigning a variable is:
+
+```sentinel
+IDENTIFIER = VALUE
+```
+
+On the left of the equal sign is an _identifier_. This is a name for your
+variable and will be how you reference it later. An identifier is any
+combination of letters and digits, but must start with a letter. An
+underscore `_` is also a valid letter.
+
+On the right is any valid Sentinel value or expression. A value is a
+literal value such as a number or string. An expression is some computed
+value such as doing math, calling a function, etc.
+
+## Assignment and Reassignment
+
+A variable is _assigned_ when it is given a value. You can also _reassign_
+variables at any time by setting it to a new value. The new value takes
+effect for any subsequent use of that variable. A variable can be reassigned
+to a different type.
+
+For example:
+
+```sentinel
+a = 1 // a = 1
+b = a // b = 1
+a = "value" // a = "value", b = 1
+c = a // c = "value", b = 1
+```
+
+In the above example you can see that the variables are set and reassigned.
+Notice that the value of a variable is the _current_ value, and that reassigning
+a variable only affects _future_ uses of that variable. You can see this with
+`c` and `b` being different values.
+
+## Unassigned Variables
+
+Using a variable that is _unassigned_ is an error.
+
+In the example below, the first line would result in the policy erroring.
+Sentinel is executed top-down and the value of `c` is not available yet
+on the first line.
+
+```sentinel
+a = c // Error!
+c = 1
+```
+
+## Type Conversion
+
+While a topic more related to [values][lang-values], variables can be assigned
+(or-reassigned) a different value type via type conversion.
+
+[lang-values]: /sentinel/language/values
+
+```sentinel
+s = "1.1"
+a = int(s) // a = 1
+a = float(s) // a = 1.1
+
+a = 1
+s = string(a) // s = "1"
+```
+
+For more details, see the type conversion sections in the
+[values][lang-values-type-conversion] page, or the [Sentinel Language
+Specification][lang-spec-type-conversion].
+
+[lang-values-type-conversion]: /sentinel/language/values#type-conversion
+[lang-spec-type-conversion]: /sentinel/language/spec#type-conversion
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/nomad.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/nomad.mdx
new file mode 100644
index 0000000000..a105b0337d
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/nomad.mdx
@@ -0,0 +1,51 @@
+---
+page_title: Nomad and Sentinel
+sidebar_current: docs-app-nomad
+description: >-
+ Nomad Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies can currently execute on job
+ submission (creation, update).
+layout: docs
+---
+
+# Nomad
+
+Nomad is a simple, flexible scheduler and workload orchestrator.
+[Nomad Enterprise](https://www.hashicorp.com/products/nomad/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies can currently execute on job submission (creation, update).
+
+Sentinel policies have full access to the job structure. This allows the
+Sentinel policy to control behavior based on any attribute within a job,
+such as the driver, resource requests, network configuration, volume
+configuration, and more. The information that Sentinel policies have access
+to will expand over time.
+
+Nomad fully supports all [enforcement levels](/sentinel/concepts/enforcement-levels).
+For soft mandatory policies, the `sentinel-override` capability must be
+available on the user's ACL policy to allow override. Overrides are always
+logged.
+
+The Nomad integration with Sentinel is documented in depth in the
+[Nomad Enterprise documentation](https://www.nomadproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Only allow Docker-based jobs.**
+
+```sentinel
+# Test policy only allows Docker based tasks
+main = rule { all_drivers_docker }
+
+# all_drivers_docker checks that all the drivers in use are Docker
+
+all_drivers_docker = rule {
+ all job.task_groups as tg {
+ all tg.tasks as task {
+ task.driver is "docker"
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/terraform.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/terraform.mdx
new file mode 100644
index 0000000000..9e4c430ddb
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/terraform.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Terraform and Sentinel
+sidebar_current: docs-app-terraform
+description: >-
+ Use Sentinel with HCP Terraform and Terraform Enterprise to enforce policy
+ on Terraform configurations, states, and plans.
+layout: docs
+---
+
+# Terraform
+
+[HCP Terraform and Terraform Enterprise](https://www.hashicorp.com/products/terraform/) use Sentinel to enforce
+policy on [Terraform](https://www.terraform.io) configurations, states, and plans.
+
+The Sentinel integration with Terraform runs within
+HCP Terraform and Terraform Enterprise
+after a `terraform plan` and before a `terraform apply`. The policies
+have access to the created plan, the state at the time of the plan,
+and the configuration at the time of the plan.
+
+The Terraform integration with Sentinel is documented in depth in the [HCP Terraform and Terraform Enterprise documentation](/terraform/cloud-docs/policy-enforcement/sentinel).
+Please read that page for full documentation. This page will only show basic examples.
+
+### Examples
+
+**Example: All AWS instances must have a tag**
+
+```sentinel
+import "tfplan"
+
+main = rule {
+ all tfplan.resources.aws*instance as *, instances {
+ all instances as \_, r {
+ (length(r.applied.tags) else 0) > 0
+ }
+ }
+}
+```
+
+**Example: Only allow GCP instance sizes smaller than `n1-standard-16`**
+
+```sentinel
+import "tfplan"
+
+allowed_machine_types = [
+ "n1-standard-1",
+ "n1-standard-2",
+ "n1-standard-4",
+ "n1-standard-8",
+]
+
+main = rule {
+ all tfplan.resources.google_compute_instance as _, instances {
+ all instances as _, r {
+ r.applied.machine_type in allowed_machine_types
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/vault.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/vault.mdx
new file mode 100644
index 0000000000..6db2f8af22
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/vault.mdx
@@ -0,0 +1,64 @@
+---
+page_title: Vault and Sentinel
+sidebar_current: docs-app-vault
+description: >-
+ Vault Enterprise uses Sentinel to augment the built-in policy system to
+ provide Role Governing Policies (RGPs) and Endpoint Governing Policies (EGPs)
+ to enable complex, flexible policies across identities and endpoints.
+layout: docs
+---
+
+# Vault
+
+[Vault Enterprise](https://www.hashicorp.com/products/vault/) uses Sentinel
+to augment the built-in policy system to provide Role Governing Policies (RGPs)
+and Endpoint Governing Policies (EGPs) to enable complex, flexible policies
+across identities and endpoints.
+
+**Role Governing Policies (RGPs)** are Sentinel policies that are tied to
+particular tokens, Identity entities, or Identity groups. They have access to
+a rich set of controls across various aspects of Vault. These are evaluated
+whenever a token they're attached to is used.
+
+**Endpoint Governing Policies (EGPs)** are Sentinel policies that are tied to
+particular paths instead of tokens. They have access to as much request
+information as possible, but they can take effect even on unauthenticated
+paths, such as login paths.
+
+The Vault integration with Sentinel is documented in depth in the
+[Vault Enterprise documentation](https://www.vaultproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Endpoint policy that requires MFA authentication from a corporate network.**
+
+```sentinel
+import "sockaddr"
+
+# We expect logins to come only from our private IP range
+cidrcheck = rule {
+ sockaddr.is_contained(request.connection.remote_addr, "10.20.0.0/16")
+}
+
+# Require Ping MFA validation to succeed
+ping_valid = rule {
+ mfa.methods.ping.valid
+}
+
+main = rule when request.path is "auth/ldap/login" {
+ ping_valid and cidrcheck
+}
+```
+
+**Example: Endpoint policy that disallows tokens created before a certain time.**
+
+```sentinel
+import "time"
+import "strings"
+
+main = rule when not strings.has_prefix(request.path, "auth/ldap/login") {
+ time.load(token.creation_time).unix > time.load("2017-09-17T13:25:29Z").unix
+}
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/why.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/why.mdx
new file mode 100644
index 0000000000..7e0d42e1ee
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/why.mdx
@@ -0,0 +1,47 @@
+---
+page_title: Why Sentinel?
+sidebar_current: docs-why
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: docs
+---
+
+# Why Sentinel?
+
+The growth of infrastructure and applications has been enabled in part
+by an increasing trend towards automation everywhere.
+Configuration as code (such as Chef or Puppet with [Packer](https://www.packer.io))
+has enabled automated machine configuration. Infrastructure
+as code (such as [Terraform](https://www.terraform.io)) has enabled
+automatic infrastructure creation. And schedulers (such as
+[Nomad](https://www.nomadproject.io) and Kubernetes) have enabled
+automatic application deployment.
+Sentinel enables guardrails to be put in place
+on automation while allowing the codification and automatic enforcement
+of business requirements in critical areas of your infrastructure.
+
+Meanwhile, businesses have business requirements and sometimes legal
+requirements which must be expressed in policies. Traditionally, these
+policies are enforced by humans. But in a highly automated world, the
+automation is only as fast as its slowest component. In many cases, this
+is the human verification step.
+
+As an example: before infrastructure as code and autoscaling, if an order
+came through for 5,000 new machines, a human would likely respond to the
+ticket verifying that the user really intended to order 5,000 new machines.
+Today, automation can almost always freely order 5,000 new compute instances
+without any hesitation, which can result in unintended expense or system
+instability.
+
+Sentinel introduces [policy as code](/sentinel/concepts/policy-as-code)
+and a powerful framework built-in to HashiCorp tooling to allow automation
+guardrails, business requirements, legal compliance, and more to be
+actively enforced by the running systems in realtime.
+
+With Sentinel, you can require an override to create certain numbers of
+infrastructure resources. You can disallow unsafe deployment configurations
+with Nomad. You can enforce certain key/value formats in Consul. You
+can restrict secret access by time in Vault. And more.
+
+Sentinel is available today in HashiCorp enterprise products.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/writing/basic.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/writing/basic.mdx
new file mode 100644
index 0000000000..ebcebf2788
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/writing/basic.mdx
@@ -0,0 +1,111 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-basic
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Policy Basics
+
+Sentinel policies are easy to write while still supporting advanced constructs
+for creating complex policies. This page will explain the basics of writing
+Sentinel policies to get started. You don't need any prior Sentinel knowledge,
+but we recommend reading the [language guide](/sentinel/language) after this.
+
+Sentinel policies are text files written using the [Sentinel language](/sentinel/language).
+The policies are evaluated top-to-bottom. The value of `main` after execution
+determines whether a policy passes or fails.
+
+## The Simplest Policy
+
+Sentinel only requires that a policy have a `main` variable that evaluates to
+a boolean value.
+
+A valid example is shown below:
+
+```sentinel playground
+main = 10 > 5
+```
+
+This type of minimal policy is not purely academic. In practice, simple
+policies can often be reduced to a single line logical statement resulting
+in `true` or `false`. However, the expression is usually wrapped in a
+[rule](/sentinel/language/rules) for testing reasons.
+
+You can verify Sentinel will execute this minimal policy using the CLI:
+
+```
+$ sentinel apply minimal.sentinel
+Pass
+```
+
+## Logical Expressions
+
+Policy is at its core a set of logic: you can or can not perform some action
+under a certain set of circumstances. Those circumstances are logical
+expressions. Therefore, Sentinel policies primarily translate into logical
+expressions.
+
+Detailed documentation on boolean expressions is
+[available in the language guide](/sentinel/language/boolexpr).
+
+A simple numerical comparison was seen in the first example on this page.
+Sentinel also provides inclusion operators such as `contains`, `any`, `all`, and
+more. Sentinel allows some operators to have aliases to promote readability
+while remaining programmer-familiar, such as `==` which can equivalently be `is`.
+
+The example below verifies that all numbers in a list are even:
+
+```sentinel playground
+numbers = [6, 22, 8, 4, 12]
+main = all numbers as n { n % 2 is 0 }
+```
+
+## Variables
+
+A policy will very often use variables. Applications such as
+[Nomad](https://www.nomadproject.io) inject variables into the global
+scope of a policy for making policy decisions. For example, Nomad injects
+the job that is being run into the policy scope. Knowing how to use
+variables is critical to effectively using Sentinel.
+
+Detailed documentation on how to define and access variables is
+[available in the language guide](/sentinel/language/variables).
+
+Variables can be defined and used explicitly. For example:
+
+```sentinel playground
+value = 10
+main = value > 5
+```
+
+But they may also be introduced implictly by the host system. Nomad
+injects `job` into policies to describe the job that is being run. The
+policy below is a valid policy that requires a job have two task groups.
+Notice that `job` is not defined anywhere. It is implicitly inserted by
+the host application (in this case Nomad). Refer to the application you're
+writing policy for to determine if it implicitly inserts values.
+
+```json sentinel
+{
+ "policy": "main = length(job.task_groups) is 2",
+ "globals": {
+ "job": {
+ "task_groups": ["foo", "bar"]
+ }
+ }
+}
+```
+
+## And more!
+
+The Sentinel language supports many more features such as functions,
+loops, and more. You can learn about all of this in the
+[complete language guide](/sentinel/language).
+
+The other pages in the [writing policy](/sentinel/writing)
+will cover other information you need to know about writing Sentinel
+policies that isn't simply a language reference.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/writing/debugging.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/writing/debugging.mdx
new file mode 100644
index 0000000000..7dc284d222
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/writing/debugging.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-debugging
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Debugging
+
+As policies become more complex, it becomes harder to understand exactly why
+a policy may be behaving differently than you expect.
+
+Prior to actively debugging, there are multiple best practices we recommend
+following. We recommend breaking your policy down into a set of
+[rules](/sentinel/writing/rules). This will make it easier to understand
+[traces](/sentinel/writing/tracing) and make it easier to
+[test](/sentinel/writing/testing) your policies.
+This should help to debug policies up to a significant complexity.
+
+## Print Logging
+
+The built-in [print function](/sentinel/language/logging-errors#print)
+can be used to log data. The print output is only shown during failures
+or when [tracing is explicitly enabled](/sentinel/writing/tracing).
+
+The `print` function outputs each argument, which can be of any type, converted
+to a human-friendly string format. Arguments are separated with a single space.
+
+Example:
+
+```sentinel playground
+value = 42
+print("the number is", value) // the number is 42
+
+object = { "foo": false }
+print(object) // { "foo": false }
+
+main = rule { true }
+```
+
+The `print` function returns the value `true` so that it can also be
+used within rule expressions. Note that the `print` function should be
+used at the beginning of statements otherwise they may never be
+reached. This is due to internal performance techniques within Sentinel
+like [short-circuiting](https://en.wikipedia.org/wiki/Short-circuit_evaluation).
+
+Example:
+
+```sentinel playground
+// rule_a and rule_b will evaluate to false
+rule_a = rule { print("rule_a is printed") and false } // "rule_a is printed"
+rule_b = rule { false and print("rule_b is printed") } // Nothing is printed
+
+// rule_c and rule_d will evaluate to true
+rule_c = rule { print("rule_c is printed") or true } // "rule_c is printed"
+rule_d = rule { true or print("rule_d is printed") } // Nothing is printed
+
+main = rule { rule_a or rule_b or rule_c and rule_d }
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/writing/imports.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/writing/imports.mdx
new file mode 100644
index 0000000000..74ac751ee4
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/writing/imports.mdx
@@ -0,0 +1,117 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom import](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only local
+context for making policy decisions.
+
+Sentinel also comes with a set of [standard imports](/sentinel/imports).
+Standard imports are available to every Sentinel policy to help policy writers
+with common tasks such as working with the time, network addresses, and more.
+
+-> **This page is about writing policies that _use_ imports.** If you're
+interested in _creating_ a new import, please see the section on [extending
+Sentinel](/sentinel/extending) for information on how to write modules and
+import plugins.
+
+## Using Imports
+
+To use an import, you use the `import` keyword at the top of your policy. This
+specifies the name of the import you want to use. The application you're writing
+the policy for must already be
+[configured](/sentinel/configuration#imports) to provide that
+import.
+
+Details on imports can be found in the
+[import section in the language reference](/sentinel/language/imports).
+
+In the example below, we use the [time](/sentinel/imports/time) import:
+
+```sentinel playground
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+There are two options to develop a policy locally when using imports:
+configure Sentinel to launch the import on `apply`, or mock the import
+using `mock`. The former requires access to the import while the
+latter is faster (doesn't have to launch a process for plugins) and
+doesn't require the import.
+
+## Mocking Imports
+
+The first option to developing policies locally is to mock the import values.
+When mocking an import, you don't need the import to be available. This can be
+useful since some imports may not be available as a plugin and may only be
+available to the application the policy runs in.
+
+Mocks are specified via the [configuration
+file](/sentinel/configuration). Mocks can also be used for
+[testing](/sentinel/writing/testing).
+
+You can supply mock configuration one of two ways, depending on your use case:
+
+- **[Using static data](/sentinel/configuration#mocking-static-data):**
+ Use this method when you can accurately represent your mock data in JSON and
+ do not need to mock complex Sentinel features such as functions.
+- **[Using Sentinel code](/sentinel/configuration#mocking-with-sentinel-code):**
+ Use this method when using a static JSON object is insufficient, such as when
+ you need to mock functions or other complex Sentinel features.
+
+Our example does not require complex data to be mocked, so a static object
+is sufficient:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ hour = 12
+ weekday_name = "Tuesday"
+ }
+ }
+}
+```
+
+This can be used via the CLI:
+
+```
+$ sentinel apply -config=config.hcl policy.sentinel
+Pass
+```
+
+## Launching Import Plugins
+
+If you have access to the plugin binary, you can launch the import. The
+benefit of this is that it is really using the import to test your
+policy. If the import changes, your policies may start failing. If you
+only use mock data and the import changes, your policies will still
+appear to work.
+
+Imports are configured in the configuration file:
+
+```hcl
+import "plugin" "custom_time" {
+ source = "/path/to/sentinel-time-import"
+}
+```
+
+This would require the `sentinel-time-import` binary. For this example
+this doesn't currently exist. We plan on writing one to provide for this
+section of the documentation.
+
+## Custom Imports
+
+You can also [create your own imports](/sentinel/extending).
+
+If your policy decisions could benefit from accessing external information,
+then you can use custom imports as a way to do this.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/writing/index.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/writing/index.mdx
new file mode 100644
index 0000000000..eec0b61fb5
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/writing/index.mdx
@@ -0,0 +1,34 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing
+description: >-
+ Sentinel provides a language and workflow for building policy across any
+ system that embeds Sentinel. By learning Sentinel once, you are able to
+ effectively control access to many systems using Sentinel's powerful features.
+ Basic concepts that are important to understand for Vault usage.
+layout: docs
+---
+
+# Writing Sentinel Policy
+
+This section covers how to write Sentinel policies.
+
+We recommend you complete the Get Started tutorials prior to
+reading this section of the documentation. The Get Started tutorials will
+explain all the basics of writing and testing policies. This section of the
+documentation will serve as more of a reference guide to all available
+features of Sentinel.
+
+Sentinel provides a language and workflow for building policy across any system
+that embeds Sentinel. By learning Sentinel once, you are able to effectively
+control access to many systems using Sentinel's powerful features.
+
+Sentinel also provides a [local CLI](/sentinel/commands) for developing and testing
+Sentinel policies. This CLI can be integrated into a daily developer's workflow
+along with CI to treat [policy as code](/sentinel/concepts/policy-as-code).
+
+Sentinel uses its own [language](/sentinel/language) for writing
+policies. You can view a [language reference](/sentinel/language)
+as well as the [specification](/sentinel/language/spec) for details. You
+don't have to read those documents immediately, since the language should
+be easy enough to pick up throughout this section.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/writing/rules.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/writing/rules.mdx
new file mode 100644
index 0000000000..ab4fc69d08
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/writing/rules.mdx
@@ -0,0 +1,65 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-rules
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Rules
+
+Rules form the basis of a policy by representing behavior that is either passing
+or failing. Rules are a first class language construct in Sentinel. A policy can
+and should be broken down into rules to aid with readability, testability, and
+performance.
+
+Rules provide:
+
+- **Readability:** A policy that is broken down into rules can be read
+ more easily. The logic becomes clearer to see for policy writers when
+ they come back to it.
+
+- **Reportability:** When [tracing](/sentinel/writing/tracing) is enabled,
+ rules form the foundation of the data returned. All evaluated rules are added
+ to the trace with the data the rule evaluated to, and their position within
+ policy or module source files.
+
+- **Testability:** [Policy testing](/sentinel/writing/testing) is
+ built on asserting the values of rules. By breaking logic down into
+ rules, you're able to more effectively test your policies.
+
+- **Performance:** Rules are only evaluated on demand, and are also only
+ evaluated once. This means that a rule referenced multiple times only has a
+ one-time performance cost. For complex logic, this could result in improved
+ performance.
+
+An example usage of rules is shown below:
+
+```json sentinel
+{
+ "policy": "is_sunny = rule { weather is \"sunny\" }\nis_wednesday = rule { day is \"wednesday\" }\nmain = rule { is_sunny and is_wednesday }",
+ "globals": {
+ "weather": "sunny",
+ "day": "wednesday"
+ }
+}
+```
+
+Rules can also contain non-boolean data. This can be useful for passing more
+detail to the caller than an opaque boolean value would be able to communicate.
+
+```sentinel playground
+days = [{"weather": "sunny"}]
+main = rule {
+ filter days as d {
+ d.weather is not "sunny"
+ }
+}
+```
+
+Details about rules and their behavior can be found in
+[the language reference](/sentinel/language/rules). Rules have important
+behavior so we recommend reading the language reference on rules.
+Rules will be used throughout the remainder of the documentation.
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/writing/testing.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/writing/testing.mdx
new file mode 100644
index 0000000000..5d8a522c28
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/writing/testing.mdx
@@ -0,0 +1,495 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-testing
+description: >-
+ Sentinel has a built-in test framework to validate a policy behaves as
+ expected.
+layout: docs
+---
+
+# Testing
+
+Sentinel has a built-in test framework to validate a policy behaves as expected.
+
+With an ever-increasing amount of automation surrounding technology,
+the guardrails provided by policies are a critical piece towards ensuring
+expected behavior. As a reliance on correct policy increases, it is important
+to test and verify policies.
+
+Testing is a necessary step to fully realize
+[policy as code](/sentinel/concepts/policy-as-code). Just as good software
+is well tested, a good set of policies should be equally well tested.
+
+## Test Folder Structure
+
+Policies are tested by asserting that [rules](/sentinel/writing/rules)
+are the expected values given a pre-configured input. Tests are run
+by executing the [test command](/sentinel/commands/test).
+
+Sentinel is opinionated about the folder structure required for tests.
+This opinionated structure allows testing to be as simple as running
+`sentinel test` with no arguments. Additionally, it becomes simple to test
+in a CI or add new policies.
+
+The structure Sentinel expects is `test//*.[hcl|json]` where ``
+is the name of your policy file without the file extension. Within that folder
+is a list of HCL or JSON files. Each file represents a single test case.
+Therefore, each policy can have multiple tests associated with it.
+
+-> Note that the primary configuration file format for Sentinel is HCL. While
+you can write configuration files in a HCL-equivalent JSON, we only discuss the
+use of HCL on this page.
+
+## Test Case Format
+
+Each HCL file within the test folder for a policy is a single test case.
+
+The file is the same configuration format as the [CLI configuration
+file](/sentinel/configuration). The format lets you define mock data, imports to
+use, and more. This mock data is the key piece in being able to test policies:
+you craft a specific scenario and assert your policy behaves as you expect.
+
+Test cases also use the `test` block within the configuration file to assert the
+value of [rules](/sentinel/writing/rules). If the `test` key is omitted, the
+policy is expected to pass. If the test key is specified, only the rules
+specified in the map will be asserted. This means if you omit `main`, then the
+final policy result is not asserted.
+
+Example with assertions:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+
+test {
+ rules = {
+ main = false
+ is_open_hours = false
+ is_weekday = true
+ }
+}
+```
+
+The configuration above specifies some parameter data, and asserts the result of
+some rules. This is the same configuration used in the example section below.
+
+## Example
+
+Lets use the following file as an example. Save this file to a directory
+and name it `policy.sentinel`. It can be named anything with the `sentinel`
+extension, but by naming it `policy.sentinel` your output should match
+the example output on this page.
+
+```sentinel
+// The day of the week.
+param day
+
+// The hour of the day.
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+### A Passing Test
+
+Next, let's define a single test case. Relative to where you saved
+the policy, create a file at the path `test/policy/good.hcl`.
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 14
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/good.hcl
+```
+
+This passed because the policy passed. We didn't assert any specific
+rules. By not specifying any assertions, `test` expects the policy itself
+to fully pass.
+
+### A Failing Test
+
+Define another test case to fail. We want to verify our policy fails when expected, too.
+
+Save the following as `test/policy/7-am.hcl`:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+FAIL - policy.sentinel
+ FAIL - test/policy/7-am.hcl
+ expected "main" to be true, got: false
+
+ trace:
+ policy.sentinel:9:1 - Rule "main"
+ bool: false
+
+ policy.sentinel:8:1 - Rule "is_open_hours"
+ bool: false
+
+ PASS - test/policy/good.hcl
+```
+
+As you can see, the test fails because "main" is false. This is good
+because the policy _should_ have failed since we specified an invalid
+hour. But, we expect main to be false and don't want our test to fail!
+Update `7-am.hcl` to add test assertions:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+
+test {
+ rules = {
+ main = false
+ is_open_hours = false
+ }
+}
+```
+
+And when we run the tests:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/7-am.hcl
+ PASS - test/policy/good.hcl
+```
+
+The test passes. We asserted that we expect the `main` rule to be false,
+the `is_open_hours` rule to be false, and the `is_weekday` rule to be
+true. By asserting some rules are true, we can verify that our policy
+is failing for reasons we expect.
+
+## Mocking
+
+The above example demonstrates how to test by supplying different
+[parameters](/sentinel/language/parameters). Parameters in a policy can be
+specifically useful when you want to control user-defined input values to a
+policy.
+
+However, generally, when testing, you will need mimic the conditions you will
+see in production. Production implementations of Sentinel will supply data using
+one of two methods:
+
+- **Global data:** Data is injected directly into the policy's scope and is
+ accessible using normal identifiers, similar to variables.
+- **Imports:** Data is stored behind an [import](/sentinel/language/imports)
+ and loaded on demand as needed by the policy author.
+
+Proper testing of a policy requires that these values be able to be _mocked_ -
+or, in other words, simulated in a way that allows the accurate testing of the
+scenarios that a policy could reasonably pass or fail under.
+
+Mocking both globals and imports can be done by setting various parts of the
+configuration file.
+
+### Mocking Globals
+
+Demonstrating the mocking of globals can be seen by making a few modifications to
+our example policy, removing the `param` declarations:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Then, change the `param` section in the configuration file to
+[`global`](/sentinel/configuration#global).
+
+```json
+global "day" {
+ value = "monday"
+}
+
+global "hour" {
+ value = 14
+}
+```
+
+This test should still pass, as if nothing had happened, although what we've
+done is shifted our parameters to globals, simulating an environment where `day`
+and `hour` are already defined for us.
+
+### Mocking Imports
+
+To mock imports, we need to use the
+[`mock`](/sentinel/configuration#mock-imports) section of the
+configuration file.
+
+Let's say the above example is behind an import named `time`.
+
+-> **NOTE:** [`time`](/sentinel/imports/time) is a valid standard import.
+This example may not be accurate to the
+import's syntax.
+
+The code now looks like this:
+
+```sentinel
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To mock this import, we can [mock it as static
+data](/sentinel/configuration#mocking-static-data). The configuration
+file now looks like, without assertions:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ weekday_name = "Monday"
+ hour = 14
+ }
+ }
+}
+```
+
+The policy will now pass, with the `time` import mocked.
+
+Data can also be [mocked as Sentinel
+code](/sentinel/configuration#mocking-with-sentinel-code). In this case,
+the above configuration file would look like:
+
+```hcl
+mock "time" {
+ module {
+ source = "mock-time.sentinel"
+ }
+}
+```
+
+And a file named `mock-time.sentinel` would now hold your mock values:
+
+```sentinel
+day = "monday"
+hour = 14
+```
+
+Mocking as Sentinel code allows more complex details to be mocked as well, such
+as functions. Say we wanted to mock the `time.load()` function. To mock this,
+just add it to the `mock-time.sentinel` file:
+
+```sentinel
+load = func(_) {
+ return {
+ "weekday_name": "Monday",
+ "hour": 14,
+ }
+}
+```
+
+Your code can now be written as:
+
+```sentinel
+import "time"
+
+t = time.load("a_mock_timestamp")
+
+is_weekday = rule { t.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { t.hour > 8 and t.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To see more details, see the [Mock
+Imports](/sentinel/configuration#mock-imports) section in the
+configuration file.
+
+## Asserting Non-Boolean Rules
+
+Non-boolean rules can also be asserted by `sentinel test`.
+
+To assert non-boolean values, simply enter the expected value into the rule
+contents:
+
+```hcl
+param "maintenance_days" {
+ value = [
+ {
+ day = "wednesday"
+ hour = 9
+ },
+ {
+ day = "friday"
+ hour = 1
+ },
+ {
+ day = "sunday"
+ hour = 1
+ },
+ ]
+}
+
+test {
+ rules = {
+ main = [
+ {
+ day = "wednesday"
+ hour = 9
+ },
+ ]
+ }
+}
+```
+
+This would assert a policy checking for violations of a maintenance policy,
+saying that maintenance hours should happen before 6AM on any given day:
+
+```sentinel
+param maintenance_days
+
+main = rule {
+ filter maintenance_days as d {
+ d.hour >= 6
+ }
+}
+```
+
+## Test JSON Output
+
+Running `sentinel test` with the `-json` flag will give you the test results in
+a JSON output format, suitable for parsing by reporting software.
+
+-> **Note:** The JSON output format is currently under development and the
+format may change at a later time. Additionally, support for other well-known
+formats, such as JUnit, may become available in the future.
+
+The current format is an object with the top-level keys being `policies` and
+`duration`, with each test grouped up by policy being run. `duration` represents
+time taken in milliseconds for all policies to run.
+
+The policy result fields are:
+
+- `path`: The path of the policy and the index of the test result in the
+ `policies` field in the root object.
+- `status`: A string representation of the policy's test status as a whole. Can
+ be one of `PASS`, `FAIL`, `ERROR`, or `?`. The final status, `?`, represents a
+ policy that has no tests to process, and acts like a passing test.
+- `errors`: An array of any error messages encountered during processing the
+ policy for testing. Usually reserved for policy file or parser-related errors.
+ For case-specific errors, see the error field for the particular case.
+- `cases`: A map of case results, indexed by case path.
+- `duration`: Time taken in milliseconds for the policy to run.
+
+The case result fields are:
+
+- `path`: The path of the test case and the result's index in the `cases` field
+ in the policy object.
+- `status`: A string representation of the case's test status. Can be one of
+ `PASS`, `FAIL` or `ERROR`.
+- `errors`: An array of any error messages encountered during running this test
+ case.
+- `trace`: The trace for this policy in JSON format. See the
+ [tracing](/sentinel/writing/tracing) page for more details.
+- `rule_detail`: When the `status` of this test case is `FAIL`, contains an
+ object of assertion failure detail, indexed by rule. Only failures are counted
+ here; any rules not found here can be assumed to have passed or not asserted.
+- `config_warnings`: An array of strings denoting any configuration warnings
+ found while processing the configuration for this test case.
+- `config_legacy`: Denotes whether or not the configuration is a legacy JSON
+ configuration and needs to be modernized. This field may be removed in future
+ releases.
+
+A passing example is shown below:
+
+```json
+{
+ "policies": {
+ "policy.sentinel": {
+ "path": "policy.sentinel",
+ "status": "PASS",
+ "errors": null,
+ "duration": 5,
+ "cases": {
+ "test/policy/pass.hcl": {
+ "path": "test/policy/pass.hcl",
+ "status": "PASS",
+ "errors": null,
+ "trace": {
+ "description": "A very basic policy to determine if a run is within working hours.",
+ "error": null,
+ "print": "",
+ "result": true,
+ "rules": {
+ "is_open_hours": {
+ "desc": "Passes if run during business hours.",
+ "ident": "is_open_hours",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 290,
+ "line": 13,
+ "column": 1
+ },
+ "value": true
+ },
+ "is_weekday": {
+ "desc": "Passes if the day does not fall on the weekend.",
+ "ident": "is_weekday",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 193,
+ "line": 10,
+ "column": 1
+ },
+ "value": true
+ },
+ "main": {
+ "desc": "",
+ "ident": "main",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 338,
+ "line": 14,
+ "column": 1
+ },
+ "value": true
+ }
+ }
+ },
+ "rule_detail": {},
+ "config_warnings": null,
+ "config_legacy": false
+ }
+ }
+ }
+ },
+ "duration": 10
+}
+```
diff --git a/content/sentinel/v0.28.x/content/sentinel/docs/writing/tracing.mdx b/content/sentinel/v0.28.x/content/sentinel/docs/writing/tracing.mdx
new file mode 100644
index 0000000000..e98767da00
--- /dev/null
+++ b/content/sentinel/v0.28.x/content/sentinel/docs/writing/tracing.mdx
@@ -0,0 +1,403 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-tracing
+description: >-
+ Sentinel provides execution traces to better understand the execution of a
+ policy.
+layout: docs
+---
+
+# Traces
+
+Sentinel provides execution traces to better understand the execution of a policy.
+
+When using the Sentinel [CLI](/sentinel/commands), traces are shown
+on policy failure during `apply` or `test`, or when the `-trace` flag is
+explicitly specified. For Sentinel-enabled applications, traces are optional
+and may require special configuration to enable.
+
+The availability of the trace may vary from application to application. While
+some applications may only log traces on failure, others, such as [Terraform
+Cloud](https://www.terraform.io/cloud), log the trace on every execution. For
+more details, consult your implementation's documentation.
+
+-> **Note:** We're actively improving Sentinel tracing in each release
+to provide better data and improve readability. The exact format of a
+trace may not match the Sentinel version embedded in the application you're
+using, but the data should be similar. The canonical format for a trace should
+get more stable as we approach a 1.0 release.
+
+## Analyzing a Trace
+
+To show traces, let's use the example policy below with the CLI:
+
+```sentinel
+param day
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+
+main = rule { is_open_hours and is_weekday }
+```
+
+Let's cause the policy to fail so the trace is more interesting.
+If you're on a terminal that supports it, the trace output will be colored
+so you can more clearly see passes, failures, and rules.
+
+
+
+ $ sentinel apply main.sentinel{'\n'}
+
+ main.sentinel:1:7: requires value for parameter day
+
+ {'\n'}
+ {'\n'}
+ {'\n'}
+ {' '}Values can be strings, floats, or JSON array or object values. To force
+ {'\n'}
+ {' '}strings, use quotes.{'\n'}
+ {'\n'}
+ {' '}Enter a value: sunday{'\n'}
+ {'\n'}
+
+ main.sentinel:2:7: requires value for parameter hour
+
+ {'\n'}
+ {'\n'}
+ {'\n'}
+ {' '}Values can be strings, floats, or JSON array or object values. To force
+ {'\n'}
+ {' '}strings, use quotes.{'\n'}
+ {'\n'}
+ {' '}Enter a value: 14{'\n'}
+ {'\n'}
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+
+ Fail - main.sentinel
+
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:7:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}
+ false
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:5:1 - Rule "is_open_hours"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}true{'\n'}
+ {'\n'}
+
+ main.sentinel:4:1 - Rule "is_weekday"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}false{'\n'}
+
+
+
+We can see all of our rules neatly and clearly displayed along with the result
+of the policy. If you are getting the trace due to a failed policy and not
+because you explicitly used `-trace`, an informational message is displayed.
+
+As we can see from our basic example, the `main` rule has failed and its result
+is highlighted in red. All peripheral rules that were also evaluated as part of
+the policy are also displayed below the main rule. Source positions are also
+displayed so that you can find the references to the rules in your code.
+
+Via the trace, we can clearly see that while `is_open_hours` returned a true
+result, `is_weekday` did not, and per the logic in our code, the policy is
+rejected.
+
+## Non-Boolean Rules and the Trace
+
+The trace is particularly important when working with rules that return
+non-boolean data, such as rules where you want to see violations instead of a
+simple opaque boolean value. Let's take our example from the [rules](./rules)
+section, and write a small static policy for it:
+
+```sentinel playground
+days = [
+ {"weekday": "Sunday", "weather": "clouds"},
+ {"weekday": "Monday", "weather": "rain"},
+ {"weekday": "Tuesday", "weather": "sunny"},
+ {"weekday": "Wednesday", "weather": "sunny"},
+ {"weekday": "Thursday", "weather": "clouds"},
+ {"weekday": "Friday", "weather": "rain"},
+ {"weekday": "Saturday", "weather": "sunny"},
+]
+
+main = rule {
+ filter days as d {
+ d.weather is not "sunny"
+ }
+}
+```
+
+Here is the output of our policy:
+
+
+
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+ {'\n'}
+
+ Fail - weather.sentinel
+
+ {'\n'}
+ {'\n'}
+
+ weather.sentinel:11:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+
+ {' [\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Sunday"'}
+ {'\n'}
+ {' "weather": "clouds"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Monday"'}
+ {'\n'}
+ {' "weather": "rain"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Thursday"'}
+ {'\n'}
+ {' "weather": "clouds"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Friday"'}
+ {'\n'}
+ {' "weather": "rain"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' ]'}
+
+
+
+
+We can now see all of the weekdays with non-sunny weather.
+
+Note that to prevent formatting issues and excessive output, the human-readable
+trace is limited in the volume of output it will display for collections.
+
+## JSON Output
+
+The trace can also be displayed in JSON by adding `-json` to `sentinel apply`
+and `sentinel test`, and will display the trace in its entirety, unlike the
+standard CLI output.
+
+Here is the result of running `sentinel apply -json` against our weather policy:
+
+```json
+{
+ "can_override": false,
+ "error": null,
+ "duration": 12,
+ "policies": [
+ {
+ "allowed_failure": false,
+ "error": null,
+ "duration": 12,
+ "policy": "weather.sentinel",
+ "result": false,
+ "trace": {
+ "description": "",
+ "error": null,
+ "print": "",
+ "result": false,
+ "rules": {
+ "main": {
+ "desc": "",
+ "ident": "main",
+ "position": {
+ "filename": "weather.sentinel",
+ "offset": 328,
+ "line": 11,
+ "column": 1
+ },
+ "value": [
+ {
+ "weather": "clouds",
+ "weekday": "Sunday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Monday"
+ },
+ {
+ "weather": "clouds",
+ "weekday": "Thursday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Friday"
+ }
+ ]
+ }
+ }
+ }
+ }
+ ],
+ "result": false
+}
+```
+
+Note that the trace is always included with this output, regardless of whether
+or not the policy passes or fails; using `-trace` is unnecessary.
+
+Additionally, with `sentinel apply`, you can display the value of a single rule
+in JSON, instead of the entire trace. This is done with the `-json-rule` flag.
+
+-> **Note:** `sentinel apply -json-rule` needs to be run either against a single
+on-disk policy, or against a single policy within a policy set. It is ignored
+in full policy set executions and functions exactly like `-json` in these
+scenarios.
+
+Here is the result of executing `sentinel apply -json-rule main weather.sentinel`:
+
+```json
+[
+ {
+ "weather": "clouds",
+ "weekday": "Sunday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Monday"
+ },
+ {
+ "weather": "clouds",
+ "weekday": "Thursday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Friday"
+ }
+]
+```
+
+## Other Trace Details
+
+There are some other details that are good to know when working with the trace:
+
+- Only rules that get executed are added to the trace. Considering our first
+ example where the `main` rule is the expression `is_open_hours and is_weekday`,
+ if our expression was using an `or` operator instead of `and`, in addition to
+ the policy passing, `is_open_hours` would be present in the trace, but
+ `is_weekday` would not be, as the policy would have never evaluated that rule.
+- Descriptions for both rules and policies will show up in the trace as well if
+ present. Here is the output to our first policy with the appropriate
+ annotations:
+
+
+
+ ...{'\n\n'}
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+
+ Fail - main.sentinel
+
+ {'\n'}
+ {'\n'}
+ Description:{'\n'}
+ {' A very basic policy to determine if a run is within working\n'}
+ {' hours.\n'}
+ {'\n'}
+
+ main.sentinel:7:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}
+ false
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:5:1 - Rule "is_open_hours"
+
+ {'\n'}
+ {' '}
+ Description:
+ {'\n'}
+ {' Passes if run during business hours.\n'}
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}true{'\n'}
+ {'\n'}
+
+ main.sentinel:4:1 - Rule "is_weekday"
+
+ {'\n'}
+ {' '}
+ Description:
+ {'\n'}
+ {' Passes if the day does not fall on the weekend.\n'}
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}false{'\n'}
+
+
diff --git a/content/sentinel/v0.28.x/data/docs-nav-data.json b/content/sentinel/v0.28.x/data/docs-nav-data.json
new file mode 100644
index 0000000000..dcd6d9f835
--- /dev/null
+++ b/content/sentinel/v0.28.x/data/docs-nav-data.json
@@ -0,0 +1,395 @@
+[
+ {
+ "title": "What is Sentinel?",
+ "path": "intro"
+ },
+ {
+ "title": "Why Sentinel?",
+ "path": "why"
+ },
+ {
+ "title": "Release Notes",
+ "path": "changelog"
+ },
+ {
+ "title": "Basic Concepts",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "concepts"
+ },
+ {
+ "title": "Policy as Code",
+ "path": "concepts/policy-as-code"
+ },
+ {
+ "title": "Policy Language",
+ "path": "concepts/language"
+ },
+ {
+ "title": "Imports",
+ "path": "concepts/imports"
+ },
+ {
+ "title": "Enforcement Levels",
+ "path": "concepts/enforcement-levels"
+ }
+ ]
+ },
+ {
+ "title": "Configuration File Syntax",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "configuration"
+ },
+ {
+ "title": "Override Files",
+ "path": "configuration/overrides"
+ },
+ {
+ "title": "Remote Sources",
+ "path": "configuration/remote-sources"
+ }
+ ]
+ },
+ {
+ "title": "Commands (CLI)",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "commands"
+ },
+ {
+ "title": "apply",
+ "path": "commands/apply"
+ },
+ {
+ "title": "fmt",
+ "path": "commands/fmt"
+ },
+ {
+ "title": "test",
+ "path": "commands/test"
+ }
+ ]
+ },
+ {
+ "title": "Writing Policy",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "writing"
+ },
+ {
+ "title": "Basics",
+ "path": "writing/basic"
+ },
+ {
+ "title": "Rules",
+ "path": "writing/rules"
+ },
+ {
+ "title": "Traces",
+ "path": "writing/tracing"
+ },
+ {
+ "title": "Testing",
+ "path": "writing/testing"
+ },
+ {
+ "title": "Imports",
+ "path": "writing/imports"
+ },
+ {
+ "title": "Debugging",
+ "path": "writing/debugging"
+ }
+ ]
+ },
+ {
+ "title": "Extending Sentinel",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "extending"
+ },
+ {
+ "title": "Modules",
+ "path": "extending/modules"
+ },
+ {
+ "title": "Plugins",
+ "path": "extending/plugins"
+ },
+ {
+ "title": "Static Imports",
+ "path": "extending/static-imports"
+ },
+ {
+ "title": "Internals",
+ "path": "extending/internals"
+ }
+ ]
+ },
+ {
+ "title": "Features",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "features"
+ },
+ {
+ "title": "terraform",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "features/terraform"
+ },
+ {
+ "title": "tfplan/v1",
+ "path": "features/terraform/tfplan-v1"
+ },
+ {
+ "title": "tfplan/v2",
+ "path": "features/terraform/tfplan-v2"
+ },
+ {
+ "title": "tfconfig/v1",
+ "path": "features/terraform/tfconfig-v1"
+ },
+ {
+ "title": "tfconfig/v2",
+ "path": "features/terraform/tfconfig-v2"
+ },
+ {
+ "title": "tfstate/v1",
+ "path": "features/terraform/tfstate-v1"
+ },
+ {
+ "title": "tfstate/v2",
+ "path": "features/terraform/tfstate-v2"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Language",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "language"
+ },
+ {
+ "title": "Variables",
+ "path": "language/variables"
+ },
+ {
+ "title": "Values",
+ "path": "language/values"
+ },
+ {
+ "title": "Lists",
+ "path": "language/lists"
+ },
+ {
+ "title": "Maps",
+ "path": "language/maps"
+ },
+ {
+ "title": "Rules",
+ "path": "language/rules"
+ },
+ {
+ "title": "Imports",
+ "path": "language/imports"
+ },
+ {
+ "title": "Parameters",
+ "path": "language/parameters"
+ },
+ {
+ "title": "Boolean Expressions",
+ "path": "language/boolexpr"
+ },
+ {
+ "title": "Arithmetic",
+ "path": "language/arithmetic"
+ },
+ {
+ "title": "Slices",
+ "path": "language/slices"
+ },
+ {
+ "title": "Conditionals",
+ "path": "language/conditionals"
+ },
+ {
+ "title": "Loops",
+ "path": "language/loops"
+ },
+ {
+ "title": "Collection Operations",
+ "path": "language/collection-operations"
+ },
+ {
+ "title": "Functions",
+ "path": "language/functions"
+ },
+ {
+ "title": "Scope",
+ "path": "language/scope"
+ },
+ {
+ "title": "Undefined",
+ "path": "language/undefined"
+ },
+ {
+ "title": "Logging and Errors",
+ "path": "language/logging-errors"
+ },
+ {
+ "title": "Specification",
+ "path": "language/spec"
+ }
+ ]
+ },
+ {
+ "title": "Builtin Functions",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "functions"
+ },
+ {
+ "title": "append",
+ "path": "functions/append"
+ },
+ {
+ "title": "compare",
+ "path": "functions/compare"
+ },
+ {
+ "title": "delete",
+ "path": "functions/delete"
+ },
+ {
+ "title": "error",
+ "path": "functions/error"
+ },
+ {
+ "title": "keys",
+ "path": "functions/keys"
+ },
+ {
+ "title": "length",
+ "path": "functions/length"
+ },
+ {
+ "title": "print",
+ "path": "functions/print"
+ },
+ {
+ "title": "range",
+ "path": "functions/range"
+ },
+ {
+ "title": "values",
+ "path": "functions/values"
+ }
+ ]
+ },
+ {
+ "title": "Standard Imports",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "imports"
+ },
+ {
+ "title": "collection",
+ "routes": [
+ {
+ "title": "Reference",
+ "path": "imports/collection"
+ },
+ {
+ "title": "collection/maps",
+ "path": "imports/collection/maps"
+ },
+ {
+ "title": "collection/lists",
+ "path": "imports/collection/lists"
+ }
+ ]
+ },
+ {
+ "title": "base64",
+ "path": "imports/base64"
+ },
+ {
+ "title": "decimal",
+ "path": "imports/decimal"
+ },
+ {
+ "title": "http",
+ "path": "imports/http"
+ },
+ {
+ "title": "json",
+ "path": "imports/json"
+ },
+ {
+ "title": "runtime",
+ "path": "imports/runtime"
+ },
+ {
+ "title": "sockaddr",
+ "path": "imports/sockaddr"
+ },
+ {
+ "title": "strings",
+ "path": "imports/strings"
+ },
+ {
+ "title": "time",
+ "path": "imports/time"
+ },
+ {
+ "title": "types",
+ "path": "imports/types"
+ },
+ {
+ "title": "units",
+ "path": "imports/units"
+ },
+ {
+ "title": "version",
+ "path": "imports/version"
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Consul",
+ "path": "consul"
+ },
+ {
+ "title": "Nomad",
+ "path": "nomad"
+ },
+ {
+ "title": "Terraform",
+ "path": "terraform"
+ },
+ {
+ "title": "Vault",
+ "path": "vault"
+ }
+]
diff --git a/content/sentinel/v0.28.x/img/sentinel-import-topology.svg b/content/sentinel/v0.28.x/img/sentinel-import-topology.svg
new file mode 100644
index 0000000000..36f7c79bc9
--- /dev/null
+++ b/content/sentinel/v0.28.x/img/sentinel-import-topology.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/content/sentinel/v0.28.x/redirects.jsonc b/content/sentinel/v0.28.x/redirects.jsonc
new file mode 100644
index 0000000000..1c7c77f138
--- /dev/null
+++ b/content/sentinel/v0.28.x/redirects.jsonc
@@ -0,0 +1,7 @@
+[
+ {
+ "source": "/sentinel/commands/config",
+ "destination": "/sentinel/configuration",
+ "permanent": true,
+ },
+]
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/changelog.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/changelog.mdx
new file mode 100644
index 0000000000..c3e9b1a08f
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/changelog.mdx
@@ -0,0 +1,936 @@
+---
+page_title: Sentinel Runtime Release Notes
+sidebar_current: docs-changelog
+description: >-
+ These are the public release notes for the Sentinel runtime. See this document
+ for the latest updates.
+layout: docs
+---
+
+# Sentinel Runtime Release Notes
+
+These are the release notes for the Sentinel runtime.
+
+Each version of the runtime is released with a corresponding version of
+[Sentinel CLI](/sentinel/commands). To download the CLI, see the [install
+page](/sentinel/install).
+
+Sentinel integrations and embedded runtimes may not always have the latest
+version installed, depending on the product's individual release cycle. For more
+information, contact the support team for your specific integration.
+
+
+## 0.29.0 (November 26, 2024)
+
+ENHANCEMENTS:
+
+- `imports/collection/lists`: Added `difference`, `union` and
+`intersection` helpers to the `collection/lists` import.
+
+BUG FIXES:
+
+- `imports/json`: Any list element or map key/value that is `undefined`
+will now be ignored within the `marshal` method instead of returning `{}`.
+Additionally, directly passing `undefined` will return `undefined` as
+documented.
+
+## 0.28.0 (September 26, 2024)
+
+BUG FIXES:
+
+- `imports/collection`: The `matches` helper now returns no results if there
+are no matches found in the collection.
+
+ENHANCEMENTS:
+
+- `cmd/apply`: A new argument, `-timeout`, has been added to the apply command.
+This argument will allow the apply command to exit after a certain time has been
+reached.
+
+NOTES:
+
+- This release introduces a EULA and Terms Of Evaluation to the release artifacts,
+which will be included in all releases going forward.
+
+## 0.27.0 (August 7, 2024)
+
+ENHANCEMENTS:
+- `imports/collection/maps`: Added an `unset` helper to the `collection/maps`
+import, providing the ability to remove keys using a provided path.
+- `imports/collection/maps`: Added an `omit` helper to the `collection/maps`
+import, which returns a map with all provided paths removed from the source
+map.
+- `imports/strings`: Added the `replace`, `trim`, `trim_left`, `trim_right` and
+`trim_space` helpers to the `strings` import.
+
+## 0.26.3 (July 19, 2024)
+
+ENHANCEMENTS:
+
+- `imports/collection/maps`: Added a `set` helper to the `collection/maps`
+import, providing the ability to set values deeply within a map.
+- `imports/collection/maps`: Added a `pick` helper to the `collection/maps`
+import, providing the ability to select a series of values from a map.
+
+## 0.26.2 (June 19, 2024)
+
+ENHANCEMENTS:
+
+- `imports/collection`: Added a `filter` helper to the `collection` import,
+providing a functional approach to filtering collections.
+
+## 0.26.1 (May 22, 2024)
+
+NOTES:
+
+- This release changes lower-level components and there are no user-facing changes.
+
+## 0.26.0 (May 15, 2024)
+
+FEATURES:
+
+- `imports/collection`, `imports/collection/lists` and `import/collection/maps`:
+Adding new helper imports for dealing with collections (lists, maps), helping
+avoid complexities within policy code.
+- `sentinel/eval`: Fixed an issue with per-policy parameters where parameters
+may leak from one policy into the following policy.
+
+## 0.25.1 (Apr 18, 2024)
+
+BREAKING CHANGES:
+
+- `imports/http`: The default `Accept` header has been changed to `*/*`, removing a redundant
+extension that some servers may not accept by default.
+
+## 0.25.0 (Apr 8, 2024)
+
+ENHANCEMENTS:
+
+- `cmd/apply`: JSON results will return an additional `duration` field for individual policies as well as the evaluation as a whole.
+- `cmd/test`: `sentinel test` will return an additional `duration` field in the JSON output.
+
+BUG FIXES:
+
+- `cmd`: Removed redundant debug logging for custom plugins.
+
+## 0.24.4 (Mar 21, 2024)
+
+ENHANCEMENTS:
+
+- `runtime/format`: Null values will now print correctly for rule value outputs.
+- `config`: Some internal changes to configuration parsing workflow.
+
+## 0.24.3 (Feb 9, 2024)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.24.2 (Jan 31, 2024)
+
+BUG FIXES:
+
+- `imports/static`: Fixed an issue where `nil` values provided to the built-in
+static import were being treated as `undefined` in policy.
+
+## 0.24.1 (Jan 19, 2024)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where the Sentinel cache was unstable due to concurrent
+tests.
+
+## 0.24.0 (Dec 7, 2023)
+
+ENHANCEMENTS:
+
+- `cmd/apply`, `cmd/test`: Custom import plugins can now be fetched from remote
+sources.
+- `cmd/apply`, `cmd/test`: Static imports can now be fetched from remote
+sources.
+- `features/terraform`: Added support for `resource_drift` in the `tfplan/v2` import.
+
+## 0.23.1 (Oct 19, 2023)
+
+BUG FIXES:
+
+- `cmd/apply`: Warnings and errors are included in the JSON output if the json flag is enabled.
+
+## 0.23.0 (Sept 5, 2023)
+
+FEATURES:
+
+- `features/apply_all`: Adds the `apply-all` feature, allowing for all policies to evaluate regardless of result, instead of exiting on first failure.
+
+BUG FIXES:
+
+- `features/terraform`: The `tfplan/v1` import correctly handles nested attribute schemas.
+
+## 0.22.1 (June 22, 2023)
+
+BUG FIXES:
+
+- `sentinel/eval`: Under certain conditions, per-policy parameters would cause
+ Sentinel to panic. This has been resolved.
+
+## 0.22.0 (May 31, 2023)
+
+- `config`: Configuration now supports a `sentinel` block to manage the
+ Sentinel runtime.
+
+## 0.21.1 (May 8, 2023)
+
+BUG FIXES:
+
+- `config`: An issue with certain identifiers being treated as incorrectly invalid
+ has been resolved.
+
+## 0.21.0 (March 8, 2023)
+
+BREAKING CHANGES:
+
+- `lang/ast`: `is empty` and `is not empty` are now treated as `ast.UnaryExpr`
+ expressions, with `ast.IsEmptyExpr` being removed.
+- `lang/ast`: You can now assert if a value is defined or not using the `is defined`
+ and `is not defined` syntax.
+
+FEATURES:
+
+- `config`: Parameter values can now be provided for individual policies within
+ a policy block.
+
+## 0.20.0 (February 16, 2023)
+
+ENHANCEMENTS:
+
+- `cmd/testcmd`: Allows sentinel tests to run concurrently by default. Sequential style testing can
+ be enabled by running with the `-maxConcurrency=1` option.
+- `cmd/testcmd`: Allows sentinel test command to timeout after a certain duration. This can be provided
+ by the user or will default to 5 minutes.
+- `cmd/apply`: The policy enforcement level is now included in the JSON output.
+- `lang/ast`: Functions can now be declared as named statements, providing
+ a safer function declaration.
+
+BREAKING CHANGES:
+
+- `cmd/apply`: Policies provided directly to the apply command will now default their enforcement
+ level to `advisory`, aligning with the `policy` configuration block.
+- `sentinel`: JSON results will no longer return `allowed_failure` or `can_override` fields.
+- `sentinel/result`: A new package has been added which provides additional methods to return
+ supplemental data about the evaluation result.
+
+## 0.19.5 (February 9, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `SelectorExpr` will correctly rewrite `X`.
+
+## 0.19.4 (February 8, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `Compile` function will correctly set the `Original`
+ field when rewriting a `CallExpr` as an `ImportExpr`.
+
+## 0.19.3 (February 3, 2023)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.19.2 (January 17, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `Compile` function will no longer modify `ast.CallExpr`
+ arguments in place.
+
+## 0.19.1 (December 14, 2022)
+
+BUG FIXES:
+
+- `lang/ast`: The `Rewrite` function will no longer modify the provided Node
+ in place.
+
+ENHANCEMENTS:
+
+- `lang/semantic`: The Break semantic check uses the AST walker.
+
+## 0.19.0 (December 6, 2022)
+
+BREAKING CHANGES:
+
+- `config`: Attempts to override a standard import with a custom import will
+ now be met with an error.
+- `imports`: All plugin imports now return `sdk.Plugin` instead of `sdk.Import`,
+ as per the SDK update to v0.4.0.
+
+ENHANCEMENTS:
+
+- `runtime/importer`: An `Import` and `ImportInstance` interface have been
+ added to improve the import capabilities.
+- `runtime/importer`: The `Importer` interface now returns a `Import` interface
+ instead of a `sdk.Import`.
+- `lang/semantic`: The Import Assignment semantic check uses the AST walker.
+- `lang/ast`: Added an AST Walker so the AST rewriter does not need to be used for basic AST tasks.
+
+FEATURES:
+
+- `config`: Configuration syntax now allows for configuration of imports within
+ the standard library.
+- `config`: A new configuration syntax for defining modules and plugins is now
+ available.
+- Static data can now be added via `import` configuration.
+- `cmd/apply`: The `apply` command now supports multiple primary configuration
+ files by default, loading all configuration files within the working
+ directory.
+- `cmd/apply`: The `apply` command now accepts applying configuration overrides
+ to primary configuration.
+
+## 0.18.13 (October 31, 2022)
+
+BUG FIXES:
+
+- `cmd/apply`: `sentinel apply` no longer aborts on advisory policy failure.
+
+## 0.18.12 (September 15, 2022)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where nested `CallExpr` were not rewritten.
+
+## 0.18.11 (June 8, 2022)
+
+NOTES:
+
+- `config`: The `enforcement_level` key for policies is now optional, defaulting to `"advisory"` when not supplied.
+
+## 0.18.10 (May 20, 2022)
+
+NOTES:
+
+- `runtime/initwd`: Improvements to the installer, including timeouts and filesize limits, as well as disabling support
+ for symlinks within `git` or `hg` repositories.
+
+## 0.18.9 (March 23, 2022)
+
+NOTES:
+
+- This release fixes an issue with the Docker container build pipeline. There are no changes to the
+Sentinel runtime or CLI.
+
+## 0.18.8 (March 22, 2022)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue in the installer for Windows paths.
+
+## 0.18.7 (February 24, 2022)
+
+NOTES:
+
+- **Darwin arm64**. This release will be our first to include Darwin arm64 architecture.
+
+BUG FIXES:
+
+- `lang/ast`: Fixed issues where `IndexSpec` and `RuleLit` nodes where returning incorrect end positions.
+- `lang/ast`: Fixed an issue where `ParamSpec` nodes where returning incorrect end positions.
+
+## 0.18.6 (February 2, 2022)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.18.5 (January 14, 2022)
+
+IMPROVEMENTS:
+
+- `runtime/initwd`: Changed the sum algorithm used during remote installation from `md5` to `sha256` to reduce chance of collision.
+
+## 0.18.4 (July 20, 2021)
+
+FEATURES:
+
+- `imports/static`: Improved handling of struct tags when performing encoding, allowing `-` to mark as ignored.
+
+## 0.18.3 (June 1, 2021)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue for remote source installation where duplicate sub-directories resulted in incorrect installation.
+- `imports/static`: Fixed an issue where global configuration that contained an empty map could not be accessed without raising selector issues.
+
+## 0.18.2 (May 18, 2021)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where import expressions inside lists and maps were not being evaluated correctly.
+
+## 0.18.1 (May 11, 2021)
+
+BUG FIXES:
+
+- `imports/json`: Fixed an issue where empty maps where failing when passed at any level to `marshal`.
+
+## 0.18.0 (March 25, 2021)
+
+FEATURES:
+
+- `imports/http`: Added support for POST actions within the `http` import
+ through addition of the `post` function. Additionally, support for adding
+ a body to the `request` object has been added via `with_body`.
+
+## 0.17.4 (February 2, 2021)
+
+BUG FIXES:
+
+- `runtime/format`: Fixed a nesting issue with the rule printer where nesting
+ state leaks were leading to all collection values eventually being truncated,
+ regardless of their nesting level.
+
+## 0.17.3 (January 15, 2021)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue where local file installation failed on Windows
+ when using `sentinel test`.
+
+## 0.17.2 (January 13, 2021)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where duplicate log messages were being printed with
+ `sentinel test`.
+
+## 0.17.1 (January 7, 2021)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where hard-coded path separator caused `sentinel
+ test` issues in Windows.
+
+## 0.17.0 (January 5, 2021)
+
+LANGUAGE CHANGES:
+
+- **Map Expression** `map`. A new quantifier expression, `map` has been added.
+ `map` takes a collection and applies an operation to each element in the
+ collection, returning a list of results with the resulting values.
+- **Emptiness checks** `is empty` and `is not empty`. Two new expressions,
+ `is empty` and `is not empty` have been added. As they are aliases to the
+ `length` built-in, only `string`, `map`, `list` or `undefined` is
+ supported.
+- **Comparable maps** Maps (the data type) are now comparable. Maps are equal if
+ they are of equal length and both their corresponding keys and values are
+ comparable and equal.
+- **Rich Return Types** Rules can now process and return non-boolean data.
+ Scalar, list, and map types are supported. When non-boolean values are used,
+ the presence of a non-zero or non-zero-length `main` rule determines policy
+ failure. More details can be found on
+ https://docs.hashicorp.com/sentinel/language#main.
+
+FEATURES:
+
+- **CLI**: `sentinel apply` and `sentinel test` now have options for JSON
+ output. For more details, see the CLI documentation.
+- `imports/base64`: Added a new import, `base64`, to assist with encoding and
+ decoding base64 strings.
+
+## 0.16.1 (October 21, 2020)
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where `contains` to ensure any instance of `undefined` would correctly result in `undefined`.
+- `runtime/eval`: Fixed an issue in `any` and `all` expressions to ensure correct boolean logic when dealing with a collection containing `undefined`.
+- `imports`: Fixed an issue where import processes were not killed, which caused non-graceful closures for the clients.
+- `lang/scanner`: Fixed an issue where inline `#` style comments were not being consumed.
+
+## 0.16.0 (October 14, 2020)
+
+BREAKING CHANGES:
+
+- `cmd/doc`: Removal of the deprecated `sentinel doc` command.
+- `sentinel`: Replace `whitelist` and `blacklist` with `allowlist` and `denylist`, as per inclusive language changes.
+
+FEATURES:
+
+- `imports/version`: Added a new import, `version`, which supplies helpers for dealing with semantic versioning.
+- `cmd/apply`: Added an `HCL` based configuration file, which will eventually deprecate the legacy `JSON` configuration.
+- `cmd/apply`: Added support for download remote policies and modules.
+- `cmd/test`: Added support for downloading remote test modules.
+- `cmd/apply`: Added support for evaluating a policy based on it's key within the configuration.
+- `cmd/apply`: Added support for running all policies in a configuration by default.
+
+## 0.15.6 (July 7, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.15.5 (May 20, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel integrations. There are no user-facing changes.
+
+## 0.15.4 (May 13, 2020)
+
+BUG FIXES:
+
+- `imports`: Fixed an issue with the standard imports that was affecting the handling of null data within lists.
+
+## 0.15.3 (April 16, 2020)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where `IndexExpr` were not rewritten, as well as ensuring `SelectorExpr` uses any nested rewrites.
+- `lang/printer`: Fixed issue printing deeply nested structures and/or loops.
+
+IMPROVEMENTS:
+
+- `imports/decimal`: Added alias methods to improve consistency of method names within the `decimal` import. The alias methods are `is_nan` for `isnan`, as well as both `is_inf` and `is_infinite` for `isinfinite`.
+- `command/apply`: `sentinel apply` will now output the policy description when a trace is forced via `-trace`.
+- `sentinel`: Improved `String` output formatting of Policy docstring for `EvalPolicyResult`.
+
+## 0.15.2 (April 2, 2020)
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue with the `printer` that was causing a panic
+ when a `#` line comment was used with no text following it.
+- `imports/types`: Fixed an issue with the `types` import where calls to
+ `type_of` for undefined types were incorrectly returning as map types. These
+ will now be correctly be identified as undefined as expected.
+
+## 0.15.1 (March 6, 2020)
+
+BUG FIXES:
+
+- `sentinel`: Fixed how modules are managed internally in the runtime to correct
+ race conditions in concurrent scenarios and to allow for per-evaluation module
+ restrictions. This functionality is only of interest to embedded applications
+ with long-lived runtimes and is currently not implemented in any HashiCorp
+ integration.
+
+## 0.15.0 (March 5, 2020)
+
+NOTES:
+
+- **Windows Digital Signature**. Our windows releases for this version and up will be signed
+ and verified according to Microsoft's requirements.
+
+FEATURES:
+
+- `cmd/config`: Modules can now be loaded off of local disk using the Sentinel
+ CLI. For information on how to do so, see the Sentinel documentation.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: Changed modules so that they now have singleton scope when
+ loaded. Importing a module under two different aliases, or within a nested
+ module, will now share scopes - modification of state in one will alter the
+ other.
+- `lang/object`: Introduced an interface to allow the duplication of various
+ types, like collections.
+- `runtime/eval`: Changed how collection data is returned from modules. This
+ data is now duplicated to prevent accidental or deliberate circumvention of
+ the prohibition on assignment to import data, and brings behavior to parity
+ with binary imports.
+
+## 0.14.4 (February 6, 2020)
+
+IMPROVEMENTS:
+
+- `imports/time`: Changed the `add` timespace function in the `time` import to
+ allow it to take float types as durations. These values will be truncated to
+ the appropriate integer-value duration.
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where out-of-place right-hand braces were being
+ parsed as empty statements, instead of raising syntax errors.
+
+## 0.14.3 (January 22, 2020)
+
+IMPROVEMENTS:
+
+- `cmd/test`: Fail when an assertion is not found in a trace.
+- `cmd/test`: Added a message to notify when no rules were evaluated in a test.
+
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue where import aliases (the `as baz` in `import
+ "foo/bar" as baz`) were being removed when formatting with `sentinel fmt`.
+
+- `imports/http`: Fixed an issue with the http import where the client library's
+ debug logs were being printed to standard error.
+
+## 0.14.2 (January 15, 2020)
+
+NOTES:
+
+With this release, we are discontinuing support for MacOS 32-bit. 64-bit builds
+are now the only builds available for MacOS.
+
+IMPROVEMENTS:
+
+- `lang/printer`: A newline is now added to files formatted via `sentinel fmt`.
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue in printing where multi-line expressions would
+ indent infinitely. They will now only indent once at the most. This is mostly
+ seen when using `sentinel fmt`.
+- `runtime/encoding`: Fixed an issue where if a plugin returned a deeply
+ embedded undefined value, the value was instead decoded into a map.
+
+## 0.14.1 (January 6, 2020)
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where reserved words could not be used as
+ selectors when the selector expression was the last lexeme on a line.
+
+## 0.14.0 (December 18, 2019)
+
+LANGUAGE CHANGES:
+
+- **Filter Expression** `filter`. A new quantifier expression, `filter` has been added.
+ `filter` returns a subset of the provided collection based on a boolean condition that
+ is asserted for each element.
+
+NOTES:
+
+- **Apple Notarization**. Our darwin releases for this version and up will be signed
+ and notarized according to Apple's requirements.
+
+## 0.13.1 (November 25, 2019)
+
+BUG FIXES:
+
+- `runtime/eval`: Parameters are no longer allowed in mock files. Adding one to
+ a mock will result in a runtime error.
+- `runtime/eval`: Fixed an issue where import calls from within mocks were
+ failing under certain circumstances.
+- `imports/decimal`: `int` should now correctly provide the truncated integer
+ representation of a decimal number, not the rounded one.
+
+## 0.13.0 (November 15, 2019)
+
+LANGUAGE CHANGES:
+
+- **Policy Parameters**. The new [policy
+ parameters](https://docs.hashicorp.com/sentinel/language/parameters) feature
+ allows authors to use a `param` declaration at the top of a policy to supply
+ values that are expected to come from outside of a policy. See the linked
+ documentation for more details.
+
+FEATURES:
+
+- `imports/http`: The [`http`
+ import](https://docs.hashicorp.com/sentinel/imports/http) is a new addition
+ to the standard library enabling the use of HTTP-accessible data from outside
+ the runtime in policy rules.
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where loading other imports before a mocked
+ import would cause those imports to no longer be visible from within the
+ policy.
+
+## 0.12.0 (October 7, 2019)
+
+LANGUAGE CHANGES:
+
+- **New `case` statement**. This statement is a selection control mechanism to
+ conditionally execute a branch based on expression equality, allowing
+ simplification of complex conditional chains that may otherwise need to be
+ written with `else if`. See the [Case
+ Statements](https://docs.hashicorp.com/sentinel/language/spec#case-statements)
+ section of the Sentinel language specification for more details.
+
+IMPROVEMENTS:
+
+- `lang/semantic`: Added a semantic check to ensure usage of append is not using
+ a return value.
+
+BUG FIXES:
+
+- `runtime/eval`: Corrected an issue where calling a method on an import object
+ value that was the result of a method call on another import object value
+ would have erroneously tried to call an import of the name of the "parent"
+ import object value. Example: in `a = subject.new(); b = a.call(); c =
+ b.call()`, `b.call()` would attempt to call a method named `call` on the root
+ namespace for an import named `a`. This has now been corrected so that
+ `b.call()` will now correctly call the `call` method for the respective object
+ namespace residing in the import named `subject`.
+- `imports`: Some standard imports may have been returning null for some unknown
+ keys in objects when they should have been returning undefined. This was due
+ to an SDK issue which was corrected in SDK version 0.3.2, which has now been
+ corrected.
+- `cmd/test`: `sentinel test` will now correctly fail a policy if it encounters
+ an error.
+- `cmd/test`: `sentinel test` will now correctly display errors and other output
+ that were missing due to a formatting issue.
+- `runtime/eval`: The `append` builtin now correctly returns undefined for all
+ calls, as called for by the Specification. Note that in most cases, the
+ semantic check outlined above will trigger an error if the return value is
+ used.
+
+## 0.11.0 (September 5, 2019)
+
+LANGUAGE CHANGES:
+
+- **New builtin function:** `range()`. This function existed in the spec in
+ earlier versions but was removed as it lacked an implementation. This has now
+ been implemented and re-added to the spec. See the
+ [Range](https://docs.hashicorp.com/sentinel/language/spec#range) section of
+ the Sentinel Specification for more details.
+- Lists are now comparable. Lists are equal if their corresponding elements are
+ comparable and equal.
+- Method calls on values returned by imports are now supported. See the
+ [Imports](https://docs.hashicorp.com/sentinel/language/spec#imports) section
+ of the Sentinel Specification for more details.
+
+FEATURES:
+
+- `imports/decimal`: This is a new import designed to do exact precision
+ mathematical calculations.
+- `runtime/eval`: Compound call expressions that refer to imports (example:
+ foo.bar().baz() when `foo` is a loaded import) will now function as expected.
+ Previously, this was only supported up to the first call (example:
+ `foo.bar()`).
+- `imports/time`: Timespaces returned by calls such as `time.now` are now
+ callable. Example: `t = time.now; t.after(some_previous_time)` will now
+ function.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: The implementation of comparison of non-comparable types has
+ now changed. Rather than triggering a runtime error, non-comparable types will
+ now return false when attempting to compare.
+
+## 0.10.4 (August 15, 2019)
+
+BUG FIXES:
+
+- `runtime/encoding`: Fixed an issue with conversion of null values that could
+ lead to crashes in imports.
+
+## 0.10.3 (July 15, 2019)
+
+BUG FIXES:
+
+- `command/config`: Parsing a configuration file where malformed data is
+ encountered after apparently properly-formed data will now report an error. An
+ example would be a situation where misplaced braces would cause a JSON object
+ to only parse part of the configuration file.
+
+## 0.10.2 (June 25, 2019)
+
+BUG FIXES:
+
+- `lang/parser`: Corrected an issue where parsing certain compound binary
+ expressions where the negation predicate (`not`) was in use would cause the
+ negation to have no effect. Example: `foo else "bar" not in "baz"` would have
+ been parsed and evaluated as `foo else "bar" in "baz"`, effectively producing
+ the opposite result.
+
+## 0.10.1 (May 9, 2019)
+
+BUG FIXES:
+
+- `command/test`: `sentinel test` now displays policies without
+tests as an unknown result with [no test files], instead of the somewhat
+erroneous behavior of displaying it as a PASS. A full test run with a mixture
+of passing tests and no tests still results in an overall successful result.
+
+## 0.10.0 (April 18, 2019)
+
+LANGUAGE CHANGES:
+
+- Mixed-number arithmetic operations are now allowed. Addition (`+`),
+ subtraction (`-`), multiplication (`*`), division (`/`), and remainder
+ operations (`%`) are no longer restricted to number values of the same
+ type, and can mix integer and floating point. The result of these operations
+ is always a floating-point number.
+- Remainder (modulo, `%`) operations are now allowed on floating-point numbers.
+
+
+BUG FIXES:
+
+- `lang/parser`: Fixed a bug that affected the use of `not` with `contains`,
+ `matches`, or `in` in a compound expression.
+- `runtime/eval`: Fixed error messages on evaluation errors with `contains` and
+ `in` to indicate that strings are an allowed type along with lists and maps.
+
+
+## 0.9.2 (March 15, 2019)
+
+This is a dependency update related to the changes mentioned in 0.9.1. No other
+changes have been made.
+
+## 0.9.1 (March 14, 2019)
+
+This is a patch release that is required to integrate with the latest versions
+of the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). No other
+changes have been made.
+
+## 0.9.0 (January 28, 2019)
+
+FEATURES:
+
+- `imports/strings`: Added a `join` function to the `strings` import. This can
+ be used to join a list into a string with a specific separator.
+ Multi-dimensional lists and all Sentinel primitives are supported.
+
+## 0.8.1 (January 17, 2019)
+
+BUG FIXES:
+
+- `lang/eval`: Fixed a bug that prevents the effective use of `return`
+ statements in `for` loops.
+
+## 0.8.0 (January 14, 2019)
+
+FEATURES:
+
+- Mocks can now be represented by Sentinel code. This allows for the mocking of
+ functions and other complex data structures that cannot be represented in
+ JSON. For more information on using this feature via mocks, click
+ [here](https://docs.hashicorp.com/sentinel/commands/config#mocking-with-sentinel-code).
+
+
+IMPROVEMENTS:
+
+- Import validation has now been moved to the semantic checking phase. This
+ should result in better reporting of validation errors. In addition, import
+ validation will now enforce the use of an `as` identifier when an import path
+ is not a valid identifier on its own (example: `import "foo/bar"`).
+
+## 0.7.0 (December 12, 2018)
+
+BUG FIXES:
+
+- There have been changes to the runtime in how scope is handled over multiple
+ policy executions. Scope is now correctly unique per single policy execution,
+ and values set or builtins that are overridden in one policy will no longer
+ affect those values within another.
+
+## 0.6.0 (November 30, 2018)
+
+FEATURES:
+
+- `imports/runtime`: This new import allows for one to check various aspects of
+ the Sentinel runtime as it may be embedded in the simulator or a specific
+ implementation. For now, it allows the version to be checked.
+
+## 0.5.1 (November 28, 2018)
+
+IMPROVEMENTS:
+
+- `imports/time`: Added the `zone` and `zone_string` attributes to assist with
+ validation of a timespace's zone.
+- `command/fmt`: Added a new -check flag. This option does not commit changes,
+ but instead checks to see what files need formatting and outputs them on
+ stdout.
+
+BUG FIXES:
+
+- `command/test`: Ensure that passing test results are correctly output one per
+ line. Tests are also now run in a deterministic fashion based on
+ lexicographical (alphabetical) order.
+- `imports/time`: `month_name` and `weekday_name` will now show up correctly in
+ a returned timespace result.
+
+
+## 0.5.0 (November 5, 2018)
+
+IMPROVEMENTS:
+
+- `spec`: Selectors can now contain any reserved word (example: `rule`) or
+ keyword operator (example: `any`, `all`, `is`, `not`). This only works for the
+ _selector_ part of the expression (after the first period) - the first primary
+ expression (before the first period) still needs to be an identifier that does
+ not conflict with reserved words.
+
+BUG FIXES:
+
+- The simulator should now display import function call names correctly in
+ import errors.
+
+## 0.4.0 (October 1, 2018)
+
+FEATURES:
+
+- `builtin`: Added the `bool` built-in type conversion function. Booleans will
+ also now accepted as conversion into other values as well, with the full list
+ of behaviors available in the spec.
+
+## 0.3.2 (September 27, 2018)
+
+FEATURES:
+
+- `command/apply`: `sentinel apply` now prints out messages output by the
+ `print()` function when a trace is output on policy failure, or when a trace
+ is forced with `-trace`.
+- `imports/time`: Added the `month_name` and `weekday_name` keys to the
+ timespace, which return full-English names for the month and day of the week.
+
+
+BUG FIXES:
+
+- `command/fmt`: `sentinel fmt -` Will no longer print out the filter status
+ message on the output stream when `-write=false` Is not explicitly stated.
+ This brings the behavior of the command in line with the help text.
+- `runtime`: Index operations on the right-hand-side that have negative indexes
+ that go out of range (example: `length(list) * -1 - 1`) now correctly return
+ `undefined`. left-hand-side index assignments with a out-of-range negative
+ index still return runtime errors.
+
+## 0.3.1 (August 3, 2018)
+
+BUG FIXES:
+
+- `runtime`: Basic index assignment has been implemented as per the spec.
+- `runtime`: Index expressions for lists with negative indexes will no longer panic if the
+ list index is less than `length(list) * -1`.
+
+## 0.3.0 (July 20, 2018)
+
+FEATURES:
+
+- **New standard import: `types`.** This can be used to dynamicaly detect the
+ type of some value.
+
+## 0.2.0 (April 11, 2018)
+
+FEATURES:
+
+- **New standard import: `json`.** Marshal and unmarshal JSON documents and
+ access their contents as native Sentinel values.
+- **`break` and `continue`.** These are now both specified and implemented.
+ `break` allows loop exiting and `continue` allows immediate execution of the
+ next iteration.
+
+IMPROVEMENTS:
+
+- runtime: `print()` map values are now ordered alphabetically by keys.
+
+BUG FIXES:
+
+- command/test: If no `test` block exists, test behaves like it is asserting
+ `main: true`.
+- runtime: default maximum stack depth to 500
+- runtime: `print()` map values now appear like more typical maps.
+- runtime: division by zero is an error, not a crash
+- runtime: plugins that send map values with `null` values now decode properly
+ into native Sentinel values.
+
+## 0.1.0 (September 19, 2017)
+
+Initial release.
+
+
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/commands/apply.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/commands/apply.mdx
new file mode 100644
index 0000000000..74b74d41fa
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/commands/apply.mdx
@@ -0,0 +1,66 @@
+---
+page_title: 'Command: apply'
+sidebar_current: docs-commands-apply
+description: >-
+ The `sentinel apply` command is used to execute a policy locally for
+ development purposes.
+layout: docs
+---
+
+# Command: `apply`
+
+The `sentinel apply` command is used to execute a policy locally for development
+purposes.
+
+## Usage
+
+Usage: `sentinel apply [options] POLICY`
+
+This command executes the policy file at the path specified by POLICY. In
+addition to a path to a policy, POLICY can reference a label of a
+[policy](/sentinel/configuration#policies) entry in the configuration.
+
+Use the exit code of this command to determine the exact status of the policy
+evaluation. `0` is pass, `1` is fail, `2` is undefined (fail, but because the
+result was undefined), and `3` is a runtime error. Errors unrelated to the
+policy status itself are returned with an exit status of `9`.
+
+To control the behavior of the `apply` command, create a [configuration
+file](/sentinel/configuration). With this, you can define available
+[import plugins](/sentinel/concepts/imports), mock data, and global values.
+This can help you simulate a policy embedded within an application.
+
+As of the 0.16 release, POLICY is optional. When it is not supplied,
+`sentinel apply` will run **all** policies in the supplied configuration.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-config=path` - Path to a configuration file specifying available imports,
+ mock data, globals, etc. The default is `sentinel.[hcl|json]`.
+
+- `-json` Enable JSON output. Using this mode, all other output will be
+ suppressed and the full detail of the apply will be output in a
+ machine-readable format. Enabling this implies `-trace`. See the section on
+ [tracing JSON output](/sentinel/writing/tracing#json-output) for more details.
+
+- `-json-rule=RULE` Enable JSON output, outputting only the value of the
+ specified rule. When running against a policy set, the policy must be supplied
+ to enable per-rule filtering. Implies `-json`.
+
+- `-global key=value` - Set global values. This is the same as setting `global`
+ in the configuration file, and will override any of these respective values
+ set in the configuration. The value is either a string, or a JSON number,
+ array, or object. To force strings, use quotes.
+
+- `-param key=value` - Set parameters, the same as setting `param` in the
+ configuration file. Values are handled in the same way they are with the
+ `-global` flag.
+
+- `-timeout` - Allows users to specify a timeout after which the apply command will stop running.
+ There is no timeout if not provided. The timeout needs to be specified as a Duration type, eg `100ms, 5s etc`
+
+- `-trace` - Always show the execution trace. This shows intermediate
+ boolean expression values. This always shows for failed policies.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/commands/fmt.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/commands/fmt.mdx
new file mode 100644
index 0000000000..8297316d8b
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/commands/fmt.mdx
@@ -0,0 +1,33 @@
+---
+page_title: 'Command: fmt'
+sidebar_current: docs-commands-fmt
+description: The `sentinel fmt` command formats a policy source to a canonical format.
+layout: docs
+---
+
+# Command: `fmt`
+
+The `sentinel fmt` command formats a policy source to a canonical format.
+
+## Usage
+
+Usage: `sentinel fmt [options] FILE ...`
+
+This command formats all the specified policy files to a canonical format.
+
+By default, policy files are overwritten in place. This behavior can be
+changed with the `-write` flag. If a specified FILE is `-` then stdin is
+read and the output is always written to stdout.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-write=true` - Write formatted policy to the named source file. If false,
+ output will go to stdout. If multiple files are specified, the output will
+ be concatenated directly.
+
+- `-check=false` - Don't format, only check if formatting is necessary. Files
+ that require formatting are printed, and a non-zero exit code is returned if
+ changes are required.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/commands/index.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/commands/index.mdx
new file mode 100644
index 0000000000..7177093cec
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/commands/index.mdx
@@ -0,0 +1,23 @@
+---
+page_title: Commands (CLI)
+sidebar_current: docs-commands
+description: >-
+ Sentinel provides a `sentinel` command-line interface (CLI) for developing and
+ testing policies.
+layout: docs
+---
+
+# Sentinel CLI Commands
+
+The Sentinel command-line interface (CLI) allows for the developing and testing
+of policies outside of a particular Sentinel implementation. Having a standard
+workflow to develop policies is critical for our mission of [policy as
+code](/sentinel/concepts/policy-as-code).
+
+The CLI takes a subcommand to execute. The complete list of subcommands is in
+the navigation to the left.
+
+The Sentinel CLI is a well-behaved command line application. In erroneous cases,
+a non-zero exit status will be returned. It also responds to -h and --help as
+you'd expect. To view a list of the available commands at any time, just run
+`sentinel` with no arguments.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/commands/test.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/commands/test.mdx
new file mode 100644
index 0000000000..ed50aacc22
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/commands/test.mdx
@@ -0,0 +1,53 @@
+---
+page_title: 'Command: test'
+sidebar_current: docs-commands-test
+description: The `sentinel test` command runs policies against the Sentinel test framework.
+layout: docs
+---
+
+# Command: `test`
+
+The `sentinel test` command runs policies against the Sentinel test framework.
+
+To learn more about testing, see the [testing
+policies](/sentinel/writing/testing) page.
+
+## Usage
+
+Usage: `sentinel test [options] [PATH] ...`
+
+This command runs the defined test cases against a set of policies.
+
+The test cases are expected to live at the path `test//*.[hcl|json]` relative
+to the policy being tested. `` should be the name of the policy
+excluding the file extension. The files should be in the [configuration
+file structure](/sentinel/configuration). The `test` key is used for
+assertions. Test cases ignore the root level configuration file and must have
+all required configuration provided in each test case.
+
+The PATH arguments specified may be a list of zero or more files or directories.
+When no arguments are given, it defaults to the current directory. When a
+directory is given, all policies ending with the extension `.sentinel` are
+tested.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-json` - Suppress normal output and output test results as a JSON document.
+ See the [testing JSON output](/sentinel/writing/testing#test-json-output)
+ section for more details.
+
+- `-run=regexp` - Run only tests matching the regular expression pattern.
+ A `/` splits policy name and test case name.
+
+- `-verbose` - Show tracing and log output for tests that succeed in addition
+ to tests that fail. By default, traces and logs are only shown for tests that
+ fail.
+
+- `-maxConcurrency` - Allows users to specify the number of tests to be run concurrently.
+ Defaults to the number of logical CPUs if not provided. To run tests sequentially, use `-maxConcurrency=1`
+
+- `-timeout` - Allows users to specify a timeout after which the test command will stop running.
+Defaults to 5 minutes if not provided. The timeout needs to be specified as a Duration type, eg `100ms, 5s etc`
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/concepts/enforcement-levels.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/concepts/enforcement-levels.mdx
new file mode 100644
index 0000000000..fc4b8062d0
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/concepts/enforcement-levels.mdx
@@ -0,0 +1,43 @@
+---
+page_title: Enforcement Levels
+sidebar_current: docs-concepts-levels
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Enforcement Levels
+
+Enforcement levels are a first class concept in Sentinel allowing pass/fail
+behavior to be associated separately from the policy logic. This enables
+any policy to be a warning, allow overrides, or be absolutely mandatory.
+Because this level is not part of the policy body itself, different uses of
+the same policy can have different enforcement levels.
+
+Sentinel has three enforcement levels:
+
+- **Advisory:** The policy is allowed to fail. However, a warning should be
+ shown to the user or logged. Advisory is the default enforcement level.
+
+- **Soft Mandatory:** The policy must pass unless an override is specified.
+ The semantics of "override" are specific to each Sentinel-enabled application.
+ The purpose of this level is to provide a level of privilege separation
+ for a behavior. Additionally, the override provides non-repudiation since
+ at least the primary actor was explicitly overriding a failed policy.
+
+- **Hard Mandatory:** The policy must pass no matter what. The only way to
+ override a hard mandatory policy is to explicitly remove the policy.
+ It should be used in situations where an override is not possible.
+
+## Configuring Enforcement Levels
+
+Enforcement levels are configured when a policy is deployed to a
+Sentinel-enabled application. The exact mechanism that the level is specified
+is determined by each application. Please reference the documentation for
+your Sentinel-enabled application for more information.
+
+Enforcement levels are not configured and are not known by the policy body
+itself. All policies should be written to describe exactly the behavior
+they're attempting to control. For example, a policy that restricts deploys
+to business hours should be written exactly like so. When that policy is
+configured on an application, the operator may specify that it is advisory,
+soft, or hard mandatory.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/concepts/imports.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/concepts/imports.mdx
new file mode 100644
index 0000000000..1306e1512e
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/concepts/imports.mdx
@@ -0,0 +1,32 @@
+---
+page_title: Imports
+sidebar_current: docs-concepts-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom imports](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only
+local context for making policy decisions.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that the
+import calendar allows access to engineer calendars. This import only exists
+for the purpose of this example and at the time of writing is not a real
+available import.
+
+```json sentinel
+{
+ "policy": "import \"calendar\"\n// Get the calendar for Bob for today\nbob_calendar = calendar.of(\"bob\").today\n\n// Allow this policy to pass if Bob is not on vacation.\nmain = rule { not bob_calendar.has_event(\"vacation\") }",
+ "mocks": {
+ "calendar": "has_event = func(event) {\n\treturn true\n}\nof = func(name) {\n\treturn { \"today\": { \"has_event\": has_event } }\n}"
+ }
+}
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/concepts/index.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/concepts/index.mdx
new file mode 100644
index 0000000000..ce7ac51454
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/concepts/index.mdx
@@ -0,0 +1,15 @@
+---
+page_title: Basic Concepts
+sidebar_current: docs-concepts
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Basic Concepts
+
+This section covers some high level basic concepts that are important
+to understand for day to day Sentinel usage. Every page in
+this section is recommended reading for anyone consuming
+or extending Sentinel.
+
+Please use the navigation to the left to learn more about a topic.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/concepts/language.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/concepts/language.mdx
new file mode 100644
index 0000000000..ce91f3eb8f
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/concepts/language.mdx
@@ -0,0 +1,91 @@
+---
+page_title: Policy Language
+sidebar_current: docs-concepts-language
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Policy Language
+
+Sentinel defines and uses its own [policy language](/sentinel/language).
+
+The language was designed to be approachable by non-programmers, since
+there are many use cases where the individual defining policy may not
+be a developer. However, the language includes constructs that
+are familiar to developers to enable powerful policies.
+
+To learn more about the language, please see the
+[writing policy section](/sentinel/writing)
+or the [language reference](/sentinel/language).
+
+An example of the policy language is shown below:
+
+```sentinel playground
+import "time"
+
+# Validate time is between 8 AM and 4 PM
+valid_time = rule { time.now.hour >= 8 and time.now.hour < 16 }
+
+# Validate day is M - Th
+valid_day = rule {
+ time.now.weekday_name in ["Monday", "Tuesday", "Wednesday", "Thursday"]
+}
+
+main = rule { valid_time and valid_day }
+```
+
+## Why?
+
+To explain why Sentinel defines and uses its own language, the question
+can be more easily split into roughly two types of languages: _configuration_ languages
+and _programming_ languages.
+
+### Configuration Languages
+
+Configuration languages are formats such as JSON, YAML, XML, etc. Many applications
+use configuration languages as their ACL format. Configuration languages
+are good for static, declarative information. ACL systems are a good fit
+for this. ACL systems are focused, providing the limiting options necessary
+to restrict access to a system.
+
+Configuration languages are not good for dynamic or logical rules. They
+typically only contain limited conditions, loops, or functions. Further
+configuration is often declarative, which can introduce complexities to
+logical statements that depend on ordering.
+
+The use cases that Sentinel was built for require the ability to perform
+complex behavior that didn't model well into a configuration format or a
+declarative form.
+
+### Programming Languages
+
+The Sentinel language was designed with the following goals:
+
+- **Non-programmer friendly.** Sentinel was built to be used by non-programmers.
+ For the use cases we discovered, non-programmers needed the ability to
+ enforce certain rules within a system. For example, a person responsible for
+ compliance may need to insert rules into a system.
+
+- **Programmer friendly.** At the same time, the language needed to support
+ programmer-friendly constructs such as conditionals, loops, and functions
+ for complex policies that a programmer may be writing.
+
+- **Embeddable.** Sentinel is embedded in existing software. The language
+ itself needs to be easily embeddedable. Further, Sentinel was designed
+ to be embedded in [HashiCorp](https://www.hashicorp.com) software
+ written in Go, so it needed to be easily embeddable in Go.
+
+- **Safe.** The language is used in highly security-sensitive environments.
+ It must not be able to crash its host system or access system resources
+ without explicit approval.
+
+Prior to building our own language, Sentinel evaluated other languages.
+Some languages worked quite well. As we continued to develop Sentinel, we
+found that the flexibility and control over designing our own language
+outweighed the costs.
+
+Because the language itself doesn't need to be general purpose, building
+a language focused on policy proved to be the best route for Sentinel.
+It allows us to build powerful tracing capabilities for debugging, make
+interesting performance choices, and extend the language as needed in the
+future.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/concepts/policy-as-code.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/concepts/policy-as-code.mdx
new file mode 100644
index 0000000000..6dac593e18
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/concepts/policy-as-code.mdx
@@ -0,0 +1,72 @@
+---
+page_title: Policy as Code
+sidebar_current: docs-concepts-pac
+description: >-
+ Policy as code is the idea of writing code in a high-level language to manage
+ and automate policies. By representing policies as code in text files, proven
+ software development best practices can be adopted such as version control,
+ automated testing, and automated deployment.
+layout: docs
+---
+
+# Policy as Code
+
+Policy as code is the idea of writing code in a high-level language to
+manage and automate policies. By representing policies as code in text files,
+proven software development best practices can be adopted such as version
+control, automated testing, and automated deployment.
+
+Many existing policy or ACL systems do not practice policy as code. Many
+policies are set by clicking in a GUI, which isn't easily repeatable nor
+versionable. They usually don't provide any system for testing policies
+other than testing an action that would violate the policy. This makes it
+difficult for automated testing. And the policy language itself varies by
+product.
+
+Sentinel is built around the idea and provides all the benefits of policy as code.
+
+## Benefits
+
+Policy as code provides a number of benefits:
+
+- **Sandboxing.** Policies provide the guardrails for other automated systems.
+ As the number of automated systems grow, there is also a growing need to
+ protect those automated systems from performing dangerous actions. Manual
+ verification is too slow; policies need to be represented as code to
+ keep up with other automated systems.
+
+- **Codification.** By representing policy logic as code, the information
+ and logic about a policy is directly represented in code and can be augmented
+ with comments rather than relying on oral tradition to learn about the
+ reason for policies.
+
+- **Version Control.** Policies are encouraged to be stored as simple text
+ files managed by a version control system. This lets you gain all the
+ benefits of a modern VCS such as history, diffs, pull requests, and more.
+
+- **Testing.** Policies are just code. Their syntax and behavior can be
+ [easily validated with Sentinel](/sentinel/commands/test). This also encourages
+ automated testing such as through a CI. Paired with a VCS system, this
+ allows a pull request workflow to verify that a policy keeps the system
+ behavior as expected before merging.
+
+- **Automation.** With all policies as code in simple text files, various
+ automation tools can be used. For example, it is trivial to create tools
+ to automatically deploy the policies into a system.
+
+## Sentinel and Policy as Code
+
+Sentinel fully embraces policy as code in a number of ways:
+
+- **Language.** All Sentinel policies are written using the
+ [Sentinel language](/sentinel/concepts/language). This language is
+ made to be inputted directly to text files. As an additional benefit,
+ all Sentinel-enabled applications share the same policy language.
+
+- **Development.** Sentinel provides a [CLI](/sentinel/commands)
+ for development and testing. This local CLI can be used to verify policies
+ before deploying them to a system.
+
+- **Testing.** Sentinel provides a [test framework](/sentinel/commands/test)
+ designed specifically for automation. This allows developers and CI systems
+ to further verify policies.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/configuration/index.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/configuration/index.mdx
new file mode 100644
index 0000000000..c7d4a8e3bd
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/configuration/index.mdx
@@ -0,0 +1,422 @@
+---
+page_title: Sentinel CLI Configuration File Syntax
+sidebar_current: docs-configuration
+description: >-
+ The Sentinel CLI's configuration file can be used to control the
+ behavior of the simulator during apply and test operations.
+layout: docs
+---
+
+# Sentinel CLI Configuration File Syntax
+
+The Sentinel CLI's configuration file can be used to control the behavior
+of the simulator during [`apply`](/sentinel/commands/apply) and
+[`test`](/sentinel/commands/test) operations.
+
+## Usage
+
+The configuration file is used in different ways depending on what operation you
+are trying to execute.
+
+### Apply
+
+When using `sentinel apply`, configuration files that have the `.hcl`
+extension are loaded into a single configuration. This allows for configuration
+to be split across multiple files, which is useful for large policy sets. To
+supply a single configuration file, the `-config=FILE` flag can be used, where
+`FILE` is the path to the configuration file. This argument also allows for a
+`.json` configuration file to be supplied. The default is `sentinel.[hcl|json]`.
+
+See the [apply command](/sentinel/commands/apply) reference for more details.
+
+### Test
+
+When using `sentinel test`, each file matching `test//*.[hcl|json]` is a
+configuration file representing a single test case, where `` is the name
+of the policy being tested. Configure assertions for each [test
+case](#test-cases) using the test section.
+
+See the [test command](/sentinel/commands/test) reference for more details.
+
+#### Remote sources
+
+When declaring a `policy` or `module` block within the configuration, a
+`source` attribute must be supplied. This `source` should declare the location
+of the resource, which can be either a local file or a URL pointing to a remote
+file. Examples of local `source` attributes are detailed both in the
+[Policies](#policies) and [Modules](#modules) sections of this page. Below
+are a few examples of remote `source` attributes.
+
+Example:
+
+```hcl
+policy "foo" {
+ source = "git::https://github.com/hashicorp/sentinel-example.git//main.sentinel"
+ enforcement_level = "hard-mandatory"
+}
+```
+
+The above example will fetch the policy from the provided URL and place it into
+the cache ready for use. Be sure to read the
+[Remote Sources](/sentinel/configuration/remote-sources) page for an
+in-depth overview.
+
+## Configuration File Reference
+
+The format of the configuration file is either JSON or HCL. The available
+blocks are:
+
+- [`mock`](#mock-imports) - A mock import specification.
+- [`policy`](#policies) - Configuration for a [policy](/sentinel/writing).
+- [`import`](#imports) - Configuration for a [module](/sentinel/extending/modules), [standard import](/sentinel/imports), [plugin](/sentinel/extending/plugins) or
+ [static import](/sentinel/extending/static-imports).
+- [`global`](#globals) - Data that is inserted into the global scope.
+- [`param`](#parameters) - Values for [parameters](/sentinel/language/parameters) defined in the policy.
+- [`test`](#test-cases) - Test cases for the [test command](/sentinel/commands/test).
+- [`sentinel`](#sentinel) - Configuration to manage the Sentinel runtime
+
+### Mock Imports
+
+Mock imports allow running a policy with an
+[import](/sentinel/concepts/imports) that you may not have access to or is
+too difficult to run. For example, it can be easier to mock data for [Terraform
+Enterprise](https://www.terraform.io/docs/enterprise/sentinel/index.html)
+locally instead of having to run a workspace through a plan/policy check cycle.
+
+Mock imports are specified using the `mock` block, labelled by the import
+name that you want to mock. For example, if you wanted to mock the `time`
+import, you could create an entry with the `time` label pointing to the data
+you want to mock.
+
+The data can take one of two forms:
+
+#### Mocking static data
+
+Static data can be mocked directly via HCl using the `data` attribute on the
+`mock` block.
+
+Example:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ hour = 9
+ minute = 42
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"time\"\n\nmain = rule { time.now.hour is 9 }",
+ "mocks": {
+ "time": "now = { \"hour\": 9, \"minute\": 42 }"
+ }
+}
+```
+
+#### Mocking with Sentinel code
+
+There are some Sentinel types that raw data cannot mock, such as functions,
+and maps that don't have string keys. To mock this data, you can use
+a Sentinel file itself. In this case, you can set the `module` attribute to
+a file with the Sentinel code in it, relative to the current working directory in
+the event of `apply`, or relative to the configuration file location in the
+event of `test`.
+
+Note that `main` is not required in a mock data file.
+
+Example:
+
+```hcl
+mock "foo" {
+ module {
+ source = "mock-foo.sentinel"
+ }
+}
+```
+
+`mock-foo.sentinel` would contain:
+
+```sentinel
+bar = func() {
+ return "baz"
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"foo\"\n\nmain = rule { foo.bar() is \"baz\" }",
+ "mocks": {
+ "foo": "bar = func() { return \"baz\" }"
+ }
+}
+```
+
+### Policies
+
+The `policy` block allows for the the configuration of policies to assist
+with integrating into other commands.
+
+Each `policy` block has a label to identify the policy, with the block
+providing configuration of the policy. The available configuration options for
+policy are `source`, `enforcement_level` and an optional `params` attribute. The
+`source` key provides the location of the policy, while `enforcement_level` is
+currently used by integrations such as [HCP Terraform](/terraform/cloud-docs/policy-enforcement/manage-policy-sets#enforcement-levels).
+For more information on the `source` value, see [Policy and Module Sources](#policy-and-module-sources).
+
+The optional `params` attribute is used to provide values to [parameters](/sentinel/language/parameters)
+defined within the policy file.
+
+```hcl
+policy "foo" {
+ source = "foo.sentinel"
+ enforcement_level = "hard-mandatory"
+ params = {
+ "name" = "Sample"
+ }
+}
+```
+
+If `enforcement_level` is not supplied, it will fallback to the `advisory` level.
+
+### Imports
+
+Import configuration allows you to configure [modules](/sentinel/extending/modules),
+[standard imports](/sentinel/imports) and [import plugins](/sentinel/extending/plugins).
+
+The `import` is a multi-label block, with the first label determining the kind of import
+being configured. Currently the supported values for this label are:
+
+* [`"module"`](#import-modules) for configuring import modules
+* [`"plugin"`](#import-plugins) for configuring standard imports and import plugins
+* [`"static"`](#static-imports) for configuring static data files as imports
+
+#### Import Modules
+
+The `import "module"` block allows you to map a Sentinel import to a
+[module](/sentinel/extending/modules). The Sentinel CLI will parse the module
+source and make it available for evaluation with the policy.
+
+Each block has a label aligning to the name of the import, with
+the block set to the configuration object for the module. Currently, the only
+value within the configuration object is `source`, which points to the
+location of the module. For more information on the `source` value, see
+[Policy and Module Sources](#policy-and-module-sources).
+
+```hcl
+import "module" "foo" {
+ source = "modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "modules/bar.sentinel"
+}
+```
+
+Note when using [`sentinel test`](/sentinel/commands/test), module paths must be
+specified relative to the location of the configuration file.
+
+```hcl
+import "module" "foo" {
+ source = "../../modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "../../modules/bar/sentinel"
+}
+```
+
+See [Writing and Using a
+Module](/sentinel/extending/modules#writing-and-using-a-module) for more details
+on using a module with this configuration.
+
+### Import Plugins
+
+Import `"plugin"` configuration allows you to configure both [standard imports](/sentinel/imports)
+as well as [import plugins](/sentinel/extending/plugins) that can be used within
+a policy. If defining a custom import plugin, the Sentinel CLI will launch the
+plugin, connect to it, configure it, and execute it as needed by the policy.
+
+The available configuration attributes for an `import "plugin"` are:
+
+- `source` (string, required) - Path to the import plugin executable.
+- `args` (list of string, optional) - A list of arguments to pass to the
+ executable when starting it.
+- `env` (map of string to string, optional) - A set of environmental variables
+ to set when launching the plugin.
+- `config` (map, optional) - Configuration for the plugin itself. This is
+ specific to each individual plugin. Please reference the documentation for
+ the plugin for more information.
+
+Standard import example:
+
+```hcl
+import "plugin" "time" {
+ config = { "timezone": "Australia/Brisbane" }
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"time\"\n\nmain = time.now.zone_string is \"+10:00\"",
+ "mocks": {
+ "time": "now = { \"zone_string\": \"+10:00\" }"
+ }
+}
+```
+
+Custom plugin example:
+
+```hcl
+# flipper receives a boolean and returns the opposite
+import "plugin" "flipper" {
+ source = "/path/to/flipper"
+}
+```
+
+```json sentinel
+{
+ "policy": "import \"flipper\"\n\nmain = flipper.flip(false)",
+ "mocks": {
+ "flipper": "flip = func(input) { return not input }"
+ }
+}
+```
+
+#### Static Imports
+
+Static import configuration allows you to supply a data file and make
+it available to a policy via an import statement.
+
+The available configuration attributes for an `import "static"` are:
+
+- `source` (string, required) - Path to the static data file.
+- `format` (string, required) - The format of the static data. Currently, only
+ the "json" value is supported.
+
+Static import example:
+
+```hcl
+import "static" "people" {
+ source = "./data/people.json"
+ format = "json"
+}
+```
+
+```json sentinel
+{
+ "policy": "import \"people\"\n\nmain = length(people.names) > 0",
+ "mocks": {
+ "people": "names = [\"John Smith\", \"Jane Smith\"]"
+ }
+}
+```
+
+### Globals
+
+Global data is injected directly into the global scope of the policy.
+This can be used to simulate global data that may be injected by a
+Sentinel-enabled application.
+
+Global data is specified by the `global` key. The value is a map of
+variables to inject directly into the running policy.
+
+Example:
+
+```hcl
+global "time" {
+ value = {
+ now = {
+ day = 31
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass. Notice
+that we don't have to do any `import`. The values of `global` are
+injected directly into the global scope.
+
+```json sentinel
+{
+ "policy": "main = time.now.day == 31",
+ "globals": {
+ "time": {
+ "now": {
+ "day": 31
+ }
+ }
+ }
+}
+```
+
+### Parameters
+
+The `param` section allows you to supply values for the
+[parameters](/sentinel/language/parameters) found in a policy. Values
+entered here will satisfy required parameters in a policy, in addition to
+override any defaults. Note that any of the manual parameter value methods
+supported by `sentinel apply` will override the values set here.
+
+```hcl
+param "foo" {
+ value = "bar"
+}
+```
+
+### Test Cases
+
+Test cases specify the test cases for the [test command](/sentinel/commands/test).
+
+Tests are specified by the `test` key. The value of this is a map of
+string to boolean. The key is the name of a rule and the value is the
+expected value of that rule. If a rule is not specified, than any value is
+allowed.
+
+Example:
+
+```hcl
+test {
+ rules = {
+ main = true
+ days = []
+ }
+}
+```
+
+For the policy:
+
+```json sentinel
+{
+ "policy": "valid_days = rule {\n\tfilter days as d {\n\td is \"monday\"\n\t}\n}\n\nmain = rule { valid_days is empty }",
+ "globals": {
+ "days": []
+ }
+}
+```
+
+For more information, read about the [test command](/sentinel/commands/test).
+
+### Sentinel
+
+The `sentinel` block provides configuration specific to the Sentinel runtime.
+
+An example of how the `sentinel` block can be used to enable the [terraform](/sentinel/features/terraform)
+feature is as follows:
+
+```hcl
+sentinel {
+ features = {
+ terraform = true
+ }
+}
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/configuration/overrides.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/configuration/overrides.mdx
new file mode 100644
index 0000000000..ca1a6842b1
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/configuration/overrides.mdx
@@ -0,0 +1,80 @@
+---
+page_title: Override Files
+sidebar_current: docs-configuration-overrides
+description: >-
+ The Sentinel CLI's configuration can be overriden using override files.
+ Override files merge additional settings into existing configuration.
+layout: docs
+---
+
+# Override Files
+
+As outlined in the configuration [overview](/sentinel/configuration#Apply),
+the normal behavior of Sentinel is to load all `.hcl` files into a single
+configuration object.
+
+In addition to appending multiple configuration files, Sentinel allows for the
+rare case of overriding configuration through override files. Override files
+are any files that match either `_override.{hcl,json}` or `override.{hcl,json}`.
+
+Sentinel will parse override files after it has loaded the primary
+configuration, and then process each override file in turn (in lexicographical
+order). All top level blocks other than [test](/sentinel/configuration#test-cases)
+require the block to already be defined in the primary configuration. It will
+then attempt to merge the override block into the existing configuration.
+
+## Example
+
+If you had a Sentinel configuration `sentinel.hcl` that contained the following:
+
+```hcl
+policy "main" {
+ source = "./main.sentinel"
+ enforcement_level = "advisory"
+}
+```
+
+And you created a file `override.hcl` that contained the following:
+
+```hcl
+policy "main" {
+ enforcement_level = "soft-mandatory"
+}
+```
+
+Sentinel will merge the contents of the override into the former, providing the
+following configuration:
+
+```hcl
+policy "main" {
+ source = "./main.sentinel"
+ enforcement_level = "soft-mandatory"
+}
+```
+
+## Merging Behavior
+
+The general rule, which applies in most cases, is:
+
+- A top-level block in an override file merges with a block in a normal
+ configuration file that has the same block header. The block header is the
+ block type and any quoted labels that follow it.
+- Within a top-level block, an attribute argument within an override block
+ replaces any argument of the same name in the original block.
+- Within a top-level block, any nested blocks within an override block replace
+ all blocks of the same type in the original block. Any block types that do not
+ appear in the override block remain from the original block.
+- The contents of nested configuration blocks are not merged.
+- The resulting merged block must still comply with any validation rules that
+ apply to the given block type.
+
+If more than one override file defines the same top-level block, the overriding
+effect is compounded, with later blocks taking precedence over earlier blocks.
+Overrides are processed in order first by filename (in lexicographical order)
+and then by position in each file.
+
+## Required Attributes
+
+It is important to note that all attributes inside an override file are
+considered to be optional, meaning that an override file can itself fail
+validation rules for block types.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/configuration/remote-sources.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/configuration/remote-sources.mdx
new file mode 100644
index 0000000000..b64e4d3abc
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/configuration/remote-sources.mdx
@@ -0,0 +1,165 @@
+---
+page_title: Remote Sources
+sidebar_current: docs-configuration-remote-sources
+description: >-
+ There are a number of options for formatting the URL provided to the
+ source attribute on policy and module blocks.
+layout: docs
+---
+
+# Remote Sources
+
+There are a number of options for formatting the URL provided to the
+`source` attribute on `policy` and `import "module"` blocks. This flexibility
+should solve most use cases and allow for reusable policies and modules.
+
+### Supported Protocols
+
+-> **NOTE:** All protocols are supported on the CLI, however each production
+Sentinel integration may not. Be sure to read the appropriate integration
+documentation to check the supported protocols.
+
+- Local filesystem
+- Git
+- Mercurial
+- HTTP
+- Amazon S3
+- Google GCP
+
+### Forced Protocols
+
+Due to the ambiguous nature of URLs, it is possible to force a particular
+protocol to be used during the fetch. To achieve this, simply prefix the url
+with the appropriate protocol as listed below:
+
+- `file` - Local filesystem
+- `git` - Git
+- `hg` - Mercurial
+- `http` or `https` - HTTP
+- `s3` - Amazon S3
+- `gcp` - Google GCP
+
+Example:
+
+```
+git::http://github.com/hashicorp/sentinel.git
+```
+
+The above would download the provided HTTP URL using the Git protocol.
+
+### Protocol Options
+
+In addition to some global options, there are options available to specific
+protocols to perform features such as authentication.
+
+#### Subdirectories / File Pathing
+
+When supplying URLs that point to a directory (eg. `git`), a subdirectory and
+file can be provided by suffixing the URL with `//` and the path. For example,
+if we have a git repository that has a policy, `main.sentinel` in the root, we
+could directly fetch the file by using the following:
+
+```
+git::https://github.com/hashicorp/sentinel-docs-example.git//main.sentinel
+```
+
+Or, if the file was nested in the `policies` folder:
+
+```
+git::https://github.com/hashicorp/sentinel-docs-example.git//policies/main.sentinel
+```
+
+#### Checksumming
+
+For downloads of any protocol, you can automatically verify a checksum. To
+checksum a file, append a `checksum` query parameter to the URL. The parameter
+value can be in the format of type:value or just value, where type is "md5",
+"sha1", "sha256", "sha512" or "file" . The "value" should be the actual
+checksum value or download URL for "file". When type part is omitted, type will
+be guessed based on the length of the checksum string.
+
+```
+./foo.sentinel?checksum=md5:b7d96c89d09d9e204f5fedc4d5d55b21
+
+./foo.sentinel?checksum=b7d96c89d09d9e204f5fedc4d5d55b21
+
+./foo.sentinel?checksum=file:./foo.txt.sha256sum
+```
+
+#### Unarchiving
+
+An `archive` query parameter can be supplied to explicitly state what format
+to attempt to extract, if required. The supported formats are:
+
+- `tar.gz` and `tgz`
+- `tar.bz2` and `tbz2`
+- `tar.xz` and `txz`
+- `zip`
+- `gz`
+- `bz2`
+- `xz`
+
+If a URL has an extension that matches one of the supported formats, it will
+use that format to unarchive.
+
+```
+./file.zip
+```
+
+The above would use the `zip` format to unarchive.
+
+```
+./path/to/file?archive=zip
+```
+
+Would force the `zip` format. If for some reason you required archiving to be
+disabled, this can also be achieved by using the `false` value.
+
+```
+./path/to/file?archive=false
+```
+
+#### Git (`git`)
+
+- `ref` - The Git ref to checkout. This is a ref, so it can point to a commit
+ SHA, a branch name, etc. If it is a named ref such as a branch name, it will
+ be updated to the latest on each get.
+- `sshkey` - An SSH private key to use during clones. The provided key must be
+ a base64-encoded string. For example, to generate a suitable sshkey from a
+ private key file on disk, you would run base64 -w0 \.
+ **Note:** Git 2.3+ is required to use this feature.
+- `depth` - The Git clone depth. The provided number specifies the last `n`
+ revisions to clone from the repository.
+
+The git getter accepts both URL-style SSH addresses like
+`git::ssh://git@example.com/foo/bar`, and "scp-style" addresses like
+`git::git@example.com/foo/bar`. In the latter case, omitting the `git::` forced
+prefix is allowed if the username prefix is exactly git@.
+
+The "scp-style" addresses cannot be used in conjunction with the `ssh://`
+scheme prefix, because in that case the colon is used to mark an optional port
+number to connect on, rather than to delimit the path from the host.
+
+#### Mercurial (`hg`)
+
+- `rev` - The Mercurial revision to checkout.
+
+#### HTTP (`http` or `https`)
+
+Basic Authentication
+
+To use HTTP basic authentication, simply prepend `username:password@` to the
+hostname in the URL such as `https://Aladdin:OpenSesame@www.example.com/index.html`.
+All special characters, including the username and password, must be URL
+encoded.
+
+#### S3 (`s3`)
+
+The following query parameters are available and will take priority when
+authenticating against an S3 bucket.
+
+- `aws_access_key_id` - AWS access key.
+- `aws_access_key_secret` - AWS access key secret.
+- `aws_access_token` - AWS access token if this is being used.
+- `aws_profile` - Use this profile from local ~/.aws/ config. Takes priority
+ over the other three.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/consul.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/consul.mdx
new file mode 100644
index 0000000000..7a3aaaa697
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/consul.mdx
@@ -0,0 +1,47 @@
+---
+page_title: Consul and Sentinel
+sidebar_current: docs-app-consul
+description: >-
+ Consul Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies are applied during writes to
+ the KV Store.
+layout: docs
+---
+
+# Consul
+
+[Consul Enterprise](https://www.hashicorp.com/products/consul/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies are applied during writes to the KV Store.
+
+Sentinel policies have access to the key/value being written. They can be used to
+allow or deny the modification. The information that Sentinel policies have access
+to will expand over time.
+
+The Consul integration with Sentinel is documented in depth in the
+[Consul Enterprise documentation](https://www.consul.io/docs/guides/sentinel.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Input validation depending on the name of the key.**
+
+```sentinel
+main = rule { valid_key() }
+
+required = [
+ ["port", "\\d+"], # ports must be integers
+ ["name", "\\w+"], # name must be a word
+]
+
+valid_key = func() {
+ for required as v {
+ if key is v[0] {
+ return value matches v[1]
+ }
+ }
+
+ return false
+}
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/extending/index.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/extending/index.mdx
new file mode 100644
index 0000000000..a39cb0384d
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/extending/index.mdx
@@ -0,0 +1,52 @@
+---
+page_title: Extending Sentinel
+sidebar_current: docs-extending
+description: Learn how to create your own Sentinel imports to extend Sentinel's functionality.
+layout: docs
+---
+
+# Extending Sentinel
+
+-> **NOTE:** Sentinel's extensibility is currently undergoing large
+improvements - watch this space for future updates!
+
+Sentinel can be extended by writing additional
+[imports](/sentinel/language/imports). These imports can bring new functionality
+to Sentinel by allowing access to new data or by the addition of new functions.
+This section is designed to show you how to accomplish this extensibility and
+describe how it works.
+
+Currently, the only usable way to add additional imports is via
+[modules](#modules). While [plugins](#plugins) currently work under the Sentinel
+CLI, no Sentinel integration currently supports their use.
+
+-> You may also be interested in advanced details on [import
+internals](/sentinel/extending/internals).
+
+## Modules
+
+Modules allow you to re-use Sentinel code as an import. Modules are loaded
+external of policies and mapped to imports. This is usually configured via a
+file such as the [CLI configuration file](/sentinel/configuration).
+
+See the [modules](/sentinel/extending/modules) section for more details.
+
+## Plugins
+
+-> **NOTE:** Plugins are currently only supported on the CLI and not in any
+production Sentinel integration, with the exception of existing configuration
+in [Nomad](https://www.nomadproject.io/docs/configuration/sentinel).
+
+Imports can also be implemented as plugins when Sentinel code itself is too
+restrictive to represent the import, or if you need access to features not
+normally available to the Sentinel runtime.
+
+See the [plugins](/sentinel/extending/plugins) section for more details.
+
+## Static Imports
+
+Static imports provide support for loading data files into the Sentinel runtime,
+making them available as an import.
+
+See the [static imports](/sentinel/extending/static-imports) section for more
+details.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/extending/internals.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/extending/internals.mdx
new file mode 100644
index 0000000000..7133e6df1b
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/extending/internals.mdx
@@ -0,0 +1,252 @@
+---
+page_title: >-
+ Extending Sentinel: Internals
+sidebar_current: docs-extending-internals
+description: This Section covers some details about Sentinel's import internals.
+layout: docs
+---
+
+# Extending Sentinel: Internals
+
+-> **Advanced Topic!** This page covers low-level technical details of Sentinel.
+You don't need to understand these details to effectively use Sentinel. The
+details are documented here for those who wish to learn about them.
+
+This section covers some of the internal details of Sentinel's import system.
+The goal of this section is to help remove notion of "magic" from Sentinel, to
+allow you to trust and understand what Sentinel is doing with imports, and also
+to help practitioners and developers alike understand the differences between
+the ways that imports can be loaded into Sentinel.
+
+## Language and Runtime
+
+Understanding the import system in Sentinel generally requires understanding of
+two key concepts within Sentinel: the _language_, and the _runtime
+implementation_ of the language.
+
+The Sentinel language and the rules governing it are detailed in the [Sentinel
+Language Specification](/sentinel/language/spec). A runtime implementation must
+conform to the rules laid out in the specification at a minimum. However, to
+meet the design goals of a particular implementation, the runtime may implement
+features on top of the language. The subtle implementation details within the
+runtime may also vary while still being compliant with the specification.
+
+The core runtime included within the [Sentinel CLI](/sentinel/commands) is
+sometimes referred to as the _reference implementation_ of the Sentinel
+language. This runtime is also the main runtime embedded within each
+Sentinel-enabled HashiCorp product such as HCP Terraform, Terraform Enterprise, Vault
+Enterprise, Nomad Enterprise, and Consul Enterprise. The runtime includes an API
+to allow these integrations implement Sentinel in as robust or as restrictive of
+a way as possible, so depending on the implementation, your experience may vary.
+
+## Sentinel's Import Model
+
+The concept of Imports within Sentinel is a _language_ feature. You can see the
+exact rules governing imports within the
+[Imports](/sentinel/language/spec#imports) section of the specification.
+
+Within the runtime, there are currently two major classifications of imports:
+
+- **Modules:** The mapping of Sentinel code to an import, essentially mapping a
+ scope of values to a particular package.
+- **Binary Imports:** A grouping of embedded and external plugins written using
+ the Sentinel SDK. These are comprised of internal or embedded imports (usually
+ the [standard imports](/sentinel/imports) and imports included by an
+ integration) and import plugins.
+
+Below is a diagram explaining in brief the relationship between the langauge
+and runtime features.
+
+
+
+This kind of topology ultimately minimizes the visibility of implementation
+internals to the actual policy language. This allows us to keep the Sentinel
+language itself simple and keep concerns such as module or plugin management,
+validation, and configuration out of the language. These kinds of things would
+impact the readability of a policy, possibly present security challenges, and
+would ultimately be of no use to more minimal embedded applications.
+
+### Implementation Differences Between Import Types
+
+As one would imagine, both modules and binary imports are loaded in much
+different ways, and the methods that they expose data to Sentinel differ as
+well. The effect is generally the same however across both, and we take care to
+ensure that both modules and binary imports behave as close as possible to each
+other, however there are some subtle differences:
+
+- Modules currently support the exporting of rules, but do not support function
+ calls with object receiver data (as seen in some standard imports such as
+ [`decimal`](/sentinel/imports/decimal), [`http`](/sentinel/imports/http), and
+ [`time`](/sentinel/imports/time)). This will be coming in a later release.
+- Binary imports cannot export rules. However, they do support object receiver
+ data (see [`framework.New`](/sentinel/extending/plugins#framework-new) in
+ [Extending Sentinel: Plugins](/sentinel/extending/plugins)).
+
+## Modules
+
+_Modules_ are essentially Sentinel code that is intended to be re-used in multiple
+policies as an import.
+
+The mechanism of module loading is fairly straightforward: during initialization
+of the Sentinel runtime, modules are parsed along with policies. The parsed
+_abstract syntax tree_ (AST) for each module is loaded into the runtime under
+the specified _import path_. These are then passed to the lower-level
+interpreter during the evaluation of each policy.
+
+As with policy code, during evaluation, a module's AST is walked to execute the
+code within that specific module. The module data is then stored within an
+specific scope mapped to the import name. The module data can then be accessed
+through the import via selector expressions and function calls (e.g.,
+`foo.some_map` or `foo.some_func()` for a module loaded via `import "foo"`).
+
+The AST for a module is only walked if a policy imports it, and it is _only
+walked once_. That means if a policy and a module import the same module, or a
+policy imports the same module twice (using [import
+aliases](/sentinel/language/imports#aliases)), those instances will share state.
+If you call a function that alters a singleton variable within the module, that
+singleton will be modified for all instances of the import.
+
+-> **Fun fact!** When you [mock an import with Sentinel
+code](/sentinel/configuration#mocking-with-sentinel-code), you are actually
+using a module. The module is loaded with a flag that allows it to override an
+existing import, which would normally give a runtime error.
+
+### Map and List Cloning for Module Calls
+
+It's also worthwhile noting that map and list values are cloned for modules.
+
+Consider the case where you have a module with a map singleton.
+
+```sentinel
+// modules/foo.sentinel
+a_map = {"a": "b"}
+```
+
+Normally, assignment to the singleton, even when using an index expression, is
+blocked, so `foo.a_map["c"] = "d"` would not pass semantic checking.
+
+However, even when you try to do assignment via an intermediary, you will still
+be only modifying the copy, and the original will not be affected:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+a_map_copy = foo.a_map
+a_map_copy["c"] = "d" // Only modifies copy, does not modify module singleton
+print(foo.a_map) // Still only prints {"a": "b"}
+```
+
+This is to ensure that modules are consistent with binary imports in how return
+data is handled, and the expectation that most non-object data within an import
+is read-only, with the exception of modification of singleton data via
+functions. As return of complex object and collection data in a binary import is
+always via copy, it's not possible to modify any value elements within the
+import in a similar fashion.
+
+There are also some other implications of this cloning that are of note:
+
+- Rules are not cloned, either within a list or map, or by themselves. This is
+ to ensure that [memoization](/sentinel/language/rules#lazy-and-memoized)
+ functions correctly. As only a module can export rules at this time, there is no
+ parity for this in binary imports.
+- Functions are _not_ cloned. This mainly has implications for scope - for
+ example, if you assign a function to another value, or assign an object to a
+ value that has a method that utilizes a global module variable, those calls will
+ reference and/or modify those values.
+
+Functions, while represented differently in binary imports, generally follow the
+same logic here - as they would be invoked in the same package within the binary
+import, any singleton values they accessed there would be affected in a similar
+fashion. As rules are pseudo-functions, this generally makes sense here too.
+
+## Binary Imports
+
+_Binary imports_ is a grouping referring to all imports that are designed with
+the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). These can be
+either internally embedded, or executed via a plugin.
+
+All imports found in the [standard library](/sentinel/imports) are binary
+imports, embedded internally.
+
+The main difference between internally embedded imports and external plugins is
+whether or not the runtime needs to execute a plugin binary as part of
+[lifecycle management](#lifecycle), and whether or not it needs to
+[communicate](#communication) over RPC. Internal binary imports do not require
+these steps, so their execution model is somewhat simpler; however, technically,
+they still are the same as external plugins in terms of design model and value
+conversion. Most standard imports are even tested using the [SDK test
+suite](/sentinel/extending/plugins#testing), which builds the import as an
+external plugin.
+
+The remainder of this section discusses topics mostly relevant to external
+plugins. A large amount of technical detail regarding binary import development
+(also applicable to embedded binary imports) can be seen on the
+[Plugins](/sentinel/extending/plugins) page.
+
+-> **NOTE:** At this time, plugins can only be written for the Sentinel CLI, and
+cannot be used with any of Sentinel's integrations.
+
+### Plugin Basics
+
+Sentinel plugins are built on top of the HashiCorp [go-plugin
+system](https://github.com/hashicorp/go-plugin). This is the same plugin system
+powering all pluggable HashiCorp tools such as
+[Terraform](https://www.terraform.io), [Vault](https://www.vaultproject.io), and
+more. go-plugin is a system that has been used in production for millions of
+users for over 5 years.
+
+Plugins are executable binaries. Sentinel is responsible for launching and
+managing the lifecycle of plugins. Once a plugin is running, Sentinel
+communicates with the plugin via [gRPC](https://grpc.io/). The [protocol is open
+source](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/import.proto)
+to allow anyone to write a Sentinel plugin.
+
+### Lifecycle
+
+Sentinel launches plugins and is responsible for plugin lifecycle.
+
+The runtime optimizes for latency by launching the plugin as soon as it is
+configured, rather than when a policy requires it. This ensures that the plugin
+is ready to be used immediately. A plugin is only closed when Sentinel is
+reconfigured to no longer allow that plugin or if the Sentinel-enabled
+application is closing.
+
+When a plugin is removed from the configuration, Sentinel may wait to shut down
+the plugin until all currently executing policies that are using that plugin
+complete. New policy executions will not be allowed to use the old plugins.
+
+Plugins are automatically restarted if they shut down unexpectedly. Policies
+that were executing while this happens may fail. The Sentinel system is
+improving to more gracefully understand locations where it is safe to retry an
+execution.
+
+### Communication
+
+An [initial
+handshake](https://github.com/hashicorp/go-plugin/blob/master/docs/internals.md)
+is done via stdout to determine the main communication location. Following the
+handshake, communication will either occur on a local Unix domain socket or via
+a local-only TCP socket. This communication is done mostly over the low-level
+[`Get`](https://github.com/hashicorp/sentinel-sdk/blob/2b4db07dd12b55142f0c016f301af80aa4422520/proto/import.proto#L12)
+RPC call.
+
+#### Value Conversion
+
+As can generally be seen in [Developing an Import
+Plugin](/sentinel/extending/plugins#developing-an-import-plugin), the Sentinel
+type system is not exposed to binary imports. While explicit values for null and
+undefined exist, values are mostly converted over the wire from their Go values
+to their Sentinel equivalents.
+
+This has a few implications:
+
+- As discussed in [Map and List Cloning for Module
+ Calls](#map-and-list-cloning-for-module-calls), Map and list data returned from
+ binary import value lookups or function calls is always cloned. These can be
+ freely modified without affecting anything within the binary import.
+- It's currently impossible to represent certain Sentinel types such as
+ [rules](/sentinel/language/rules) within a binary import. This is not a major
+ issue at this point in time as practitioners generally do not look to binary
+ imports for rules; we may examine this as a later time if this situation
+ changes.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/extending/modules.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/extending/modules.mdx
new file mode 100644
index 0000000000..9846d263e5
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/extending/modules.mdx
@@ -0,0 +1,135 @@
+---
+page_title: >-
+ Extending Sentinel: Modules
+sidebar_current: docs-extending-modules
+description: >-
+ Modules allow you to re-use Sentinel code as an import.
+layout: docs
+---
+
+# Extending Sentinel: Modules
+
+Modules allow you to re-use Sentinel code as an import. This allows for the
+export of any general construct that Sentinel supports, including values,
+functions, and even rules. As such, it's a great way to package code that is
+useful across multiple policies, reducing boilerplate and making final policy
+code simpler.
+
+You may want to use modules for:
+
+- **Writing a simple re-usable helper library.** If you mostly know Sentinel or
+ have helpers that you have written in Sentinel, moving these helpers to a module
+ will offer a low-friction way of facilitating their re-use.
+- **Writing re-usable rules.** If you have a set of
+ [rules](/sentinel/language/rules) you know you want to use across multiple
+ policies, bundling them into a module is a great way of abstracting the logic
+ away.
+- **Abstracting existing Sentinel imports.** Using a module is a great way to
+ extend existing Sentinel imports by abstracting the common functionality that
+ you use within an import to your own workflow.
+
+This page describes how you can use modules within the context of the Sentinel
+CLI. For instructions for a particular integration, see the documentation for
+that product.
+
+-> **Not all Sentinel-enabled applications support modules.** Please refer
+to the documentation of the specific Sentinel-enabled application. Some
+applications disable modules for security reasons.
+
+## Configuring a Module
+
+A module is configured within the Sentinel CLI by adding an entry for it in
+within the `imports` section of the [CLI configuration
+file](/sentinel/configuration). The key for each entry specifies the path that
+the module is imported as.
+
+```hcl
+import "module" "foo" {
+ source = "modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "modules/bar.sentinel"
+}
+```
+
+In this example, we have two modules:
+
+- Import path `foo`, being loaded from the code within `modules/foo.sentinel`.
+- Import path `bar`, being loaded from the code within `modules/bar.sentinel`.
+
+See the [Import Modules](/sentinel/configuration#import-modules) section within
+the CLI configuration file syntax page for more details.
+
+### Remote Modules
+
+In addition to loading from a local source, modules can be loaded from a remote
+location. This can increase reusability and allow for sharing modules across
+multiple configurations.
+
+### Testing Using Modules
+
+As with [mocks](/sentinel/configuration#mocking-with-sentinel-code), modules
+are loaded relative to the configuration file location when using `sentinel test`. As such, you need to account for this in your test configuration files:
+
+```hcl
+import "module" "foo" {
+ source = "../../modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "../../modules/bar.sentinel"
+}
+```
+
+This could be a test file for a policy (example: `policy.sentinel`), residing
+under the correct test file directory for this policy (example:
+`test/policy/pass.json`).
+
+## Writing and Using a Module
+
+Writing a module is nearly the same as writing a Sentinel policy, except for the
+following two exceptions:
+
+- Modules do not require [`main`](/sentinel/writing/basic#the-simplest-policy)
+ to be present. It can be, but it will not carry any extra significance as it
+ does within a policy.
+- [`param`](/sentinel/language/parameters) declarations cannot be present in a module. If they are encountered,
+ a runtime error is given.
+
+Once you have a complete module ready for use and configured, using the module
+is as simple as importing it.
+
+Building on the above [configuration example](#configuring-a-module), we can
+export a simple test function in the module `foo` by adding this code to
+`modules/foo.sentinel`:
+
+```sentinel
+// modules/foo.sentinel
+hello = func() {
+ print("hello world!")
+ return undefined
+}
+```
+
+We can then import it within our policy and use it:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+// prints "hello world" in the trace
+foo.hello()
+
+main = true
+```
+
+Note that modules are considered [imports](/sentinel/language/spec#imports) by
+the Sentinel runtime and as such conform to the specification. This means that
+you cannot directly assign values to anything behind a module scope. You can,
+however, use functions to change state.
+
+-> **NOTE:** At this time, modules do not support object receiver data in the
+way that some imports do, like [`time`](/sentinel/imports/time),
+[`decimal`](/sentinel/imports/decimal), and [`http`](/sentinel/imports/http).
+Support for this will be coming in later versions of Sentinel.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/extending/plugins.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/extending/plugins.mdx
new file mode 100644
index 0000000000..e80de5d4b1
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/extending/plugins.mdx
@@ -0,0 +1,521 @@
+---
+page_title: >-
+ Extending Sentinel: Plugins
+sidebar_current: docs-extending-plugins
+description: >-
+ Plugins allow you to write imports as standalone executables, allowing you to
+ extend Sentinel in ways not normally possible with policy code alone.
+layout: docs
+---
+
+# Extending Sentinel: Plugins
+
+-> **NOTE:** Plugins are currently only supported on the CLI and not in any
+production Sentinel integration, with the exception of existing configuration
+in [Nomad](https://www.nomadproject.io/docs/configuration/sentinel).
+
+Plugins allow you to write imports as standalone executables, allowing you to
+extend Sentinel in ways not normally possible with policy code alone.
+
+You might want to write or use a plugin when you need to:
+
+- **Use a provider or API integration for Sentinel.** In this case, you will
+ probably be working with a provider SDK, or other libraries that will be making
+ complex network requests to external services. Sentinel only provides limited
+ capabilities for these kinds of operations in the standard library, so writing
+ this kind of import as a module is not optimal.
+- **Introduce novel functionality not currently supported by Sentinel.**
+ Sentinel's policy language is limited by design to encourage simple policies,
+ reduce its runtime footprint, and to keep it efficient and safe. This will
+ mean that some functionality may be difficult or impossible to express as
+ Sentinel code, and would require a plugin to write.
+- **Extend a complex module.** Additionally, modules are restricted to a single
+ file of Sentinel code, so these will naturally become more and more unwieldy as
+ you continue to add to them. Eventually, there might be a time where a plugin is
+ better suited for the functionality, especially if it's a general-purpose
+ library.
+
+This section details how import plugins are currently configured in the Sentinel
+CLI, and some detail on how they are written. As we continue to build on the
+ability to use import plugins, we will extend this section with more details.
+
+-> **Not all Sentinel-enabled applications will support plugins.** Some
+applications will disable plugins for security reasons. For those that do enable
+plugins, the method to configure plugins will vary.
+
+## Installing Plugins
+
+Sentinel plugins are standalone executables. Sentinel-enabled applications such
+as the CLI launch these plugins and communicate with them via
+[RPC](https://en.wikipedia.org/wiki/Remote_procedure_call). To install a plugin,
+the first step is to download the plugin executable.
+
+After downloading the plugin, you must add it in the CLI configuration file,
+setting its name, path, and any arguments, environment variables, or
+configuration. An example is below:
+
+```hcl
+import "plugin" "custom_time" {
+ source = "/path/to/sentinel-import-time"
+ config = { "fixed_time": 1504155600 }
+}
+```
+
+-> **NOTE:** We are using "custom_time" as standard import paths cannot be
+overriden. See [Configuration File Syntax](/sentinel/configuration#imports) for
+more details.
+
+After configuring the plugin, it will be available on the next `sentinel apply`
+or `sentinel test`.
+
+For more details on configuration, see the
+[plugins](/sentinel/configuration#plugins) section of the [CLI configuration
+file syntax](/sentinel/configuration).
+
+## Debugging Plugins
+
+The Sentinel CLI logs when it launches, configures, and closes a plugin. The
+plugin may also log additional information when it is running.
+
+To debug issues with a plugin, you can usually refer to these logs. Depending on
+the plugin configuration, the logs you see may vary - refer to the plugin
+documentation on how to enable logs for a particular plugin.
+
+## Developing an Import Plugin
+
+Anyone can develop a Sentinel import plugin using the [Sentinel
+SDK](https://github.com/hashicorp/sentinel-sdk). The SDK contains a high-level
+framework for writing plugins in [Go](https://golang.org) including a test
+framework. Additionally, the SDK contains the low-level [gRPC protocol buffers
+definition](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/plugin.proto)
+for writing plugins in other languages.
+
+The rest of this page will document how to write a new Sentinel plugin in Go
+using the Sentinel SDK and the high-level framework provided. You are expected
+to already know the basics of Go and have a properly configured Go installation.
+
+Throughout this page, we'll be developing a simple plugin to access time values.
+
+-> **NOTE:** The example here is not reflective of the actual implementation of
+the [`time`](/sentinel/imports/time) standard import, so make sure to not
+get the two confused.
+
+### Plugin Framework
+
+The Sentinel SDK provides a high-level
+[framework](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework)
+for writing plugins. We recommend using this framework.
+
+#### Basic Concepts
+
+This framework works by implementing the correct Go interfaces.
+
+As a general overview:
+[`framework.Plugin`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Plugin)
+implements the
+[`sdk.Plugin`](https://godoc.org/github.com/hashicorp/sentinel-sdk#Plugin)
+interface and therefore is a valid import plugin. You must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface to configure the plugin. The root may then delegate nested access to
+various
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+implementations. Other interfaces are implemented to provide functionality past
+what is provided by the basic `framework.Namespace` implementation - these are
+documented below.
+
+This all may sound like a lot of interfaces, but each interface typically only
+requires a single function implementation and you're only required to implement
+a single namespace (`framework.Namespace`).
+
+As we move on, we'll use real examples to make this clear.
+
+#### Implementing framework.Root
+
+To begin, you must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface. This is the interface representing the root of your plugin. The root
+itself must be implement
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace),
+which allows values to be accessed. Note that the root can optionally implement
+other interfaces, but they're more advanced and omitted here for simplicity.
+
+For our custom time example, our root may look like this:
+
+```sentinel
+type root struct {
+ time time.Time
+}
+
+// framework.Root impl.
+func (m *root) Configure(raw map[string]interface{}) error {
+ if _, ok := raw["timestamp"]; !ok {
+ raw["timestamp"] = time.Now().Unix()
+ }
+
+ v := raw["timestamp"]
+ timestamp, ok := v.(int)
+ if !ok {
+ return fmt.Errorf("invalid timestamp type %T", v)
+ }
+
+ m.time = time.Unix(timestamp, 0).UTC()
+ return nil
+}
+
+// framework.Namespace impl.
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "minute":
+ return m.time.Minute(), nil
+ }
+
+ return nil, nil
+}
+```
+
+This example implements `framework.Root` and `framework.Namespace` and would
+enable the following within a Sentinel policy once the completed plugin is
+installed.
+
+```sentinel
+import "custom_time"
+
+print(custom_time.minute)
+main = true
+```
+
+#### framework.Namespace
+
+The
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+interface implements a namespace of values. You saw above that
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+must itself be a namespace. However, a namespace `Get` implementation may
+further return namespaces to access nested values.
+
+This enables behavior such as `custom_time.month.string` vs. `custom_time.month.index`.
+Notice each of these examples accesses a nested value within `month`. This can
+be modeled as namespaces within the framework.
+
+In the example below, we return a new namespace for `month` to do just this:
+
+```sentinel
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "month":
+ return &namespaceMonth{Month: m.time.Month()}, nil
+ }
+
+ // ...
+}
+
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil nil
+}
+```
+
+#### Primitive Types and Structs
+
+A namespace may also return any primitive Go type as well as structs. The
+framework automatically exposes primitive values as you would expect. For
+structs, exported fields are lowercased and exposed to the policies. The
+`sentinel` struct tag can be used to control how Sentinel can access the field.
+
+In the example below, we expose a struct directly from the root namespace:
+
+```sentinel
+type Location struct {
+ Name string
+ TimeZone string `sentinel:"time_zone"`
+}
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "location":
+ return &Location{Name: "somewhere", TimeZone: "some zone"}, nil
+ }
+
+ // ...
+}
+```
+
+This makes the following values work within a Sentinel policy:
+`time.location.name`, `time.location.time_zone`.
+
+If a field has an empty `sentinel` struct tag (example: `sentinel:""`),
+then that field will not be accessible from a policy.
+
+#### Optional Interfaces
+
+The following are optional interfaces that can be implemented on top of
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace).
+All of these interfaces can be implemented at any level, except for
+[`framework.New`](#framework-new), which only works at the root level.
+
+##### `framework.Call`
+
+The
+[`framework.Call`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Call)
+interface can be implemented to support function calls.
+
+Implementing this interface is very similar to attribute access, except instead
+of returning the attribute value, you return a function. The framework uses Go
+reflection to determine the argument and result types and calls it. If the
+argument types do not match or the signature is otherwise invalid, an error is
+returned.
+
+For example, let's implement a function to add months to the current month:
+
+```sentinel
+func (m *root) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *root) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.time.AddDate(0, n, 0).Month()}
+}
+```
+
+You can now call the function. If today was in the month of September,
+the policy below would pass:
+
+```sentinel
+import "custom_time"
+
+main = custom_time.add_month(4).string == "January"
+```
+
+##### `framework.Map`
+
+The optional
+[`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+interface allows an alternative method to to present a namespace back to a
+Sentinel policy as a map.
+
+`framework.Map` is best paired with
+[`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys),
+which allows you to quickly fetch the necessary data via `Get` calls on the
+namespace:
+
+```
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil, nil
+}
+
+func (m *namespaceMonth) Map() (map[string]interface{}, error) {
+ return framework.MapFromKeys(m, []string{"string", "index"})
+}
+```
+
+##### `framework.New`
+
+The optional
+[`framework.New`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#New)
+interface can be implemented on a root namespace to add _methods_ to your
+namespaces.
+
+Data returned from a namespace is memoized and returned as a map, and is
+normally not callable - to call a function to operate on the data, you would
+need to create a new namespace from the top-level of the plugin, and then make a
+function call on the result within the same expression.
+
+Let's consider the [root example](#implementing-framework-root). Let's say,
+instead of hard-coding the time value at configuration, we wanted to load it
+on-demand using a `time.now` key, and allow `add_month` to be callable on that
+value. Our root and de-coupled time namespace would now look like:
+
+```
+type root struct{}
+
+func (m *root) Configure(raw map[string]interface{}) error { return nil }
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "now":
+ return &namespaceTime{Time: time.Now()}
+ }
+
+ return nil
+}
+
+func (m *root) New(data map[string]interface{}) (framework.Namespace, error) {
+ if v, ok := data["unix"]; ok {
+ if t, ok := v.(int64); !ok {
+ return &namespaceTime{Time: time.Unix(t, 0)}
+ }
+
+ return nil, fmt.Errorf("expected timestamp to be int64, got %T", v)
+ }
+
+ return nil, nil
+}
+
+type namespaceTime struct {
+ Time time.Time
+}
+
+func (m *namespaceTime) Get(key string) interface{} {
+ switch key {
+ case "unix":
+ return m.Time.Unix()
+
+ // case ...
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) Get(key string) (interface{}, error) {
+ return framework.MapFromKeys(m, []string{"unix", "..."})
+}
+
+func (m *namespaceTime) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.Time.AddDate(0, n, 0).Month()}
+}
+```
+
+Via this example, we can now create and assign a `namespaceTime` using `t = custom_time.now`, and then call `t.add_month(months_to_add)` to return a
+`namespaceMonth` for the corresponding month.
+
+Methods on a namespace can also _modfiy_ the receiver data. So you can, for
+example, add a method named `increment_month` that takes no arguments, but
+increments the time stored in `namespaceTime.Time` by a month. Subsequent
+statements using the receiver would see the new value.
+
+Note that there are some restrictions and guidelines that you should take into
+account when using `framework.New`:
+
+- Only data that makes it back to a policy can be used as receiver data to
+ instantiate new namespaces. As such it's recommended to make use of
+ [`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+ and
+ [`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys)
+ to return all of the attribute data you need to construct new objects.
+- `framework.New` is only supported on the root namespace and has no effect on
+ namespaces below the root. Check all possible cases of receiver data within
+ the top-level `New` function and return the appropriate namespace based on the
+ input data.
+- Do not return general errors from your `New` method. When an unknown lookup is
+ made off of memoized data, it will hit your `New` method for possible
+ instantiation and key calls. This will ensure `undefined` is correctly
+ returned for these cases.
+- `framework.New` is designed to add utility to plugins where calling methods on
+ a value is more intuitive than just making a function call, or just returning
+ all of a data set as a memoized map. Use it sparingly and avoid using it on
+ recursively complex data sets.
+
+### Testing
+
+The SDK exposes a testing framework to verify your plugin works as
+expected. This test framework integrates directly into `go test` so it
+is part of a familiar workflow.
+
+The test framework works by dynamically building your plugin and
+running it against the `sentinel` CLI to verify it behaves as expected.
+This ensures that your plugin builds, your plugin communicates via RPC
+correctly, and that the behavior within a policy is also correct.
+
+The example below shows a complete ready table-driven test for our time plugin.
+You can run this with a normal `go test`.
+
+```sentinel
+package main
+
+import (
+ "os"
+ "testing"
+
+ "github.com/hashicorp/sentinel-sdk"
+ plugintesting "github.com/hashicorp/sentinel-sdk/testing"
+)
+
+func TestMain(m *testing.M) {
+ exitCode := m.Run()
+ plugintesting.Clean()
+ os.Exit(exitCode)
+}
+
+func TestPlugin(t *testing.T) {
+ cases := []struct {
+ Name string
+ Data map[string]interface{}
+ Source string
+ }{
+ {
+ "month",
+ map[string]interface{}{"timestamp": 1495483674},
+ `main = subject.month.string == "September"`,
+ },
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.Name, func(t *testing.T) {
+ plugintesting.TestPlugin(t, plugintesting.TestPluginCase{
+ Config: tc.Data,
+ Source: tc.Source,
+ })
+ })
+ }
+}
+```
+
+### Building Your Plugin
+
+To build your plugin, you must first implement the `main` function.
+The main function should just use the `rpc.Serve` method to serve the
+plugin over the Sentinel RPC layer:
+
+```sentinel
+package main
+
+import (
+ "github.com/hashicorp/sentinel-sdk"
+ "github.com/hashicorp/sentinel-sdk/rpc"
+)
+
+func main() {
+ rpc.Serve(&rpc.ServeOpts{
+ PluginFunc: func() sdk.Plugin {
+ return &framework.Plugin{Root: &root{}}
+ },
+ })
+}
+```
+
+You can then build this using `go build`. The output can be named
+anything. Once your plugin is built, [install it](#installing-plugins)
+and try it out!
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/extending/static-imports.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/extending/static-imports.mdx
new file mode 100644
index 0000000000..cb51beb5d8
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/extending/static-imports.mdx
@@ -0,0 +1,48 @@
+---
+page_title: >-
+ Extending Sentinel: Static Imports
+sidebar_current: docs-extending-static-imports
+description: >-
+ Static imports allow you to write imports that use static data files.
+layout: docs
+---
+
+# Extending Sentinel: Static Imports
+
+Static imports allow you to write imports that use static data files. This can
+provide methods of consuming data in ways that are not available via
+[modules](./modules) or [plugins](./plugins).
+
+### Configuring a Static Import
+
+The [configuration page](/sentinel/configuration#static-imports) details how to
+configure static imports.
+
+### Usage
+
+Using a static import is not that different to using a plugin or module. The
+import must first be introduced to the policy via an import statement.
+
+```sentinel
+import "people"
+```
+
+From here, the static data can be used throughout the policy as you would any
+normal Sentinel value.
+
+```sentinel
+admins = filter people as person {
+ person.role is "Admin"
+}
+```
+
+One differentiator for static imports is that you can directly reference the
+import without the use of selectors. For example, to print the value;
+
+```sentinel
+print(people)
+```
+
+This is due to the static data being directly loaded and converted into a
+Sentinel value, ready for use. Static imports, like modules and plugins, cannot
+be assigned a new value.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/features/index.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/features/index.mdx
new file mode 100644
index 0000000000..e035be978c
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/features/index.mdx
@@ -0,0 +1,28 @@
+---
+page_title: Features
+sidebar_current: docs-features
+description: Learn how features can enhance the Sentinel runtime by enabling further capabilities.
+layout: docs
+---
+
+# Features
+
+Features are a set of capabilities that enhance the runtime experience. They
+are enabled through the [`sentinel`](/sentinel/configuration#sentinel) block of the
+configuration, using the features attribute. An example of how to enable features
+is as follows:
+
+```hcl
+sentinel {
+ features = {
+ apply-all = true
+ terraform = true
+ }
+}
+```
+
+The current set of features are:
+
+- [terraform](/sentinel/features/terraform) - Allowing the Sentinel runtime
+ to interact with Terraform data.
+- apply-all - Force Sentinel to evaluate all policies within a policy set without prematurely terminating on failures.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/features/terraform/index.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/features/terraform/index.mdx
new file mode 100644
index 0000000000..a810dbe478
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/features/terraform/index.mdx
@@ -0,0 +1,24 @@
+---
+page_title: terraform
+sidebar_current: docs-features-terraform
+description: Learn about the terraform feature and its capabilities.
+layout: docs
+---
+
+# terraform feature
+
+The `terraform` feature enhances the Sentinel runtime to work with Terraform
+data. It will add the following imports to the standard library:
+
+- [tfplan/v1](/sentinel/features/terraform/tfplan-v1)
+- [tfplan/v2](/sentinel/features/terraform/tfplan-v2)
+- [tfconfig/v1](/sentinel/features/terraform/tfconfig-v1)
+- [tfconfig/v2](/sentinel/features/terraform/tfconfig-v2)
+- [tfstate/v1](/sentinel/features/terraform/tfstate-v1)
+- [tfstate/v2](/sentinel/features/terraform/tfstate-v2)
+
+-> **NOTE:** The above imports will only work with Terraform 0.12 and above,
+as they rely on the output from the `terraform show -json` command.
+
+It is recommended that the `/v2` suffixed imports are used, as they provide
+the best experience when interacting with the underlying data structures.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/features/terraform/tfconfig-v1.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/features/terraform/tfconfig-v1.mdx
new file mode 100644
index 0000000000..95fb5840ab
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/features/terraform/tfconfig-v1.mdx
@@ -0,0 +1,949 @@
+---
+page_title: tfconfig/v1 - terraform - Features
+sidebar_current: docs-features-terraform-config-v1
+description: The tfconfig/v1 import provides access to a Terraform configuration.
+layout: docs
+---
+
+# Import: tfconfig/v1
+
+~> **Warning:** The `tfconfig/v1` import is deprecated and will be permanently removed in August 2025.
+Use the [tfconfig/v2](/sentinel/docs/features/terraform/tfconfig-v2) import as soon as possible to avoid disruptions.
+The `tfconfig/v2` import offers improved functionality and is designed to better support your policy enforcement needs.
+
+The `tfconfig/v1` import provides access to a Terraform configuration.
+
+The Terraform configuration is the set of `*.tf` files that are used to
+describe the desired infrastructure state. Policies using the `tfconfig/v1`
+import can access all aspects of the configuration: providers, resources,
+data sources, modules, and variables.
+
+Some use cases for `tfconfig/v1` include:
+
+* **Organizational naming conventions**: requiring that configuration elements
+ are named in a way that conforms to some organization-wide standard.
+* **Required inputs and outputs**: organizations may require a particular set
+ of input variable names across all workspaces or may require a particular
+ set of outputs for asset management purposes.
+* **Enforcing particular modules**: organizations may provide a number of
+ "building block" modules and require that each workspace be built only from
+ combinations of these modules.
+* **Enforcing particular providers or resources**: an organization may wish to
+ require or prevent the use of providers and/or resources so that configuration
+ authors cannot use alternative approaches to work around policy
+ restrictions.
+
+Note with these use cases that this import is concerned with object _names_
+in the configuration. Since this is the configuration and not an invocation
+of Terraform, you can't see values for variables, the state, or the diff for
+a pending plan. If you want to write policy around expressions used
+within configuration blocks, you likely want to use the
+[`tfplan/v1`](/sentinel/features/terraform/tfplan-v1) import.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfconfig/v1" {
+ config = {
+ "plan": "./path/to/plan.json"
+ }
+}
+```
+
+## Namespace Overview
+
+The following is a tree view of the import namespace. For more detail on a
+particular part of the namespace, see below.
+
+-> **Note:** The root-level alias keys shown here (`data`, `modules`,
+`providers`, `resources`, and `variables`) are shortcuts to a [module
+namespace](#namespace-module) scoped to the root module. For more details, see
+the section on [root namespace aliases](#root-namespace-aliases).
+
+```
+tfconfig/v1
+├── module() (function)
+│ └── (module namespace)
+│ ├── data
+│ │ └── TYPE.NAME
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── provisioners
+│ │ └── NUMBER
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── type (string)
+│ ├── modules
+│ │ └── NAME
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ ├── source (string)
+│ │ └── version (string)
+│ ├──outputs
+│ │ └── NAME
+│ │ ├── depends_on (list of strings)
+│ │ ├── description (string)
+│ │ ├── sensitive (boolean)
+│ │ ├── references (list of strings)
+│ │ └── value (value)
+│ ├── providers
+│ │ └── TYPE
+│ │ ├── alias
+│ │ │ └── ALIAS
+│ │ │ ├── config (map of keys)
+│ │ | ├── references (map of keys)
+│ │ │ └── version (string)
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── version (string)
+│ ├── resources
+│ │ └── TYPE.NAME
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── provisioners
+│ │ └── NUMBER
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── type (string)
+│ └── variables
+│ └── NAME
+│ ├── default (value)
+│ └── description (string)
+├── module_paths ([][]string)
+│
+├── data (root module alias)
+├── modules (root module alias)
+├── outputs (root module alias)
+├── providers (root module alias)
+├── resources (root module alias)
+└── variables (root module alias)
+```
+
+### `references` Overview
+
+As an example, consider the following resource block:
+
+```hcl
+resource "local_file" "accounts" {
+ content = "some text"
+ filename = "${var.subdomain}.${var.domain}/accounts.txt"
+}
+```
+
+In this example, one might want to ensure `domain` and `subdomain` input
+variables are used within `filename` in this configuration.
+
+-> Any non-static values (such as interpolated strings) are not present within the
+configuration value and `references` should be used instead:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+# filename_references is a list of string values containing the references used in the expression
+filename_references = tfconfig.resources.local_file.accounts.references.filename
+
+main = rule {
+ filename_references contains "var.domain" and
+ filename_references contains "var.subdomain"
+}
+```
+
+The `references` value is present in any namespace where non-constant
+configuration values can be expressed. This is essentially every namespace
+which has a `config` value as well as the `outputs` namespace.
+
+-> **Note:** Remember, this import enforces policy around the literal Terraform
+configuration and not the final values as a result of invoking Terraform. If
+you want to write policy around the _result_ of expressions used within
+configuration blocks (for example, if you wanted to ensure the final value of
+`filename` above includes `accounts.txt`), you likely want to use the
+[`tfplan/v1`](/sentinel/features/terraform/tfplan-v1) import.
+
+## Namespace: Root
+
+The root-level namespace consists of the values and functions documented below.
+
+In addition to this, the root-level `data`, `modules`, `providers`, `resources`,
+and `variables` keys all alias to their corresponding namespaces within the
+[module namespace](#namespace-module).
+
+
+
+### Function: `module()`
+
+```
+module = func(ADDR)
+```
+
+* **Return Type:** A [module namespace](#namespace-module).
+
+The `module()` function in the [root namespace](#namespace-root) returns the
+[module namespace](#namespace-module) for a particular module address.
+
+The address must be a list and is the module address, split on the period (`.`),
+excluding the root module.
+
+Hence, a module with an address of simply `foo` (or `root.foo`) would be
+`["foo"]`, and a module within that (so address `foo.bar`) would be read as
+`["foo", "bar"]`.
+
+[`null`][ref-null] is returned if a module address is invalid, or if the module
+is not present in the configuration.
+
+[ref-null]: /sentinel/language/spec#null
+
+As an example, given the following module block:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+If the module contained the following content:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { subject.module(["foo"]).resources.null_resource.foo.config.triggers[0].foo is "bar" }
+```
+
+
+
+### Value: `module_paths`
+
+* **Value Type:** List of a list of strings.
+
+The `module_paths` value within the [root namespace](#namespace-root) is a list
+of all of the modules within the Terraform configuration.
+
+Modules not present in the configuration will not be present here, even if they
+are present in the diff or state.
+
+This data is represented as a list of a list of strings, with the inner list
+being the module address, split on the period (`.`).
+
+The root module is included in this list, represented as an empty inner list.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The value of `module_paths` would be:
+
+```
+[
+ [],
+ ["foo"],
+]
+```
+
+And the following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.module_paths contains ["foo"] }
+```
+
+#### Iterating Through Modules
+
+Iterating through all modules to find particular resources can be useful. This
+[example][iterate-over-modules] shows how to use `module_paths` with the
+[`module()` function](#function-module-) to find all resources of a
+particular type from all modules using the `tfplan` import. By changing `tfplan`
+in this function to `tfconfig`, you could make a similar function find all
+resources of a specific type in the Terraform configuration.
+
+[iterate-over-modules]: https://developer.hashicorp.com/terraform/cloud-docs/policy-enforcement/sentinel#sentinel-imports
+
+## Namespace: Module
+
+The **module namespace** can be loaded by calling [`module()`](#root-function-module)
+for a particular module.
+
+It can be used to load the following child namespaces:
+
+* `data` - Loads the [resource namespace](#namespace-resources-data-sources),
+ filtered against data sources.
+* `modules` - Loads the [module configuration
+ namespace](#namespace-module-configuration).
+* `outputs` - Loads the [output namespace](#namespace-outputs).
+* `providers` - Loads the [provider namespace](#namespace-providers).
+* `resources` - Loads the [resource
+ namespace](#namespace-resources-data-sources), filtered against resources.
+* `variables` - Loads the [variable namespace](#namespace-variables).
+
+### Root Namespace Aliases
+
+The root-level `data`, `modules`, `providers`, `resources`, and `variables` keys
+all alias to their corresponding namespaces within the module namespace, loaded
+for the root module. They are the equivalent of running `module([]).KEY`.
+
+
+
+## Namespace: Resources/Data Sources
+
+The **resource namespace** is a namespace _type_ that applies to both resources
+(accessed by using the `resources` namespace key) and data sources (accessed
+using the `data` namespace key).
+
+Accessing an individual resource or data source within each respective namespace
+can be accomplished by specifying the type and name, in the syntax
+`[resources|data].TYPE.NAME`.
+
+In addition, each of these namespace levels is a map, allowing you to filter
+based on type and name. Some examples of multi-level access are below:
+
+* To fetch all `aws_instance` resources within the root module, you can specify
+ `tfconfig.resources.aws_instance`. This would give you a map of resource
+ namespaces indexed from the names of each resource (`foo`, `bar`, and so
+ on).
+* To fetch all resources within the root module, irrespective of type, use
+ `tfconfig.resources`. This is indexed by type, as shown above with
+ `tfconfig.resources.aws_instance`, with names being the next level down.
+
+As an example, perhaps you wish to deny use of the `local_file` resource
+in your configuration. Consider the following resource block:
+
+```hcl
+resource "local_file" "foo" {
+ content = "foo!"
+ filename = "${path.module}/foo.bar"
+}
+```
+
+The following policy would fail:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.resources not contains "local_file" }
+```
+
+Further explanation of the namespace will be in the context of resources. As
+mentioned, when operating on data sources, use the same syntax, except with
+`data` in place of `resources`.
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+The `config` value within the [resource
+namespace](#namespace-resources-data-sources) is a map of key-value pairs that
+directly map to Terraform config keys and values.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#resources-value-references) should be used instead.
+
+As an example, consider the following resource block:
+
+```hcl
+resource "local_file" "accounts" {
+ content = "some text"
+ filename = "accounts.txt"
+}
+```
+
+In this example, one might want to access `filename` to validate that the correct
+file name is used. Given the above example, the following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule {
+ tfconfig.resources.local_file.accounts.config.filename is "accounts.txt"
+}
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [resource namespace](#namespace-resources-data-sources)
+contains the identifiers within non-constant expressions found in [`config`](#resources-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+
+
+### Value: `provisioners`
+
+* **Value Type:** List of [provisioner namespaces](#namespace-provisioners).
+
+The `provisioners` value within the [resource namespace](#namespace-resources)
+represents the [provisioners][ref-tf-provisioners] within a specific resource.
+
+Provisioners are listed in the order they were provided in the configuration
+file.
+
+While the `provisioners` value will be present within data sources, it will
+always be an empty map `null` (in Terraform 0.12) since data sources cannot
+actually have provisioners.
+
+The data within a provisioner can be inspected via the returned [provisioner
+namespace](#namespace-provisioners).
+
+[ref-tf-provisioners]: https://developer.hashicorp.com/terraform/language/resources/provisioners/syntax
+
+## Namespace: Provisioners
+
+The **provisioner namespace** represents the configuration for a particular
+[provisioner][ref-tf-provisioners] within a specific resource.
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+The `config` value within the [provisioner namespace](#namespace-provisioners)
+represents the values of the keys within the provisioner.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#provisioners-value-references) should be used instead.
+
+As an example, given the following resource block:
+
+```hcl
+resource "null_resource" "foo" {
+ # ...
+
+ provisioner "local-exec" {
+ command = "echo ${self.private_ip} > file.txt"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule {
+ tfconfig.resources.null_resource.foo.provisioners[0].config.command is "echo ${self.private_ip} > file.txt"
+}
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [provisioner namespace](#namespace-provisioners)
+contains the identifiers within non-constant expressions found in [`config`](#provisioners-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+
+
+### Value: `type`
+
+* **Value Type:** String.
+
+The `type` value within the [provisioner namespace](#namespace-provisioners)
+represents the type of the specific provisioner.
+
+As an example, in the following resource block:
+
+```hcl
+resource "null_resource" "foo" {
+ # ...
+
+ provisioner "local-exec" {
+ command = "echo ${self.private_ip} > file.txt"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.resources.null_resource.foo.provisioners[0].type is "local-exec" }
+```
+
+## Namespace: Module Configuration
+
+The **module configuration** namespace displays data on _module configuration_
+as it is given within a `module` block. This means that the namespace concerns
+itself with the contents of the declaration block (example: the `source`
+parameter and variable assignment keys), not the data within the module
+(example: any contained resources or data sources). For the latter, the module
+instance would need to be looked up with the [`module()`
+function](#root-function-module).
+
+
+
+### Value: `source`
+
+* **Value Type:** String.
+
+The `source` value within the [module configuration
+namespace](#namespace-module-configuration) represents the module source path as
+supplied to the module configuration.
+
+As an example, given the module declaration block:
+
+```hcl
+module "foo" {
+ source = "./foo"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.modules.foo.source is "./foo" }
+```
+
+
+
+### Value: `version`
+
+* **Value Type:** String.
+
+The `version` value within the [module configuration
+namespace](#namespace-module-configuration) represents the [version
+constraint][module-version-constraint] for modules that support it, such as
+modules within the [Terraform Module Registry][terraform-module-registry] or the
+[HCP Terraform private module registry][tfe-private-registry].
+
+[module-version-constraint]: https://developer.hashicorp.com/terraform/language/modules#module-versions
+
+[terraform-module-registry]: https://registry.terraform.io/
+
+[tfe-private-registry]: https://developer.hashicorp.com/terraform/cloud-docs/registry
+
+As an example, given the module declaration block:
+
+```hcl
+module "foo" {
+ source = "foo/bar"
+ version = "~> 1.2"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.modules.foo.version is "~> 1.2" }
+```
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#modules-value-references) should be used instead.
+
+The `config` value within the [module configuration
+namespace](#namespace-module-configuration) represents the values of the keys
+within the module configuration. This is every key within a module declaration
+block except [`source`](#modules-value-source) and [`version`](#modules-value-version), which
+have their own values.
+
+As an example, given the module declaration block:
+
+```hcl
+module "foo" {
+ source = "./foo"
+
+ bar = "baz"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.modules.foo.config.bar is "baz" }
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [module configuration namespace](#namespace-module-configuration)
+contains the identifiers within non-constant expressions found in [`config`](#modules-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+## Namespace: Outputs
+
+The **output namespace** represents _declared_ output data within a
+configuration. As such, configuration for the [`value`](#outputs-value-value) attribute
+will be in its raw form, and not yet interpolated. For fully interpolated output
+values, see the [`tfstate` import][ref-tfe-sentinel-tfstate].
+
+[ref-tfe-sentinel-tfstate]: /sentinel/features/terraform/tfstate-v1
+
+This namespace is indexed by output name.
+
+
+
+### Value: `depends_on`
+
+* **Value Type:** A list of strings.
+
+The `depends_on` value within the [output namespace](#namespace-outputs)
+represents any _explicit_ dependencies for this output. For more information,
+see the [depends_on output setting][ref-depends_on] within the general Terraform
+documentation.
+
+[ref-depends_on]: https://developer.hashicorp.com/terraform/language/values/outputs#depends_on
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ depends_on = ["null_resource.bar"]
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.depends_on[0] is "null_resource.bar" }
+```
+
+
+
+### Value: `description`
+
+* **Value Type:** String.
+
+The `description` value within the [output namespace](#namespace-outputs)
+represents the defined description for this output.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ description = "foobar"
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.description is "foobar" }
+```
+
+
+
+### Value: `sensitive`
+
+* **Value Type:** Boolean.
+
+The `sensitive` value within the [output namespace](#namespace-outputs)
+represents if this value has been marked as sensitive or not.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ sensitive = true
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { subject.outputs.id.sensitive }
+```
+
+
+
+### Value: `value`
+
+* **Value Type:** Any primitive type, list or map.
+
+The `value` value within the [output namespace](#namespace-outputs) represents
+the defined value for the output as declared in the configuration. Primitives
+will bear the implicit type of their declaration (string, int, float, or bool),
+and maps and lists will be represented as such.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#outputs-value-references) should be used instead.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ value = "foo"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.value is "foo" }
+```
+
+
+
+### Value: `references`
+
+* **Value Type:**. List of strings.
+
+The `references` value within the [output namespace](#namespace-outputs)
+contains the names of any referenced identifiers when [`value`](#outputs-value-value)
+is a non-constant expression.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.references contains "null_resource.foo.id" }
+```
+
+## Namespace: Providers
+
+The **provider namespace** represents data on the declared providers within a
+namespace.
+
+This namespace is indexed by provider type and _only_ contains data about
+providers when actually declared. If you are using a completely implicit
+provider configuration, this namespace will be empty.
+
+This namespace is populated based on the following criteria:
+
+* The top-level namespace [`config`](#providers-value-config) and
+ [`version`](#providers-value-version) values are populated with the configuration and
+ version information from the default provider (the provider declaration that
+ lacks an alias).
+* Any aliased providers are added as namespaces within the
+ [`alias`](#providers-value-alias) value.
+* If a module lacks a default provider configuration, the top-level `config` and
+ `version` values will be empty.
+
+
+
+### Value: `alias`
+
+* **Value Type:** A map of [provider namespaces](#namespace-providers), indexed
+ by alias.
+
+The `alias` value within the [provider namespace](#namespace-providers)
+represents all declared [non-default provider
+instances][ref-tf-provider-instances] for a specific provider type, indexed by
+their specific alias.
+
+[ref-tf-provider-instances]: https://developer.hashicorp.com/terraform/language/providers/configuration#alias-multiple-provider-configurations
+
+The return type is a provider namespace with the data for the instance in
+question loaded. The `alias` key will not be available within this namespace.
+
+As an example, given the following provider declaration block:
+
+```hcl
+provider "aws" {
+ alias = "east"
+ region = "us-east-1"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.providers.aws.alias.east.config.region is "us-east-1" }
+```
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#providers-value-references) should be used instead.
+
+The `config` value within the [provider namespace](#namespace-providers)
+represents the values of the keys within the provider's configuration, with the
+exception of the provider version, which is represented by the
+[`version`](#providers-value-version) value. [`alias`](#providers-value-alias) is also not included
+when the provider is aliased.
+
+As an example, given the following provider declaration block:
+
+```hcl
+provider "aws" {
+ region = "us-east-1"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.providers.aws.config.region is "us-east-1" }
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [provider namespace](#namespace-providers)
+contains the identifiers within non-constant expressions found in [`config`](#providers-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+
+
+### Value: `version`
+
+* **Value Type:** String.
+
+The `version` value within the [provider namespace](#namespace-providers)
+represents the explicit expected version of the supplied provider. This includes
+the pessimistic operator.
+
+As an example, given the following provider declaration block:
+
+```hcl
+provider "aws" {
+ version = "~> 1.34"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.providers.aws.version is "~> 1.34" }
+```
+
+## Namespace: Variables
+
+The **variable namespace** represents _declared_ variable data within a
+configuration. As such, static data can be extracted, such as defaults, but not
+dynamic data, such as the current value of a variable within a plan (although
+this can be extracted within the [`tfplan` import][ref-tfe-sentinel-tfplan]).
+
+[ref-tfe-sentinel-tfplan]: /sentinel/features/terraform/tfplan-v1
+
+This namespace is indexed by variable name.
+
+
+
+### Value: `default`
+
+* **Value Type:** Any primitive type, list, map, or `null`.
+
+The `default` value within the [variable namespace](#namespace-variables)
+represents the default for the variable as declared in the configuration.
+
+The actual value will be as configured. Primitives will bear the implicit type
+of their declaration (string, int, float, or bool), and maps and lists will be
+represented as such.
+
+If no default is present, the value will be [`null`][ref-sentinel-null] (not to
+be confused with [`undefined`][ref-sentinel-undefined]).
+
+[ref-sentinel-null]: /sentinel/language/spec#null
+
+[ref-sentinel-undefined]: /sentinel/language/undefined
+
+As an example, given the following variable blocks:
+
+```hcl
+variable "foo" {
+ default = "bar"
+}
+
+variable "number" {
+ default = 42
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+default_foo = rule { tfconfig.variables.foo.default is "bar" }
+default_number = rule { tfconfig.variables.number.default is 42 }
+
+main = rule { default_foo and default_number }
+```
+
+
+
+### Value: `description`
+
+* **Value Type:** String.
+
+The `description` value within the [variable namespace](#namespace-variables)
+represents the description of the variable, as provided in configuration.
+
+As an example, given the following variable block:
+
+```hcl
+variable "foo" {
+ description = "foobar"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.variables.foo.description is "foobar" }
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/features/terraform/tfconfig-v2.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/features/terraform/tfconfig-v2.mdx
new file mode 100644
index 0000000000..1fe4fff092
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/features/terraform/tfconfig-v2.mdx
@@ -0,0 +1,441 @@
+---
+page_title: tfconfig/v2 - terraform - Features
+sidebar_current: docs-features-terraform-tfconfig-v2
+description: The tfconfig/v2 import provides access to a Terraform configuration.
+layout: docs
+---
+
+# Import: tfconfig/v2
+
+The `tfconfig/v2` import provides access to a Terraform configuration.
+
+The Terraform configuration is the set of `*.tf` files that are used to
+describe the desired infrastructure state. Policies using the `tfconfig`
+import can access all aspects of the configuration: providers, resources,
+data sources, modules, and variables.
+
+Some use cases for `tfconfig` include:
+
+* **Organizational naming conventions**: requiring that configuration elements
+ are named in a way that conforms to some organization-wide standard.
+* **Required inputs and outputs**: organizations may require a particular set
+ of input variable names across all workspaces or may require a particular
+ set of outputs for asset management purposes.
+* **Enforcing particular modules**: organizations may provide a number of
+ "building block" modules and require that each workspace be built only from
+ combinations of these modules.
+* **Enforcing particular providers or resources**: an organization may wish to
+ require or prevent the use of providers and/or resources so that configuration
+ authors cannot use alternative approaches to work around policy
+ restrictions.
+
+The data in the `tfconfig/v2` import is sourced from the JSON configuration file
+that is generated by the [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) command. For more information on
+the file format, see the [JSON Output Format](https://developer.hashicorp.com/terraform/internals/json-format)
+page.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfconfig/v2" {
+ config = {
+ "plan": "./path/to/plan.json"
+ }
+}
+```
+
+## Import Overview
+
+The `tfconfig/v2` import is structured as a series of _collections_, keyed as a
+specific format, such as resource address, module address, or a
+specifically-formatted provider key.
+
+```
+tfconfig/v2
+├── strip_index() (function)
+├── providers
+│ └── (indexed by [module_address:]provider[.alias])
+│ ├── provider_config_key (string)
+│ ├── name (string)
+│ ├── full_name (string)
+│ ├── alias (string)
+│ ├── module_address (string)
+│ ├── config (block expression representation)
+│ └── version_constraint (string)
+├── resources
+│ └── (indexed by address)
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── provider_config_key (string)
+│ ├── provisioners (list)
+│ │ └── (ordered provisioners for this resource only)
+│ ├── config (block expression representation)
+│ ├── count (expression representation)
+│ ├── for_each (expression representation)
+│ └── depends_on (list of strings)
+├── provisioners
+│ └── (indexed by resource_address:index)
+│ ├── resource_address (string)
+│ ├── type (string)
+│ ├── index (string)
+│ └── config (block expression representation)
+├── variables
+│ └── (indexed by module_address:name)
+│ ├── module_address (string)
+│ ├── name (string)
+│ ├── default (value)
+│ └── description (string)
+├── outputs
+│ └── (indexed by module_address:name)
+│ ├── module_address (string)
+│ ├── name (string)
+│ ├── sensitive (boolean)
+│ ├── value (expression representation)
+│ ├── description (string)
+│ └── depends_on (list of strings)
+└── module_calls
+ └── (indexed by module_address:name)
+ ├── module_address (string)
+ ├── name (string)
+ ├── source (string)
+ ├── config (block expression representation)
+ ├── count (expression representation)
+ ├── depends_on (expression representation)
+ ├── for_each (expression representation)
+ └── version_constraint (string)
+```
+
+The collections are:
+
+* [`providers`](#the-providers-collection) - The configuration for all provider
+ instances across all modules in the configuration.
+* [`resources`](#the-resources-collection) - The configuration of all resources
+ across all modules in the configuration.
+* [`variables`](#the-variables-collection) - The configuration of all variable
+ definitions across all modules in the configuration.
+* [`outputs`](#the-outputs-collection) - The configuration of all output
+ definitions across all modules in the configuration.
+* [`module_calls`](#the-module_calls-collection) - The configuration of all module
+ calls (individual [`module`](/terraform/language/modules) blocks) across
+ all modules in the configuration.
+
+These collections are specifically designed to be used with the
+[`filter`](/sentinel/language/collection-operations#filter-expression)
+quantifier expression in Sentinel, so that one can collect a list of resources
+to perform policy checks on without having to write complex module or
+configuration traversal. As an example, the following code will return all
+`aws_instance` resource types within the configuration, regardless of what
+module they are in:
+
+```
+all_aws_instances = filter tfconfig.resources as _, r {
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+You can add specific attributes to the filter to narrow the search, such as the
+module address. The following code would return resources in a module named
+`foo` only:
+
+```
+all_aws_instances = filter tfconfig.resources as _, r {
+ r.module_address is "module.foo" and
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+### Address Differences Between `tfconfig`, `tfplan`, and `tfstate`
+
+This import deals with configuration before it is expanded into a
+resource graph by Terraform. As such, it is not possible to compute an index as
+the import is building its collections and computing addresses for resources and
+modules.
+
+As such, addresses found here may not always match the expanded addresses found
+in the [`tfplan/v2`](/sentinel/features/terraform/tfplan-v2) and [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2)
+imports, specifically when
+[`count`](https://developer.hashicorp.com/terraform/language/resources#count-multiple-resource-instances-by-count)
+and
+[`for_each`](https://developer.hashicorp.com/terraform/language/resources#for_each-multiple-resource-instances-defined-by-a-map-or-set-of-strings),
+are used.
+
+As an example, consider a resource named `null_resource.foo` with a count of `2`
+located in a module named `bar`. While there will possibly be entries in the
+other imports for `module.bar.null_resource.foo[0]` and
+`module.bar.null_resource.foo[1]`, in `tfconfig/v2`, there will only be a
+`module.bar.null_resource.foo`. As mentioned in the start of this section, this
+is because configuration actually _defines_ this scaling, whereas _expansion_
+actually happens when the resource graph is built, which happens as a natural
+part of the refresh and planning process.
+
+The `strip_index` helper function, found in this import, can assist in
+removing the indexes from addresses found in the `tfplan/v2` and `tfstate/v2`
+imports so that data from those imports can be used to reference data in this
+one.
+
+## The `strip_index` Function
+
+The `strip_index` helper function can be used to remove indexes from addresses
+found in [`tfplan/v2`](/sentinel/features/terraform/tfplan-v2) and [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2),
+by removing the indexes from each resource.
+
+This can be used to help facilitate cross-import lookups for data between plan,
+state, and config.
+
+```
+import "tfconfig/v2" as tfconfig
+import "tfplan/v2" as tfplan
+
+main = rule {
+ all filter tfplan.resource_changes as _, rc {
+ rc.mode is "managed" and
+ rc.type is "aws_instance"
+ } as _, rc {
+ tfconfig.resources[tfconfig.strip_index(rc.address)].config.ami.constant_value is "ami-abcdefgh012345"
+ }
+}
+```
+
+## Expression Representations
+
+Most collections in this import will have one of two kinds of _expression
+representations_. This is a verbose format for expressing a (parsed)
+configuration value independent of the configuration source code, which is not
+100% available to a policy check in HCP Terraform.
+
+```
+(expression representation)
+├── constant_value (value)
+└── references (list of strings)
+```
+
+There are two major parts to an expression representation:
+
+* Any _strictly constant value_ is expressed as an expression with a
+ `constant_value` field.
+* Any expression that requires some degree of evaluation to generate the final
+ value - even if that value is known at plan time - is not expressed in
+ configuration. Instead, any particular references that are made are added to
+ the `references` field. More details on this field can be found in the
+ [expression
+ representation](https://developer.hashicorp.com/terraform/internals/json-format#expression-representation)
+ section of the JSON output format documentation.
+
+For example, to determine if an output is based on a particular
+resource value, one could do:
+
+```
+import "tfconfig/v2" as tfconfig
+
+main = rule {
+ tfconfig.outputs["instance_id"].value.references is ["aws_instance.foo"]
+}
+```
+
+-> **Note:** The representation does not account for
+complex interpolations or other expressions that combine constants with other
+expression data. For example, the partially constant data in `"foo${var.bar}"` would be lost.
+
+### Block Expression Representation
+
+Expanding on the above, a multi-value expression representation (such as the
+kind found in a [`resources`](#the-resources-collection) collection element) is
+similar, but the root value is a keyed map of expression representations. This
+is repeated until a "scalar" expression value is encountered, ie: a field that
+is not a block in the resource's schema.
+
+```
+(block expression representation)
+└── (attribute key)
+ ├── (child block expression representation)
+ │ └── (...)
+ ├── constant_value (value)
+ └── references (list of strings)
+```
+
+As an example, one can validate expressions in an
+[`aws_instance`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance) resource using the
+following:
+
+```
+import "tfconfig/v2" as tfconfig
+
+main = rule {
+ tfconfig.resources["aws_instance.foo"].config.ami.constant_value is "ami-abcdefgh012345"
+}
+```
+
+Note that _nested blocks_, sometimes known as _sub-resources_, will be nested in
+configuration as as list of blocks (reflecting their ultimate nature as a list
+of objects). An example would be the `aws_instance` resource's
+[`ebs_block_device`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#ebs-ephemeral-and-root-block-devices) block:
+
+```
+import "tfconfig/v2" as tfconfig
+
+main = rule {
+ tfconfig.resources["aws_instance.foo"].config.ebs_block_device[0].volume_size < 10
+}
+```
+
+## The `providers` Collection
+
+The `providers` collection is a collection representing the configurations of
+all provider instances across all modules in the configuration.
+
+This collection is indexed by an opaque key. This is currently
+`module_address:provider.alias`, the same value as found in the
+`provider_config_key` field. `module_address` and the colon delimiter are
+omitted for the root module.
+
+The `provider_config_key` field is also found in the `resources` collection and
+can be used to locate a provider that belongs to a configured resource.
+
+The fields in this collection are as follows:
+
+* `provider_config_key` - The opaque configuration key, used as the index key.
+* `name` - The name of the provider, ie: `aws`.
+* `full_name` - The fully-qualified name of the provider, e.g. `registry.terraform.io/hashicorp/aws`.
+* `alias` - The alias of the provider, ie: `east`. Empty for a default provider.
+* `module_address` - The address of the module this provider appears in.
+* `config` - A [block expression
+ representation](#block-expression-representation) with provider configuration
+ values.
+* `version_constraint` - The defined version constraint for this provider.
+
+## The `resources` Collection
+
+The `resources` collection is a collection representing all of the resources
+found in all modules in the configuration.
+
+This collection is indexed by the resource address.
+
+The fields in this collection are as follows:
+
+* `address` - The resource address. This is the index of the collection.
+* `module_address` - The module address that this resource was found in.
+* `mode` - The resource mode, either `managed` (resources) or `data` (data
+ sources).
+* `type` - The type of resource, ie: `null_resource` in `null_resource.foo`.
+* `name` - The name of the resource, ie: `foo` in `null_resource.foo`.
+* `provider_config_key` - The opaque configuration key that serves as the index
+ of the [`providers`](#the-providers-collection) collection.
+* `provisioners` - The ordered list of provisioners for this resource. The
+ syntax of the provisioners matches those found in the
+ [`provisioners`](#the-provisioners-collection) collection, but is a list
+ indexed by the order the provisioners show up in the resource.
+* `config` - The [block expression
+ representation](#block-expression-representation) of the configuration values
+ found in the resource.
+* `count` - The [expression data](#expression-representations) for the `count`
+ value in the resource.
+* `for_each` - The [expression data](#expression-representations) for the
+ `for_each` value in the resource.
+* `depends_on` - The contents of the `depends_on` config directive, which
+ declares explicit dependencies for this resource.
+
+## The `provisioners` Collection
+
+The `provisioners` collection is a collection of all of the provisioners found
+across all resources in the configuration.
+
+While normally bound to a resource in an ordered fashion, this collection allows
+for the filtering of provisioners within a single expression.
+
+This collection is indexed with a key following the format
+`resource_address:index`, with each field matching their respective field in the
+particular element below:
+
+* `resource_address`: The address of the resource that the provisioner was found
+ in. This can be found in the [`resources`](#the-resources-collection)
+ collection.
+* `type`: The provisioner type, ie: `local_exec`.
+* `index`: The provisioner index as it shows up in the resource provisioner
+ order.
+* `config`: The [block expression
+ representation](#block-expression-representation) of the configuration values
+ in the provisioner.
+
+## The `variables` Collection
+
+The `variables` collection is a collection of all variables across all modules
+in the configuration.
+
+Note that this tracks variable definitions, not values. See the [`tfplan/v2`
+`variables` collection](/sentinel/features/terraform/tfplan-v2#the-variables-collection) for variable
+values set within a plan.
+
+This collection is indexed by the key format `module_address:name`, with each
+field matching their respective name below. `module_address` and the colon
+delimiter are omitted for the root module.
+
+* `module_address` - The address of the module the variable was found in.
+* `name` - The name of the variable.
+* `default` - The defined default value of the variable.
+* `description` - The description of the variable.
+
+## The `outputs` Collection
+
+The `outputs` collection is a collection of all outputs across all modules in
+the configuration.
+
+Note that this tracks variable definitions, not values. See the [`tfstate/v2`
+`outputs` collection](/sentinel/features/terraform/tfstate-v2#the-outputs-collection) for the final
+values of outputs set within a state. The [`tfplan/v2` `output_changes`
+collection](/sentinel/features/terraform/tfplan-v2#the-output_changes-collection) also contains a more
+complex collection of planned output changes.
+
+This collection is indexed by the key format `module_address:name`, with each
+field matching their respective name below. `module_address` and the colon
+delimiter are omitted for the root module.
+
+* `module_address` - The address of the module the output was found in.
+* `name` - The name of the output.
+* `sensitive` - Indicates whether or not the output was marked as
+ [`sensitive`](https://developer.hashicorp.com/terraform/language/values/outputs#sensitive-suppressing-values-in-cli-output).
+* `value` - An [expression representation](#expression-representations) for the output.
+* `description` - The description of the output.
+* `depends_on` - A list of resource names that the output depends on. These are
+ the hard-defined output dependencies as defined in the
+ [`depends_on`](https://developer.hashicorp.com/terraform/language/values/outputs#depends_on-explicit-output-dependencies)
+ field in an output declaration, not the dependencies that get derived from
+ natural evaluation of the output expression (these can be found in the
+ `references` field of the expression representation).
+
+## The `module_calls` Collection
+
+The `module_calls` collection is a collection of all module declarations at all
+levels within the configuration.
+
+Note that this is the
+[`module`](https://developer.hashicorp.com/terraform/language/modules#calling-a-child-module) stanza in
+any particular configuration, and not the module itself. Hence, a declaration
+for `module.foo` would actually be declared in the root module, which would be
+represented by a blank field in `module_address`.
+
+This collection is indexed by the key format `module_address:name`, with each
+field matching their respective name below. `module_address` and the colon
+delimiter are omitted for the root module.
+
+* `module_address` - The address of the module the declaration was found in.
+* `name` - The name of the module.
+* `source` - The contents of the `source` field.
+* `config` - A [block expression
+ representation](#block-expression-representation) for all parameter values
+ sent to the module.
+* `count` - An [expression representation](#expression-representations) for the
+ `count` field.
+* `depends_on`: An [expression representation](#expression-representations) for the
+ `depends_on` field.
+* `for_each` - An [expression representation](#expression-representations) for
+ the `for_each` field.
+* `version_constraint` - The string value found in the `version` field of the
+ module declaration.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/features/terraform/tfplan-v1.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/features/terraform/tfplan-v1.mdx
new file mode 100644
index 0000000000..1e9f9f1f9b
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/features/terraform/tfplan-v1.mdx
@@ -0,0 +1,614 @@
+---
+page_title: tfplan/v1 - terraform - Features
+sidebar_current: docs-features-terraform-tfplan-v1
+description: >-
+ The tfplan/v1 import provides access to a Terraform plan. A Terraform plan is the
+ file created as a result of `terraform plan` and is the input to `terraform
+ apply`. The plan represents the changes that Terraform needs to make to
+ infrastructure to reach the desired state represented by the configuration.
+layout: docs
+---
+
+# Import: tfplan/v1
+
+~> **Warning:** The `tfplan/v1` import is deprecated and will be permanently removed in August 2025.
+Use the updated [tfplan/v2](/sentinel/docs/features/terraform/tfplan-v2) import as soon as possible to avoid disruptions.
+The `tfplan/v2` import offers improved functionality and is designed to better support your policy enforcement needs.
+
+The `tfplan/v1` import provides access to a Terraform plan. A Terraform plan is the
+file created as a result of `terraform plan` and is the input to `terraform
+apply`. The plan represents the changes that Terraform needs to make to
+infrastructure to reach the desired state represented by the configuration.
+
+In addition to the diff data available in the plan, there is an
+[`applied`](#value-applied) state available that merges the plan with the state
+to create the planned state after apply.
+
+Finally, this import also allows you to access the configuration files and the
+Terraform state at the time the plan was run. See the section on [accessing a
+plan's state and configuration
+data](#accessing-a-plan-39-s-state-and-configuration-data) for more information.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan and schemas file.
+
+```hcl
+import "plugin" "tfplan/v1" {
+ config = {
+ "plan_path": "./path/to/plan.json",
+ "schemas_path": "./path/to/schemas.json"
+ }
+}
+```
+
+## Namespace Overview
+
+The following is a tree view of the import namespace. For more detail on a
+particular part of the namespace, see below.
+
+-> Note that the root-level alias keys shown here (`data`, `path`, and
+`resources`) are shortcuts to a [module namespace](#namespace-module) scoped to
+the root module. For more details, see the section on [root namespace
+aliases](#root-namespace-aliases).
+
+```
+tfplan/v1
+├── module() (function)
+│ └── (module namespace)
+│ ├── path ([]string)
+│ ├── data
+│ │ └── TYPE.NAME[NUMBER]
+│ │ ├── applied (map of keys)
+│ │ └── diff
+│ │ └── KEY
+│ │ ├── computed (bool)
+│ │ ├── new (string)
+│ │ └── old (string)
+│ └── resources
+│ └── TYPE.NAME[NUMBER]
+│ ├── applied (map of keys)
+│ ├── destroy (bool)
+│ ├── requires_new (bool)
+│ └── diff
+│ └── KEY
+│ ├── computed (bool)
+│ ├── new (string)
+│ └── old (string)
+├── module_paths ([][]string)
+├── terraform_version (string)
+├── variables (map of keys)
+│
+├── data (root module alias)
+├── path (root module alias)
+├── resources (root module alias)
+│
+├── config (tfconfig namespace alias)
+└── state (tfstate import alias)
+```
+
+## Namespace: Root
+
+The root-level namespace consists of the values and functions documented below.
+
+In addition to this, the root-level `data`, `path`, and `resources` keys alias
+to their corresponding namespaces or values within the [module
+namespace](#namespace-module).
+
+### Accessing a Plan's State and Configuration Data
+
+The `config` and `state` keys alias to the [`tfconfig`](/sentinel/features/terraform/tfconfig-v1) and
+[`tfstate`](/sentinel/features/terraform/tfstate-v1) namespaces, respectively, with the data sourced from
+the Terraform _plan_ (as opposed to actual configuration and state).
+
+-> Note that these aliases are not represented as maps. While they will appear
+empty when viewed as maps, the specific import namespace keys will still be
+accessible.
+
+### Function: `module()`
+
+```
+module = func(ADDR)
+```
+
+* **Return Type:** A [module namespace](#namespace-module).
+
+The `module()` function in the [root namespace](#namespace-root) returns the
+[module namespace](#namespace-module) for a particular module address.
+
+The address must be a list and is the module address, split on the period (`.`),
+excluding the root module.
+
+Hence, a module with an address of simply `foo` (or `root.foo`) would be
+`["foo"]`, and a module within that (so address `foo.bar`) would be read as
+`["foo", "bar"]`.
+
+[`null`](/sentinel/language/spec#null) is returned if a module address is
+invalid, or if the module is not present in the diff.
+
+As an example, given the following module block:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+If the module contained the following content:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.module(["foo"]).resources.null_resource.foo[0].applied.triggers.foo is "bar" }
+```
+
+### Value: `module_paths`
+
+* **Value Type:** List of a list of strings.
+
+The `module_paths` value within the [root namespace](#namespace-root) is a list
+of all of the modules within the Terraform diff for the current plan.
+
+Modules not present in the diff will not be present here, even if they are
+present in the configuration or state.
+
+This data is represented as a list of a list of strings, with the inner list
+being the module address, split on the period (`.`).
+
+The root module is included in this list, represented as an empty inner list, as
+long as there are changes.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The value of `module_paths` would be:
+
+```
+[
+ [],
+ ["foo"],
+]
+```
+
+And the following policy would evaluate to `true`:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.module_paths contains ["foo"] }
+```
+
+-> Note the above example only applies if the module is present in the diff.
+
+#### Iterating Through Modules
+
+Iterating through all modules to find particular resources can be useful. This
+[example][iterate-over-modules] shows how to use `module_paths` with the
+[`module()` function](#function-module-) to find all resources of a
+particular type from all modules that have pending changes using the `tfplan/v1`
+import.
+
+[iterate-over-modules]: https://developer.hashicorp.com/terraform/cloud-docs/policy-enforcement/sentinel#iterate-over-modules-and-find-resources
+
+### Value: `terraform_version`
+
+* **Value Type:** String.
+
+The `terraform_version` value within the [root namespace](#namespace-root)
+represents the version of Terraform used to create the plan. This can be used to
+enforce a specific version of Terraform in a policy check.
+
+As an example, the following policy would evaluate to `true`, as long as the
+plan was made with a version of Terraform in the 0.11.x series, excluding any
+pre-release versions (example: `-beta1` or `-rc1`):
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.terraform_version matches "^0\\.11\\.\\d+$" }
+```
+
+### Value: `variables`
+
+* **Value Type:** A string-keyed map of values.
+
+The `variables` value within the [root namespace](#namespace-root) represents
+all of the variables that were set when creating the plan. This will only
+contain variables set for the root module.
+
+Note that unlike the [`default`][import-tfconfig-variables-default] value in the
+[`tfconfig` variables namespace][import-tfconfig-variables], primitive values
+here are stringified, and type conversion will need to be performed to perform
+comparison for int, float, or boolean values. This only applies to variables
+that are primitives themselves and not primitives within maps and lists, which
+will be their original types.
+
+[import-tfconfig-variables-default]: /sentinel/features/terraform/tfconfig-v1#value-default
+
+[import-tfconfig-variables]: /sentinel/features/terraform/tfconfig-v1#namespace-variables
+
+If a default was accepted for the particular variable, the default value will be
+populated here.
+
+As an example, given the following variable blocks:
+
+```hcl
+variable "foo" {
+ default = "bar"
+}
+
+variable "number" {
+ default = 42
+}
+
+variable "map" {
+ default = {
+ foo = "bar"
+ number = 42
+ }
+}
+```
+
+The following policy would evaluate to `true`, if no values were entered to
+change these variables:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+default_foo = rule { tfplan.variables.foo is "bar" }
+default_number = rule { tfplan.variables.number is "42" }
+default_map_string = rule { tfplan.variables.map["foo"] is "bar" }
+default_map_int = rule { tfplan.variables.map["number"] is 42 }
+
+main = rule { default_foo and default_number and default_map_string and default_map_int }
+```
+
+## Namespace: Module
+
+The **module namespace** can be loaded by calling
+[`module()`](#function-module-) for a particular module.
+
+It can be used to load the following child namespaces, in addition to the values
+documented below:
+
+* `data` - Loads the [resource namespace](#namespace-resources-data-sources),
+ filtered against data sources.
+* `resources` - Loads the [resource
+ namespace](#namespace-resources-data-sources), filtered against resources.
+
+### Root Namespace Aliases
+
+The root-level `data` and `resources` keys both alias to their corresponding
+namespaces within the module namespace, loaded for the root module. They are the
+equivalent of running `module([]).KEY`.
+
+### Value: `path`
+
+* **Value Type:** List of strings.
+
+The `path` value within the [module namespace](#namespace-module) contains the
+path of the module that the namespace represents. This is represented as a list
+of strings.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The following policy would evaluate to `true` _only_ if the diff had changes for
+that module:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.module(["foo"]).path contains "foo" }
+```
+
+## Namespace: Resources/Data Sources
+
+The **resource namespace** is a namespace _type_ that applies to both resources
+(accessed by using the `resources` namespace key) and data sources (accessed
+using the `data` namespace key).
+
+Accessing an individual resource or data source within each respective namespace
+can be accomplished by specifying the type, name, and resource number (as if the
+resource or data source had a `count` value in it) in the syntax
+`[resources|data].TYPE.NAME[NUMBER]`. Note that NUMBER is always needed, even if
+you did not use `count` in the resource.
+
+In addition, each of these namespace levels is a map, allowing you to filter
+based on type and name.
+
+-> The (somewhat strange) notation here of `TYPE.NAME[NUMBER]` may imply that
+the inner resource index map is actually a list, but it's not - using the square
+bracket notation over the dotted notation (`TYPE.NAME.NUMBER`) is required here
+as an identifier cannot start with a number.
+
+Some examples of multi-level access are below:
+
+* To fetch all `aws_instance.foo` resource instances within the root module, you
+ can specify `tfplan.resources.aws_instance.foo`. This would then be indexed by
+ resource count index (`0`, `1`, `2`, and so on). Note that as mentioned above,
+ these elements must be accessed using square-bracket map notation (so `[0]`,
+ `[1]`, `[2]`, and so on) instead of dotted notation.
+* To fetch all `aws_instance` resources within the root module, you can specify
+ `tfplan.resources.aws_instance`. This would be indexed from the names of
+ each resource (`foo`, `bar`, and so on), with each of those maps containing
+ instances indexed by resource count index as per above.
+* To fetch all resources within the root module, irrespective of type, use
+ `tfplan.resources`. This is indexed by type, as shown above with
+ `tfplan.resources.aws_instance`, with names being the next level down, and so
+ on.
+
+~> When [resource targeting](https://developer.hashicorp.com/terraform/cli/commands/plan#resource-targeting) is
+ in effect, `tfplan.resources` will only include the resources specified as
+ targets for the run. This may lead to unexpected outcomes if a policy expects
+ a resource to be present in the plan.
+
+Further explanation of the namespace will be in the context of resources. As
+mentioned, when operating on data sources, use the same syntax, except with
+`data` in place of `resources`.
+
+### Value: `applied`
+
+* **Value Type:** A string-keyed map of values.
+
+The `applied` value within the [resource
+namespace](#namespace-resources-data-sources) contains a "predicted"
+representation of the resource's state post-apply. It's created by merging the
+pending resource's diff on top of the existing data from the resource's state
+(if any). The map is a complex representation of these values with data going
+as far down as needed to represent any state values such as maps, lists, and
+sets.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if the resource was in the diff:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].applied.triggers.foo is "bar" }
+```
+
+-> Note that some values will not be available in the `applied` state because
+they cannot be known until the plan is actually applied. In Terraform 0.11 or
+earlier, these values are represented by a placeholder (the UUID value
+`74D93920-ED26-11E3-AC10-0800200C9A66`) and in Terraform 0.12 or later they
+are `undefined`. **In either case**, you should instead use the
+[`computed`](#value-computed) key within the [diff
+namespace](#namespace-resource-diff) to determine that a computed value will
+exist.
+
+-> If a resource is being destroyed, its `applied` value is omitted from the
+namespace and trying to fetch it will return undefined.
+
+### Value: `diff`
+
+* **Value Type:** A map of [diff namespaces](#namespace-resource-diff).
+
+The `diff` value within the [resource
+namespace](#namespace-resources-data-sources) contains the diff for a particular
+resource. Each key within the map links to a [diff
+namespace](#namespace-resource-diff) for that particular key.
+
+Note that unlike the [`applied`](#value-applied) value, this map is not complex;
+the map is only 1 level deep with each key possibly representing a diff for a
+particular complex value within the resource.
+
+See the below section for more details on the diff namespace, in addition to
+usage examples.
+
+### Value: `destroy`
+
+* **Value Type:** Boolean.
+
+The `destroy` value within the [resource
+namespace](#namespace-resources-data-sources) is `true` if a resource is being
+destroyed for _any_ reason, including cases where it's being deleted as part of
+a resource re-creation, in which case [`requires_new`](#value-requires_new) will
+also be set.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` when `null_resource.foo` is being
+destroyed:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].destroy }
+```
+
+### Value: `requires_new`
+
+* **Value Type:** Boolean.
+
+The `requires_new` value within the [resource
+namespace](#namespace-resources-data-sources) is `true` if the resource is still
+present in the configuration, but must be replaced to satisfy its current diff.
+Whenever `requires_new` is `true`, [`destroy`](#value-destroy) is also `true`.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if one of the `triggers` in
+`null_resource.foo` was being changed:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].requires_new }
+```
+
+## Namespace: Resource Diff
+
+The **diff namespace** is a namespace that represents the diff for a specific
+attribute within a resource. For details on reading a particular attribute,
+see the [`diff`](#value-diff) value in the [resource
+namespace](#namespace-resources-data-sources).
+
+### Value: `computed`
+
+* **Value Type:** Boolean.
+
+The `computed` value within the [diff namespace](#namespace-resource-diff) is
+`true` if the resource key in question depends on another value that isn't yet
+known. Typically, that means the value it depends on belongs to a resource that
+either doesn't exist yet, or is changing state in such a way as to affect the
+dependent value so that it can't be known until the apply is complete.
+
+-> Keep in mind that when using `computed` with complex structures such as maps,
+lists, and sets, it's sometimes necessary to test the count attribute for the
+structure, versus a key within it, depending on whether or not the diff has
+marked the whole structure as computed. This is demonstrated in the example
+below. Count keys are `%` for maps, and `#` for lists and sets. If you are
+having trouble determining the type of specific field within a resource, contact
+the support team.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+
+resource "null_resource" "bar" {
+ triggers = {
+ foo_id = "${null_resource.foo.id}"
+ }
+}
+```
+
+The following policy would evaluate to `true`, if the `id` of
+`null_resource.foo` was currently not known, such as when the resource is
+pending creation, or is being deleted and re-created:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.bar[0].diff["triggers.%"].computed }
+```
+
+### Value: `new`
+
+* **Value Type:** String.
+
+The `new` value within the [diff namespace](#namespace-resource-diff) contains
+the new value of a changing attribute, _if_ the value is known at plan time.
+
+-> `new` will be an empty string if the attribute's value is currently unknown.
+For more details on detecting unknown values, see [`computed`](#value-computed).
+
+Note that this value is _always_ a string, regardless of the actual type of the
+value changing. [Type conversion][ref-sentinel-type-conversion] within policy
+may be necessary to achieve the comparison needed.
+
+[ref-sentinel-type-conversion]: https://docs.hashicorp.com/sentinel/language/values#type-conversion
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`, if the resource was in the diff
+and each of the concerned keys were changing to new values:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].diff["triggers.foo"].new is "bar" }
+```
+
+### Value: `old`
+
+* **Value Type:** String.
+
+The `old` value within the [diff namespace](#namespace-resource-diff) contains
+the old value of a changing attribute.
+
+Note that this value is _always_ a string, regardless of the actual type of the
+value changing. [Type conversion][ref-sentinel-type-conversion] within policy
+may be necessary to achieve the comparison needed.
+
+If the value did not exist in the previous state, `old` will always be an empty
+string.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "baz"
+ }
+}
+```
+
+If that resource was previously in config as:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].diff["triggers.foo"].old is "bar" }
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/features/terraform/tfplan-v2.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/features/terraform/tfplan-v2.mdx
new file mode 100644
index 0000000000..61fb7b2bf2
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/features/terraform/tfplan-v2.mdx
@@ -0,0 +1,402 @@
+---
+page_title: tfplan/v2 - terraform - Features
+sidebar_current: docs-features-terraform-tfplan-v2
+description: >-
+ The tfplan import provides access to a Terraform plan. A Terraform plan is the
+ file created as a result of `terraform plan` and is the input to `terraform
+ apply`. The plan represents the changes that Terraform needs to make to
+ infrastructure to reach the desired state represented by the configuration.
+layout: docs
+---
+
+# Import: tfplan/v2
+
+The `tfplan/v2` import provides access to a Terraform plan.
+
+A Terraform plan is the file created as a result of `terraform plan` and is the
+input to `terraform apply`. The plan represents the changes that Terraform needs
+to make to infrastructure to reach the desired state represented by the
+configuration.
+
+In addition to the diff data available in the plan, there is a "planned state"
+that is available through this import, via the
+[`planned_values`](#the-planned_values-collection) collection. This collection
+presents the Terraform state as how it might look after the plan data is
+applied, but is not guaranteed to be the final state.
+
+The data in the `tfplan/v2` import is sourced from the JSON configuration file
+that is generated by the [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) command. For more information on
+the file format, see the [JSON Output Format](https://developer.hashicorp.com/terraform/internals/json-format)
+page.
+
+The entirety of the JSON output file is exposed as a Sentinel map via the
+[`raw`](#the-raw-collection) collection. This allows direct, low-level access to
+the JSON data, but should only be used in complex situations where the
+higher-level collections do not serve the purpose.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfplan/v2" {
+ config = {
+ "plan_path": "./path/to/plan.json"
+ }
+}
+```
+
+## Import Overview
+
+The `tfplan/v2` import is structured as a series of _collections_, keyed as a
+specific format depending on the collection.
+
+```
+tfplan/v2
+├── terraform_version (string)
+├── variables
+│ └── (indexed by name)
+│ ├── name (string)
+│ └── value (value)
+├── planned_values
+│ ├── outputs (tfstate/v2 outputs representation)
+│ └── resources (tfstate/v2 resources representation)
+├── resource_changes
+│ └── (indexed by address[:deposed])
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── index (float (number) or string)
+│ ├── provider_name (string)
+│ ├── deposed (string)
+│ └── change (change representation)
+├── resource_drift
+│ └── (indexed by address[:deposed])
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── index (float (number) or string)
+│ ├── provider_name (string)
+│ ├── deposed (string)
+│ └── change (change representation)
+├── output_changes
+│ └── (indexed by name)
+│ ├── name (string)
+│ └── change (change representation)
+└── raw (map)
+```
+
+The collections are:
+
+* [`variables`](#the-variables-collection) - The values of variables that have
+ been set in the plan itself. This collection only contains variables set in
+ the root module.
+* [`planned_values`](#the-planned_values-collection) - The state representation
+ of _planned values_, or an estimation of what the state will look like after
+ the plan is applied.
+* [`resource_changes`](#the-resource_changes-and-resource_drift-collections) - The set of change
+ operations for resources and data sources within this plan.
+* [`resource_drift`](#the-resource_changes-and-resource_drift-collections) - A description of the
+ changes Terraform detected when it compared the most recent state to the prior saved state.
+* [`output_changes`](#the-output_changes-collection) - The changes to outputs
+ within this plan. This collection only contains outputs set in the root
+ module.
+* [`raw`](#the-raw-collection) - Access to the raw plan data.
+
+These collections are specifically designed to be used with the
+[`filter`](/sentinel/language/collection-operations#filter-expression)
+quantifier expression in Sentinel, so that one can collect a list of resources
+to perform policy checks on without having to write complex discovery code. As
+an example, the following code will return all `aws_instance` resource changes,
+across all modules in the plan:
+
+```
+all_aws_instances = filter tfplan.resource_changes as _, rc {
+ rc.mode is "managed" and
+ rc.type is "aws_instance"
+}
+```
+
+You can add specific attributes to the filter to narrow the search, such as the
+module address, or the operation being performed. The following code would
+return resources in a module named `foo` only, and further narrow the search
+down to only resources that were being created:
+
+```
+all_aws_instances = filter tfplan.resource_changes as _, rc {
+ rc.module_address is "module.foo" and
+ rc.mode is "managed" and
+ rc.type is "aws_instance" and
+ rc.change.actions is ["create"]
+}
+```
+
+### Change Representation
+
+Certain collections in this import contain a _change representation_, an object
+with details about changes to a particular entity, such as a resource (within
+the [`resource_changes`](#the-resource_changes-collection) collection), or
+output (within the [`output_changes`](#the-output_changes-collection)
+collection).
+
+```
+(change representation)
+├── actions (list)
+├── before (value, or map)
+├── after (value, or map)
+└── after_unknown (boolean, or map of booleans)
+```
+
+This change representation contains the following fields:
+
+* `actions` - A list of actions being carried out for this change. The order is
+ important, for example a regular replace operation is denoted by `["delete",
+ "create"]`, but a
+ [`create_before_destroy`](https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle#create_before_destroy)
+ resource will have an operation order of `["create", "delete"]`.
+* `before` - The representation of the resource data object value before the
+ action. For create-only actions, this is unset. For no-op actions, this value
+ will be identical with `after`.
+* `after` - The representation of the resource data object value after the
+ action. For delete-only actions, this is unset. For no-op actions, this value
+ will be identical with `before`. Note that unknown values will not show up in
+ this field.
+* `after_unknown` - A deep object of booleans that denotes any values that are
+ unknown in a resource. These values were previously referred to as "computed"
+ values. If the value cannot be found in this map, then its value should be
+ available within `after`, so long as the operation supports it.
+
+#### Actions
+
+As mentioned above, actions show up within the `actions` field of a change
+representation and indicate the type of actions being performed as part of the
+change, and the order that they are being performed in.
+
+The current list of actions are as follows:
+
+* `create` - The action will create the associated entity. Depending on the
+ order this appears in, the entity may be created alongside a copy of the
+ entity before replacing it.
+* `read` - The action will read the associated entity. In practice, seeing this
+ change type should be rare, as reads generally happen before a plan is
+ executed (usually during a refresh).
+* `update` - The action will update the associated entity in a way that alters its state
+ in some way.
+* `delete` - The action will remove the associated entity, deleting any
+ applicable state and associated real resources or infrastructure.
+* `no-op` - No action will be performed on the associated entity.
+
+The `actions` field is a list, as some real-world actions are actually a
+composite of more than one primitive action. At this point in time, this
+is generally only applicable to resource replacement, in which the following
+action orders apply:
+
+* **Normal replacement:** `["delete", "create"]` - Applies to default lifecycle
+ configurations.
+* **Create-before-destroy:** `["create", "delete"]` - Applies when
+ [`create_before_destroy`](https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle#create_before_destroy)
+ is used in a lifecycle configuration.
+
+Note that, in most situations, the plan will list all "changes", including no-op
+changes. This makes filtering on change type crucial to the accurate selection
+of data if you are concerned with the state change of a particular resource.
+
+To filter on a change type, use exact list comparison. For example, the
+following example from the [Import Overview](#import-overview) filters on
+exactly the resources being created _only_:
+
+```
+all_aws_instances = filter tfplan.resource_changes as _, rc {
+ rc.module_address is "module.foo" and
+ rc.mode is "managed" and
+ rc.type is "aws_instance" and
+ rc.change.actions is ["create"]
+}
+```
+
+#### `before`, `after`, and `after_unknown`
+
+The exact attribute changes for a particular operation are outlined in the
+`before` and `after` attributes. Depending on the entity being operated on, this
+will either be a map (as with
+[`resource_changes`](#the-resource_changes-collection)) or a singular value (as
+with [`output_changes`](#the-output_changes-collection)).
+
+What you can expect in these fields varies depending on the operation:
+
+* For fresh create operations, `before` will generally be `null`, and `after`
+ will contain the data you can expect to see after the change.
+* For full delete operations, this will be reversed - `before` will contain
+ data, and `after` will be `null`.
+* Update or replace operations will have data in both fields relevant to their
+ states before and after the operation.
+* No-op operations should have identical data in `before` and `after`.
+
+For resources, if a field cannot be found in `after`, it generally means one of
+two things:
+
+* The attribute does not exist in the resource schema. Generally, known
+ attributes that do not have a value will show up as `null` or otherwise empty
+ in `after`.
+* The attribute is _unknown_, that is, it was unable to be determined at plan
+ time and will only be available after apply-time values have been able to be
+ calculated.
+
+In the latter case, there should be a value for the particular attribute in
+`after_unknown`, which can be checked to assert that the value is indeed
+unknown, versus invalid:
+
+```
+import "tfplan/v2" as tfplan
+
+no_unknown_amis = rule {
+ all filter tfplan.resource_changes as _, rc {
+ rc.module_address is "module.foo" and
+ rc.mode is "managed" and
+ rc.type is "aws_instance" and
+ rc.change.actions is ["create"]
+ } as _, rc {
+ rc.change.after_unknown.ami else false is false
+ }
+}
+```
+
+For output changes, `after_unknown` will simply be `true` if the value won't be
+known until the plan is applied.
+
+## The `terraform_version` Value
+
+The top-level `terraform_version` value in this import gives the Terraform
+version that made the plan. This can be used to do version validation.
+
+```
+import "tfplan/v2" as tfplan
+import "strings"
+
+v = strings.split(tfplan.terraform_version, ".")
+version_major = int(v[1])
+version_minor = int(v[2])
+
+main = rule {
+ version_major is 12 and version_minor >= 19
+}
+```
+
+## The `variables` Collection
+
+The `variables` collection is a collection of the variables set in the root
+module when creating the plan.
+
+This collection is indexed on the name of the variable.
+
+The valid values are:
+
+* `name` - The name of the variable, also used as the collection key.
+* `value` - The value of the variable assigned during the plan.
+
+## The `planned_values` Collection
+
+The `planned_values` collection is a special collection in that it contains two
+fields that alias to state collections with the _planned_ state set. This is the
+best prediction of what the state will look like after the plan is executed.
+
+The two fields are:
+
+* `outputs` - The prediction of what output values will look like after the
+ state is applied. For more details on the structure of this collection, see
+ the [`outputs`](/sentinel/features/terraform/tfstate-v2#the-outputs-collection) collection in the
+ [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2) documentation.
+* `resources` - The prediction of what resource values will look like after the
+ state is applied. For more details on the structure of this collection, see
+ the [`resources`](/sentinel/features/terraform/tfstate-v2#the-resources-collection) collection in the
+ [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2) documentation.
+
+-> **NOTE:** Unknown values are omitted from the `planned_values` state
+representations, regardless of whether or not they existed before. Use
+[`resource_changes`](#the-resource_changes-collection) if awareness of unknown
+data is important.
+
+## The `resource_changes` and `resource_drift` Collections
+
+The `resource_changes` and `resource_drift` collections are a set of change operations for resources
+and data sources within this plan.
+
+The `resource_drift` collection provides a description of the changes Terraform detected
+when it compared the most recent state to the prior saved state.
+
+The `resource_changes` collection includes all resources that have been found in the configuration and state,
+regardless of whether or not they are changing.
+
+~> When [resource targeting](/terraform/cli/commands/plan#resource-targeting) is in effect, the `resource_changes` collection will only include the resources specified as targets for the run. This may lead to unexpected outcomes if a policy expects a resource to be present in the plan. To prohibit targeted runs altogether, ensure [`tfrun.target_addrs`](/terraform/cloud-docs/policy-enforcement/sentinel/import/tfrun#value-target_addrs) is undefined or empty.
+
+This collection is indexed on the complete resource address as the key. If
+`deposed` is non-empty, it is appended to the end, and may look something like
+`aws_instance.foo:deposed-abc123`.
+
+An element contains the following fields:
+
+* `address` - The absolute resource address - also the key for the collection's
+ index, if `deposed` is empty.
+
+* `module_address` - The module portion of the absolute resource address.
+
+* `mode` - The resource mode, either `managed` (resources) or `data` (data
+ sources).
+
+* `type` - The resource type, example: `aws_instance` for `aws_instance.foo`.
+
+* `name` - The resource name, example: `foo` for `aws_instance.foo`.
+
+* `index` - The resource index. Can be either a number or a string.
+
+* `provider_name` - The name of the provider this resource belongs to. This
+ allows the provider to be interpreted unambiguously in the unusual situation
+ where a provider offers a resource type whose name does not start with its own
+ name, such as the `googlebeta` provider offering `google_compute_instance`.
+
+ -> **Note:** Starting with Terraform 0.13, the `provider_name` field contains the
+ _full_ source address to the provider in the Terraform Registry. Example:
+ `registry.terraform.io/hashicorp/null` for the null provider.
+
+* `deposed` - An identifier used during replacement operations, and can be used
+ to identify the exact resource being replaced in state.
+
+* `change` - The data describing the change that will be made to this resource.
+ For more details, see [Change Representation](#change-representation).
+
+## The `output_changes` Collection
+
+The `output_changes` collection is a collection of the change operations for
+outputs within this plan.
+
+Only outputs for the root module are included.
+
+This collection is indexed by the name of the output. The fields in a collection
+value are below:
+
+* `name` - The name of the output, also the index key.
+* `change` - The data describing the change that will be made to this output.
+ For more details, see [Change Representation](#change-representation).
+
+## The `raw` Collection
+
+The `raw` collection exposes the raw, unprocessed plan data.
+
+This is the same data that is produced by [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) on the plan file for the run this
+policy check is attached to.
+
+Use of this data is only recommended in expert situations where the data the
+collections present may not exactly serve the needs of the policy. For more
+information on the file format, see the [JSON Output
+Format](https://developer.hashicorp.com/terraform/internals/json-format) page.
+
+-> **NOTE:** Although designed to be relatively stable, the actual makeup for
+the JSON output format is a Terraform CLI concern and as such not managed by
+Sentinel. Use at your own risk, follow the [Terraform CLI
+project](https://github.com/hashicorp/terraform), and watch the file format
+documentation for any changes.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/features/terraform/tfstate-v1.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/features/terraform/tfstate-v1.mdx
new file mode 100644
index 0000000000..7ff2690947
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/features/terraform/tfstate-v1.mdx
@@ -0,0 +1,556 @@
+---
+page_title: tfstate/v1 - terraform - Features
+sidebar_current: docs-features-terraform-tfstate-v1
+description: The tfstate/v1 import provides access to a Terraform state.
+layout: docs
+---
+
+# Import: tfstate/v1
+
+~> **Warning:** The `tfstate/v1` import is deprecated and will be permanently removed in August 2025.
+Use the updated [tfstate/v2](/sentinel/docs/features/terraform/tfstate-v2) import as soon as possible to avoid disruptions.
+The `tfstate/v2` import offers improved functionality and is designed to better support your policy enforcement needs.
+
+The `tfstate/v1` import provides access to the Terraform state.
+
+The _state_ is the data that Terraform has recorded about a workspace at a
+particular point in its lifecycle, usually after an apply. You can read more
+general information about how Terraform uses state [here][ref-tf-state].
+
+[ref-tf-state]: https://developer.hashicorp.com/terraform/language/state
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfstate/v1" {
+ config = {
+ "path": "./path/to/plan.json"
+ }
+}
+```
+
+## Namespace Overview
+
+The following is a tree view of the import namespace. For more detail on a
+particular part of the namespace, see below.
+
+-> Note that the root-level alias keys shown here (`data`, `outputs`, `path`,
+and `resources`) are shortcuts to a [module namespace](#namespace-module) scoped
+to the root module. For more details, see the section on [root namespace
+aliases](#root-namespace-aliases).
+
+```
+tfstate/v1
+├── module() (function)
+│ └── (module namespace)
+│ ├── path ([]string)
+│ ├── data
+│ │ └── TYPE.NAME[NUMBER]
+│ │ ├── attr (map of keys)
+│ │ ├── depends_on ([]string)
+│ │ ├── id (string)
+│ │ └── tainted (boolean)
+│ ├── outputs (root module only in TF 0.12 or later)
+│ │ └── NAME
+│ │ ├── sensitive (bool)
+│ │ ├── type (string)
+│ │ └── value (value)
+│ └── resources
+│ └── TYPE.NAME[NUMBER]
+│ ├── attr (map of keys)
+│ ├── depends_on ([]string)
+│ ├── id (string)
+│ └── tainted (boolean)
+│
+├── module_paths ([][]string)
+├── terraform_version (string)
+│
+├── data (root module alias)
+├── outputs (root module alias)
+├── path (root module alias)
+└── resources (root module alias)
+```
+
+## Namespace: Root
+
+The root-level namespace consists of the values and functions documented below.
+
+In addition to this, the root-level `data`, `outputs`, `path`, and `resources`
+keys alias to their corresponding namespaces or values within the [module
+namespace](#namespace-module).
+
+### Function: `module()`
+
+```
+module = func(ADDR)
+```
+
+* **Return Type:** A [module namespace](#namespace-module).
+
+The `module()` function in the [root namespace](#namespace-root) returns the
+[module namespace](#namespace-module) for a particular module address.
+
+The address must be a list and is the module address, split on the period (`.`),
+excluding the root module.
+
+Hence, a module with an address of simply `foo` (or `root.foo`) would be
+`["foo"]`, and a module within that (so address `foo.bar`) would be read as
+`["foo", "bar"]`.
+
+[`null`][ref-null] is returned if a module address is invalid, or if the module
+is not present in the state.
+
+[ref-null]: /sentinel/language/spec#null
+
+As an example, given the following module block:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+If the module contained the following content:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if the resource was present in
+the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.module(["foo"]).resources.null_resource.foo[0].attr.triggers.foo is "bar" }
+```
+
+### Value: `module_paths`
+
+* **Value Type:** List of a list of strings.
+
+The `module_paths` value within the [root namespace](#namespace-root) is a list
+of all of the modules within the Terraform state at plan-time.
+
+Modules not present in the state will not be present here, even if they are
+present in the configuration or the diff.
+
+This data is represented as a list of a list of strings, with the inner list
+being the module address, split on the period (`.`).
+
+The root module is included in this list, represented as an empty inner list, as
+long as it is present in state.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The value of `module_paths` would be:
+
+```
+[
+ [],
+ ["foo"],
+]
+```
+
+And the following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.module_paths contains ["foo"] }
+```
+
+-> Note the above example only applies if the module is present in the state.
+
+#### Iterating Through Modules
+
+Iterating through all modules to find particular resources can be useful. This
+[example][iterate-over-modules] shows how to use `module_paths` with the
+[`module()` function](#function-module-) to find all resources of a
+particular type from all modules using the `tfplan` import. By changing `tfplan`
+in this function to `tfstate`, you could make a similar function find all
+resources of a specific type in the current state.
+
+[iterate-over-modules]: https://developer.hashicorp.com/terraform/cloud-docs/policy-enforcement/sentinel#sentinel-imports
+
+### Value: `terraform_version`
+
+* **Value Type:** String.
+
+The `terraform_version` value within the [root namespace](#namespace-root)
+represents the version of Terraform in use when the state was saved. This can be
+used to enforce a specific version of Terraform in a policy check.
+
+As an example, the following policy would evaluate to `true` as long as the
+state was made with a version of Terraform in the 0.11.x series, excluding any
+pre-release versions (example: `-beta1` or `-rc1`):
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.terraform_version matches "^0\\.11\\.\\d+$" }
+```
+
+-> **NOTE:** This value is also available via the [`tfplan`](/sentinel/features/terraform/tfplan-v1)
+import, which will be more current when a policy check is run against a plan.
+It's recommended you use the value in `tfplan` until HCP Terraform
+supports policy checks in other stages of the workspace lifecycle. See the
+[`terraform_version`][import-tfplan-terraform-version] reference within the
+`tfplan` import for more details.
+
+[import-tfplan-terraform-version]: /sentinel/features/terraform/tfplan-v1#value-terraform_version
+
+## Namespace: Module
+
+The **module namespace** can be loaded by calling
+[`module()`](#function-module-) for a particular module.
+
+It can be used to load the following child namespaces, in addition to the values
+documented below:
+
+* `data` - Loads the [resource namespace](#namespace-resources-data-sources),
+ filtered against data sources.
+* `outputs` - Loads the [output namespace](#namespace-outputs), which supply the
+ outputs present in this module's state. Note that with Terraform 0.12 or
+ later, this value is only available for the root namespace.
+* `resources` - Loads the [resource
+ namespace](#namespace-resources-data-sources), filtered against resources.
+
+### Root Namespace Aliases
+
+The root-level `data`, `outputs`, and `resources` keys both alias to their
+corresponding namespaces within the module namespace, loaded for the root
+module. They are the equivalent of running `module([]).KEY`.
+
+### Value: `path`
+
+* **Value Type:** List of strings.
+
+The `path` value within the [module namespace](#namespace-module) contains the
+path of the module that the namespace represents. This is represented as a list
+of strings.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The following policy would evaluate to `true`, _only_ if the module was present
+in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.module(["foo"]).path contains "foo" }
+```
+
+## Namespace: Resources/Data Sources
+
+The **resource namespace** is a namespace _type_ that applies to both resources
+(accessed by using the `resources` namespace key) and data sources (accessed
+using the `data` namespace key).
+
+Accessing an individual resource or data source within each respective namespace
+can be accomplished by specifying the type, name, and resource number (as if the
+resource or data source had a `count` value in it) in the syntax
+`[resources|data].TYPE.NAME[NUMBER]`. Note that NUMBER is always needed, even if
+you did not use `count` in the resource.
+
+In addition, each of these namespace levels is a map, allowing you to filter
+based on type and name.
+
+-> The (somewhat strange) notation here of `TYPE.NAME[NUMBER]` may imply that
+the inner resource index map is actually a list, but it's not - using the square
+bracket notation over the dotted notation (`TYPE.NAME.NUMBER`) is required here
+as an identifier cannot start with number.
+
+Some examples of multi-level access are below:
+
+* To fetch all `aws_instance.foo` resource instances within the root module, you
+ can specify `tfstate.resources.aws_instance.foo`. This would then be indexed
+ by resource count index (`0`, `1`, `2`, and so on). Note that as mentioned
+ above, these elements must be accessed using square-bracket map notation (so
+ `[0]`, `[1]`, `[2]`, and so on) instead of dotted notation.
+* To fetch all `aws_instance` resources within the root module, you can specify
+ `tfstate.resources.aws_instance`. This would be indexed from the names of
+ each resource (`foo`, `bar`, and so on), with each of those maps containing
+ instances indexed by resource count index as per above.
+* To fetch all resources within the root module, irrespective of type, use
+ `tfstate.resources`. This is indexed by type, as shown above with
+ `tfstate.resources.aws_instance`, with names being the next level down, and so
+ on.
+
+Further explanation of the namespace will be in the context of resources. As
+mentioned, when operating on data sources, use the same syntax, except with
+`data` in place of `resources`.
+
+### Value: `attr`
+
+* **Value Type:** A string-keyed map of values.
+
+The `attr` value within the [resource
+namespace](#namespace-resources-data-sources) is a direct mapping to the state
+of the resource.
+
+The map is a complex representation of these values with data going as far down
+as needed to represent any state values such as maps, lists, and sets.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if the resource was in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.resources.null_resource.foo[0].attr.triggers.foo is "bar" }
+```
+
+### Value: `depends_on`
+
+* **Value Type:** A list of strings.
+
+The `depends_on` value within the [resource
+namespace](#namespace-resources-data-sources) contains the dependencies for the
+resource.
+
+This is a list of full resource addresses, relative to the module (example:
+`null_resource.foo`).
+
+As an example, given the following resources:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+
+resource "null_resource" "bar" {
+ # ...
+
+ depends_on = [
+ "null_resource.foo",
+ ]
+}
+```
+
+The following policy would evaluate to `true` if the resource was in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.resources.null_resource.bar[0].depends_on contains "null_resource.foo" }
+```
+
+### Value: `id`
+
+* **Value Type:** String.
+
+The `id` value within the [resource
+namespace](#namespace-resources-data-sources) contains the id of the resource.
+
+-> **NOTE:** The example below uses a _data source_ here because the
+[`null_data_source`][ref-tf-null-data-source] data source gives a static ID,
+which makes documenting the example easier. As previously mentioned, data
+sources share the same namespace as resources, but need to be loaded with the
+`data` key. For more information, see the
+[synopsis](#namespace-resources-data-sources) for the namespace itself.
+
+[ref-tf-null-data-source]: https://registry.terraform.io/providers/hashicorp/null/latest/docs/data-sources/data_source
+
+As an example, given the following data source:
+
+```hcl
+data "null_data_source" "foo" {
+ # ...
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.data.null_data_source.foo[0].id is "static" }
+```
+
+### Value: `tainted`
+
+* **Value Type:** Boolean.
+
+The `tainted` value within the [resource
+namespace](#namespace-resources-data-sources) is `true` if the resource is
+marked as tainted in Terraform state.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`, if the resource was marked as
+tainted in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.resources.null_resource.foo[0].tainted }
+```
+
+## Namespace: Outputs
+
+The **output namespace** represents all of the outputs present within a
+[module](#namespace-module). Outputs are present in a state if they were saved
+during a previous apply, or if they were updated with known values during the
+pre-plan refresh.
+
+**With Terraform 0.11 or earlier** this can be used to fetch both the outputs
+of the root module, and the outputs of any module in the state below the root.
+This makes it possible to see outputs that have not been threaded to the root
+module.
+
+**With Terraform 0.12 or later** outputs are available in the top-level (root
+module) namespace only and not accessible within submodules.
+
+This namespace is indexed by output name.
+
+### Value: `sensitive`
+
+* **Value Type:** Boolean.
+
+The `sensitive` value within the [output namespace](#namespace-outputs) is
+`true` when the output has been [marked as sensitive][ref-tf-sensitive-outputs].
+
+[ref-tf-sensitive-outputs]: https://developer.hashicorp.com/terraform/language/values/outputs#sensitive-suppressing-values-in-cli-output
+
+As an example, given the following output:
+
+```hcl
+output "foo" {
+ sensitive = true
+ value = "bar"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.outputs.foo.sensitive }
+```
+
+### Value: `type`
+
+* **Value Type:** String.
+
+The `type` value within the [output namespace](#namespace-outputs) gives the
+output's type. This will be one of `string`, `list`, or `map`. These are
+currently the only types available for outputs in Terraform.
+
+As an example, given the following output:
+
+```hcl
+output "string" {
+ value = "foo"
+}
+
+output "list" {
+ value = [
+ "foo",
+ "bar",
+ ]
+}
+
+output "map" {
+ value = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+type_string = rule { tfstate.outputs.string.type is "string" }
+type_list = rule { tfstate.outputs.list.type is "list" }
+type_map = rule { tfstate.outputs.map.type is "map" }
+
+main = rule { type_string and type_list and type_map }
+```
+
+### Value: `value`
+
+* **Value Type:** String, list, or map.
+
+The `value` value within the [output namespace](#namespace-outputs) is the value
+of the output in question.
+
+Note that the only valid primitive output type in Terraform is currently a
+string, which means that any int, float, or boolean value will need to be
+converted before it can be used in comparison. This does not apply to primitives
+within maps and lists, which will be their original types.
+
+As an example, given the following output blocks:
+
+```hcl
+output "foo" {
+ value = "bar"
+}
+
+output "number" {
+ value = "42"
+}
+
+output "map" {
+ value = {
+ foo = "bar"
+ number = 42
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+value_foo = rule { tfstate.outputs.foo.value is "bar" }
+value_number = rule { int(tfstate.outputs.number.value) is 42 }
+value_map_string = rule { tfstate.outputs.map.value["foo"] is "bar" }
+value_map_int = rule { tfstate.outputs.map.value["number"] is 42 }
+
+main = rule { value_foo and value_number and value_map_string and value_map_int }
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/features/terraform/tfstate-v2.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/features/terraform/tfstate-v2.mdx
new file mode 100644
index 0000000000..9b29aa2c51
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/features/terraform/tfstate-v2.mdx
@@ -0,0 +1,176 @@
+---
+page_title: tfstate/v2 - terraform - Features
+sidebar_current: docs-features-terraform-tfstate-v2
+description: The tfstate/v2 import provides access to a Terraform state.
+layout: docs
+---
+
+# Import: tfstate/v2
+
+The `tfstate/v2` import provides access to a Terraform state.
+
+The _state_ is the data that Terraform has recorded about a workspace at a
+particular point in its lifecycle, usually after an apply. You can read more
+general information about how Terraform uses state
+[here](https://developer.hashicorp.com/terraform/language/state).
+
+The data in the `tfstate/v2` import is sourced from the JSON configuration file
+that is generated by the [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) command. For more information on
+the file format, see the [JSON Output Format](https://developer.hashicorp.com/terraform/internals/json-format)
+page.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfstate/v2" {
+ config = {
+ "path": "./path/to/plan.json"
+ }
+}
+```
+
+## Import Overview
+
+The `tfstate/v2` import is structured as currently two _collections_, keyed in
+resource address and output name, respectively.
+
+```
+(tfstate/v2)
+├── terraform_version (string)
+├── resources
+│ └── (indexed by address)
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── index (float (number) or string)
+│ ├── provider_name (string)
+│ ├── values (map)
+│ ├── depends_on (list of strings)
+│ ├── tainted (boolean)
+│ └── deposed_key (string)
+└── outputs
+ └── (indexed by name)
+ ├── name (string)
+ ├── sensitive (boolean)
+ └── value (value)
+```
+
+The collections are:
+
+* [`resources`](#the-resources-collection) - The state of all resources across
+ all modules in the state.
+* [`outputs`](#the-outputs-collection) - The state of all outputs from the root module in the state.
+
+These collections are specifically designed to be used with the
+[`filter`](/sentinel/language/collection-operations#filter-expression)
+quantifier expression in Sentinel, so that one can collect a list of resources
+to perform policy checks on without having to write complex module traversal. As
+an example, the following code will return all `aws_instance` resource types
+within the state, regardless of what module they are in:
+
+```
+all_aws_instances = filter tfstate.resources as _, r {
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+You can add specific attributes to the filter to narrow the search, such as the
+module address. The following code would return resources in a module named
+`foo` only:
+
+```
+all_aws_instances = filter tfstate.resources as _, r {
+ r.module_address is "module.foo" and
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+## The `terraform_version` Value
+
+The top-level `terraform_version` value in this import gives the Terraform
+version that recorded the state. This can be used to do version validation.
+
+```
+import "tfstate/v2" as tfstate
+import "strings"
+
+v = strings.split(tfstate.terraform_version, ".")
+version_major = int(v[1])
+version_minor = int(v[2])
+
+main = rule {
+ version_major is 12 and version_minor >= 19
+}
+```
+
+## The `resources` Collection
+
+The `resources` collection is a collection representing all of the resources in
+the state, across all modules.
+
+This collection is indexed on the complete resource address as the key.
+
+An element in the collection has the following values:
+
+* `address` - The absolute resource address - also the key for the collection's
+ index.
+
+* `module_address` - The address portion of the absolute resource address.
+
+* `mode` - The resource mode, either `managed` (resources) or `data` (data
+ sources).
+
+* `type` - The resource type, example: `aws_instance` for `aws_instance.foo`.
+
+* `name` - The resource name, example: `foo` for `aws_instance.foo`.
+
+* `index` - The resource index. Can be either a number or a string.
+
+* `provider_name` - The name of the provider this resource belongs to. This
+ allows the provider to be interpreted unambiguously in the unusual situation
+ where a provider offers a resource type whose name does not start with its own
+ name, such as the `googlebeta` provider offering `google_compute_instance`.
+
+ -> **Note:** Starting with Terraform 0.13, the `provider_name` field contains the
+ _full_ source address to the provider in the Terraform Registry. Example:
+ `registry.terraform.io/hashicorp/null` for the null provider.
+
+* `values` - An object (map) representation of the attribute values of the
+ resource, whose structure depends on the resource type schema. When accessing
+ proposed state through the [`planned_values`](/sentinel/features/terraform/tfplan-v2#the-planned_values-collection)
+ collection of the tfplan/v2 import, unknown values will be omitted.
+
+* `depends_on` - The addresses of the resources that this resource depends on.
+
+* `tainted` - `true` if the resource has been explicitly marked as
+ [tainted](https://developer.hashicorp.com/terraform/cli/commands/taint) in the state.
+
+* `deposed_key` - Set if the resource has been marked deposed and will be
+ destroyed on the next apply. This matches the deposed field in the
+ [`resource_changes`](/sentinel/features/terraform/tfplan-v2#the-resource_changes-collection)
+ collection in the [`tfplan/v2`](/sentinel/features/terraform/tfplan-v2) import.
+
+## The `outputs` Collection
+
+The `outputs` collection is a collection of outputs from the root module of the
+state.
+
+Note that no child modules are included in this output set, and there is no way
+to fetch child module output values. This is to encourage the correct flow of
+outputs to the recommended root consumption level.
+
+The collection is indexed on the output name, with the following fields:
+
+* `name`: The name of the output, also the collection key.
+* `sensitive`: Whether or not the value was marked as
+ [sensitive](https://developer.hashicorp.com/terraform/language/values/outputs#sensitive-suppressing-values-in-cli-output)
+ in
+ configuration.
+* `value`: The value of the output.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/functions/append.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/functions/append.mdx
new file mode 100644
index 0000000000..81395154f6
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/functions/append.mdx
@@ -0,0 +1,46 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Append'
+sidebar_current: docs-funcs-append
+description: The built-in function `append` appends a value to the end of a list.
+layout: docs
+---
+
+# Builtin Function: append
+
+The built-in function `append` appends a value to the end of a list. The list
+is modified in-place. The return value will always be [undefined](/sentinel/language/undefined).
+
+`append` called on any value other than a list or undefined will result
+in an immediate error.
+
+Examples:
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+### Why does this function return undefined?
+
+This function returns `undefined` to avoid unintended side effects of the function in the context
+of a [rule](/sentinel/language/rules).
+
+For example, if `append` returned the list value as a result, the following policy would depend
+on the order in which rules are referenced:
+
+```sentinel
+list = []
+a = rule { append(list, 1) contains 1 }
+b = rule { append(list, 2) contains 2 }
+
+// Scenario 1: list becomes [1, 2]
+main = rule { a and b }
+
+// Scenario 2: list becomes [2, 1]
+main = rule { b and a }
+```
+
+This sort of side effect behavior introduces complex and difficult to track logical errors in programs.
+As a result, functions like this return `undefined` and modify values in-place.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/functions/compare.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/functions/compare.mdx
new file mode 100644
index 0000000000..ed55775454
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/functions/compare.mdx
@@ -0,0 +1,42 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Compare'
+sidebar_current: docs-funcs-compare
+description: The built-in function `compare` compares two values.
+layout: docs
+---
+
+# Builtin Function: compare
+
+**_compare(value1, value2)_**
+
+The built-in function `compare` compares two values. The only valid types that
+can be provided are integers, floats or strings. Strings are compared according
+to lexicographic ordering; which is comparable to alphabetical
+ordering, but also takes into account symbols.
+
+The following table provides an overview of the possible return values:
+
+| Result | Description |
+|--------|-------------|
+| -1 | `value1` is less than `value2` |
+| 0 | `value1` is equal to `value2` |
+| +1 | `value1` is greater than `value2` |
+
+## Examples
+
+```sentinel
+// ints
+compare(1, 4) // -1
+compare(1, 4) // 0
+compare(4, 1) // +1
+
+// floats
+compare(1.0, 4.0) // -1
+compare(1.0, 4.0) // 0
+compare(4.0, 1.0) // +1
+
+// strings
+compare("apple", "banana") // -1
+compare("apple", "apple") // 0
+compare("banana", "apple") // +1
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/functions/delete.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/functions/delete.mdx
new file mode 100644
index 0000000000..3b60803997
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/functions/delete.mdx
@@ -0,0 +1,25 @@
+---
+page_title: 'Sentinel Language - Builtin Function: delete'
+sidebar_current: docs-funcs-delete
+description: The built-in function `delete` deletes an element from a map.
+layout: docs
+---
+
+# Builtin Function: delete
+
+The built-in function `delete` deletes an element from a map.
+
+If the element to delete does not exist, the map is left unchanged.
+Calling `delete` on a value that is not a map or
+[undefined](/sentinel/language/undefined)
+is an immediate error. The result of `delete` is always `undefined`.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/functions/error.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/functions/error.mdx
new file mode 100644
index 0000000000..96d253b662
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/functions/error.mdx
@@ -0,0 +1,15 @@
+---
+page_title: 'Sentinel Language - Builtin Function: error'
+sidebar_current: docs-funcs-error
+description: >-
+ The built-in function `error` is used to exit immediately with an error
+ message.
+layout: docs
+---
+
+# Builtin Function: error
+
+The built-in function `error` is used to exit immediately with an error message.
+
+Please see the [page on errors](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/functions/index.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/functions/index.mdx
new file mode 100644
index 0000000000..4e12143ad0
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/functions/index.mdx
@@ -0,0 +1,13 @@
+---
+page_title: Sentinel Language - Builtin Functions
+sidebar_current: docs-funcs
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Language: Builtin Functions
+
+Builtin functions are
+[functions](/sentinel/language/functions)
+that are available in all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in function.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/functions/keys.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/functions/keys.mdx
new file mode 100644
index 0000000000..a96cafe897
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/functions/keys.mdx
@@ -0,0 +1,22 @@
+---
+page_title: 'Sentinel Language - Builtin Function: keys'
+sidebar_current: docs-funcs-keys
+description: The built-in function `keys` returns the keys of a map.
+layout: docs
+---
+
+# Builtin Function: keys
+
+The built-in function `keys` returns the keys of a map.
+
+`keys` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned keys are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/functions/length.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/functions/length.mdx
new file mode 100644
index 0000000000..8b874b14c4
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/functions/length.mdx
@@ -0,0 +1,23 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Length'
+sidebar_current: docs-funcs-length
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Builtin Function: length
+
+The built-in function `length` returns the length of a collection or string.
+
+The length of a string is the number of bytes in the string. It is not
+necessarilly the number of characters. The length of a collection is the
+number of elements in that collection. The length of
+[undefined](/sentinel/language/undefined) is `undefined`.
+
+Examples:
+
+```sentinel
+length("foo") // 3
+length([1, 2, 3, 4]) // 4
+length({ "key": "value" }) // 1
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/functions/print.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/functions/print.mdx
new file mode 100644
index 0000000000..bcf441d09d
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/functions/print.mdx
@@ -0,0 +1,13 @@
+---
+page_title: 'Sentinel Language - Builtin Function: print'
+sidebar_current: docs-funcs-print
+description: The built-in function `print` is used for logging and debugging.
+layout: docs
+---
+
+# Builtin Function: print
+
+The built-in function `print` is used for logging and debugging.
+
+Please see the [page on logging](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/functions/range.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/functions/range.mdx
new file mode 100644
index 0000000000..0f86bfc402
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/functions/range.mdx
@@ -0,0 +1,49 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Range'
+sidebar_current: docs-funcs-range
+description: The built-in function `range` returns a list of numbers in a range.
+layout: docs
+---
+
+# Builtin Function: range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it
+defaults to `1`.
+
+Negative steps are permitted and count down from `start` towards `end`, instead
+of up.
+
+The range ends when the next value given by the sum of the last value and `step`
+would exceed `end`, regardless of if it has been reached.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4] - starts at 1 instead of 0
+range(1, 5, 2) // [1,3] - 3+2 does not satisfy r[-1] < end
+range(0, -3, -1) // [0,-1,-2] - example of negative range
+```
+
+Impossible ranges, or ranges where `start` will never reach `end` given `step`,
+yield an empty list.
+
+```
+range(1, 1) // [] - already starting at 1
+range(0, 1, -1) // [] - negative step will never reach 1
+```
+
+Supplying an undefined value to any argument of `range` yields an undefined
+value back.
+
+A range with a zero step is a runtime error.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/functions/values.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/functions/values.mdx
new file mode 100644
index 0000000000..8f98d3ab56
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/functions/values.mdx
@@ -0,0 +1,22 @@
+---
+page_title: 'Sentinel Language - Builtin Function: values'
+sidebar_current: docs-funcs-values
+description: The built-in function `values` returns the values of a map.
+layout: docs
+---
+
+# Builtin Function: values
+
+The built-in function `values` returns the values of a map.
+
+`values` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned values are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+values(data) // [3, 2]
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/imports/base64.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/imports/base64.mdx
new file mode 100644
index 0000000000..4e58429268
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/imports/base64.mdx
@@ -0,0 +1,46 @@
+---
+page_title: 'Import: base64'
+sidebar_current: docs-imports-base64
+description: The base64 import enables a Sentinel policy to encode and decode Base64 values.
+layout: docs
+---
+
+# Import: base64
+
+The base64 import enables a Sentinel policy to encode and decode Base64 values.
+
+### base64.encode(str)
+
+Encodes the string `str` into a Base64 encoded string, using the standard
+encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-4).
+
+```sentinel
+enc = base64.encode("foo") // "Zm9v"
+```
+
+### base64.decode(str)
+
+Decodes the Base64 encoded string `str`, returning the string result,
+using the standard encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-4).
+
+```sentinel
+dec = base64.decode("Zm9v") // "foo"
+```
+
+### base64.urlencode(str)
+
+Encodes the string `str` into a Base64 encoded string, using the alternate
+encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-5).
+
+```sentinel
+enc = base64.urlencode("foo") // "Zm9v"
+```
+
+### base64.urldecode(str)
+
+Decodes the Base64 encoded string `str`, returning the string result, using the
+alternate encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-5).
+
+```sentinel
+dec = base64.urldecode("Zm9v") // "foo"
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/imports/collection/index.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/imports/collection/index.mdx
new file mode 100644
index 0000000000..ae6db20bbb
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/imports/collection/index.mdx
@@ -0,0 +1,207 @@
+---
+page_title: 'Import: collection'
+sidebar_current: docs-imports-collection
+description: The collection import provides useful helpers for working with maps and lists.
+layout: docs
+---
+
+# Import: collection
+
+The `collection` import provides helpers for working with [maps](/sentinel/language/maps) and [lists](/sentinel/language/lists).
+
+## filter
+
+**_filter(items, predicate)_**
+
+Calls [predicate](#predicates) for each element in collection, returning a list of elements that the
+predicate __does__ return true for.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| items | the list or map to perform the filter against. |
+| predicate | a [predicate](#predicates) function to call for each element in the collection. A response of `true` will add the element to the result list. |
+
+### Examples
+
+Return all even numbers:
+
+```sentinel playground
+import "collection"
+
+items = [2, 3, 4, 5, 6, 7, 8]
+result = collection.filter(items, func(el) {
+ return el % 2 is 0
+})
+main = result is [2, 4, 6, 8]
+```
+
+## find
+
+**_find(items, predicate)_**
+
+Find and return an element within a collection according to the provided [predicate](#predicates). If nothing is found, returns [undefined](/sentinel/language/undefined).
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| items | the list or map to perform the find against. |
+| predicate | a [predicate](#predicates) function to call for each element in the collection. A response of `true` will return the element and complete the find. |
+
+### Examples
+
+Find an element in a collection based on its value:
+
+```sentinel playground
+import "collection"
+
+items = ["foo", "bar", "qux"]
+item = collection.find(items, func(el) {
+ return el is "bar"
+})
+main = item is "bar"
+```
+
+Find an element in a collection based on its key:
+
+```sentinel playground
+import "collection"
+
+items = {"foo": 2, "bar": 4}
+item = collection.find(items, func(el, key) {
+ return key is "bar"
+})
+main = item is 4
+```
+
+## matches
+
+**_matches(items, partial)_**
+
+Compare each element in a collection against a partial map using deep comparison. Returns
+the list of elements that returned true for the partial comparison. If no matches are found, returns an empty list.
+
+### Arguments
+
+| Name | Description |
+|---------|-------------|
+| items | the list or map to perform the match against. |
+| partial | the map used for partial deep comparison against each element in the collection. |
+
+### Examples
+
+Return all items that contain `{"foo": {"bar": "wip"}}`:
+
+```sentinel playground
+import "collection"
+
+items = [
+ # This item should match
+ {
+ "foo": { "bar": "wip"},
+ "baz": "qux",
+ },
+ # This item will not match
+ {
+ "foo": "bar",
+ "baz": "bar",
+ },
+]
+result = collection.matches(items, {"foo": {"bar": "wip"}})
+main = result is [{"foo": {"bar": "wip"}, "baz": "qux"}]
+```
+
+## reduce
+
+**_reduce(items, accumulator[, initial])_**
+
+Call an accumulator function for each element in collection, supplying the
+previous accumulated value as the accumulation parameter.
+
+### Arguments
+
+| Name | Description |
+|-------------|-------------|
+| items | the list or map to perform the reduce against. |
+| accumulator | a function that is called for each element in the collection, used to accumulate the value. The first argument is the current accumulated value, the remaining arguments use the same rules as [predicate](#predicates) functions. |
+| initial | the initial value to use. It is an optional argument, and if not provided the first element in the collection is used as the initial value. |
+
+### Examples
+
+Reduce the collection by adding each element together:
+
+```sentinel playground
+import "collection"
+
+items = [1, 2, 3, 4, 5]
+result = collection.reduce(items, func(acc, el) {
+ return acc + el
+}, 0)
+main = result is 15
+```
+
+## reject
+
+**_reject(items, predicate)_**
+
+Calls [predicate](#predicates) for each element in collection, returning a list of elements that the
+predicate __does not__ return true for.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| items | the list or map to perform the reject against. |
+| predicate | a [predicate](#predicates) function to call for each element in the collection. A response of `false` will add the element to the result list. |
+
+### Examples
+
+Return all odd numbers by rejecting even ones:
+
+```sentinel playground
+import "collection"
+
+items = [2, 3, 4, 5, 6, 7, 8]
+result = collection.reject(items, func(el) {
+ return el % 2 is 0
+})
+main = result is [3, 5, 7]
+```
+
+## Predicates
+
+Some helpers accept a predicate function, which has the purpose of making an
+assertion. Each predicate may accept differing arguments and return differing
+types. If the collection is a list, the parameters will be the element and index.
+If the collection is a Map, the parameters will be the value and the key.
+
+### Examples
+
+A predicate for a list of items:
+
+```sentinel
+// including index
+func(item, index) {
+ return true
+}
+
+// excluding index
+func(item) {
+ return true
+}
+```
+
+A predicate for a map of items:
+```sentinel
+// including key
+func(value, key) {
+ return true
+}
+
+// excluding key
+func(value) {
+ return true
+}
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/imports/collection/lists.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/imports/collection/lists.mdx
new file mode 100644
index 0000000000..cf258a3956
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/imports/collection/lists.mdx
@@ -0,0 +1,211 @@
+---
+page_title: 'Import: collection/lists'
+sidebar_current: docs-imports-collection-lists
+description: The collection/lists import provides useful helpers for working with lists.
+layout: docs
+---
+
+# Import: collection/lists
+
+The `collection/lists` import provides helpers for working with [lists](/sentinel/language/lists).
+
+## concat
+
+**_concat(items, others[, ...additional])_**
+
+Join multiple lists together, returning the resulting list. This helper must
+have at least two arguments supplied. Order is important,
+as the order of arguments is the order that lists will be appended.
+
+### Arguments
+
+| Name | Description |
+|------------|-------------|
+| items | the first list to add to the resulting value |
+| others | the second list to add to the resulting value |
+| additional | any number of additional lists to add to the resulting value |
+
+### Examples
+
+Concatenate two lists:
+
+```sentinel playground
+import "collection/lists" as lists
+
+result = lists.concat([1], [2, 3])
+main = result is [1, 2, 3]
+```
+
+Concatenate many lists:
+```sentinel playground
+import "collection/lists" as lists
+
+result = lists.concat([1, 2], [10, 20], [100, 200])
+main = result is [1, 2, 10, 20, 100, 200]
+```
+
+## difference
+
+**_difference(items[, ...additional])_**
+
+Difference returns a new list with all values that exist in the first list
+that are not present in all remaining provided lists.
+
+### Arguments
+
+| Name | Description |
+|------------|-------------|
+| items | the first list, used as the source for ordering |
+| additional | any number of additional lists used to subtract from the source list |
+
+### Aliases
+
+__diff__
+
+### Examples
+
+Difference between two lists:
+
+```sentinel playground
+import "collection/lists" as lists
+
+result = lists.difference([1, 2], [2, 3])
+main = result is [1]
+```
+
+Difference between many lists:
+```sentinel playground
+import "collection/lists" as lists
+
+result = lists.difference([1, 2, 3, 4, 5], [2, 5], [1])
+main = result is [3, 4]
+```
+
+## intersection
+
+**_intersection(items[, ...additional])_**
+
+Intersection returns a list consisting of unique values that are present in all
+of the provided lists.
+
+### Arguments
+
+| Name | Description |
+|------------|-------------|
+| items | the first list to use for intersection |
+| additional | any number of additional lists used to intersect |
+
+### Examples
+
+Intersection between three lists:
+
+```sentinel playground
+import "collection/lists" as lists
+
+result = lists.intersection([1, 2], [2, 3], [3, 2])
+main = result is [2]
+```
+
+## sort
+
+**_sort(items, sort_function)_**
+
+Sort will sort a list of elements according to the provided sort function, returning
+a new, sorted list.
+
+### Arguments
+
+| Name | Description |
+|------|-------------|
+| items | the list to sort |
+| sort_function | the function that is used to perform the sort |
+
+### Sort Function
+
+The provided sort function accepts two arguments, which can be considered as the
+`current` and `next` item. The function should return one of the following values:
+
+| Result | Description |
+|--------|-------------|
+| -1 | `current` is less than `next` |
+| 0 | `current` is equal to `next` |
+| +1 | `current` is greater than `next` |
+
+The [compare](/sentinel/functions/compare) built-in method provides a way of
+performing comparisons against integers, floats or strings to return a supported
+value.
+
+### Examples
+
+Sort a list of words:
+
+```sentinel playground
+import "collection/lists" as lists
+
+items = ["zebra", "bat", "horse"]
+result = lists.sort(items, func(x, y) {
+ return compare(x, y)
+})
+main = result is ["bat", "horse", "zebra"]
+```
+
+Sort a list of objects by a key:
+
+```sentinel playground
+import "collection/lists" as lists
+
+items = [{"foo": 8}, {"foo": 4}, {"foo": 100}]
+result = lists.sort(items, func(x, y) {
+ return compare(x.foo, y.foo)
+})
+main = result is [{"foo": 4}, {"foo": 8}, {"foo": 100}]
+```
+
+## sum
+
+**_sum(items)_**
+
+Sum will add all elements within the provided list. The
+list must only contain integers or floats. The return value will always be a float.
+
+### Arguments
+
+| Name | Description |
+|------|-------------|
+| items | the list to sum |
+
+### Examples
+
+Perform a sum on a list of numbers:
+
+```sentinel playground
+import "collection/lists" as lists
+
+items = [2, 5, 10, 500]
+main = lists.sum(items) is 517
+```
+
+## union
+
+**_union(items[, ...additional])_**
+
+Union will return a list that contains unique values from all provided lists.
+
+### Arguments
+
+| Name | Description |
+|------------|-------------|
+| items | the first list of values |
+| additional | any number of additional lists of values |
+
+### Examples
+
+Perform a union on multiple lists:
+
+```sentinel playground
+import "collection/lists" as lists
+
+items = lists.union([1, 3], [2, 3, 5], [6, 2, 1])
+
+main = items is [1, 3, 2, 5, 6]
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/imports/collection/maps.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/imports/collection/maps.mdx
new file mode 100644
index 0000000000..d96bb15639
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/imports/collection/maps.mdx
@@ -0,0 +1,360 @@
+---
+page_title: 'Import: collection/maps'
+sidebar_current: docs-imports-collection-maps
+description: The collection/maps import provides useful helpers for working with maps.
+layout: docs
+---
+
+# Import: collection/maps
+
+The `collection/maps` import provides helpers for working with [maps](/sentinel/language/maps).
+
+## get
+
+**_get(object, path[, default])_**
+
+Get the value from the provided object using the [path](#paths).
+When the path is invalid or the object doesn't contain a value at the path, then the default is returned. The default return value is undefined.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the get against. |
+| path | a [path](#paths) to a key within object to retreive the value |
+| default | an optional value that will return if the path does not exist or returns undefined |
+
+### Examples
+
+Get a value from a simple object:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+item = maps.get(object, "foo")
+main = item is "bar"
+```
+
+Get a nested value from a complex object:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [{"bar": {"baz": 4}}]}
+item = maps.get(object, "foo.0.bar.baz")
+main = item is 4
+```
+
+Get a list of values using [splat](#splat):
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [{"bar": 4}, {"bar": 8}, {"bar": 45}]}
+item = maps.get(object, "foo.*.bar")
+main = item is [4, 8, 45]
+```
+
+Get an invalid path, providing a default:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {}
+item = maps.get(object, "foo", "bar")
+main = item is "bar"
+```
+
+Get an invalid path, not providing a default:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {}
+item = maps.get(object, "foo")
+main = item is not defined
+```
+
+## has
+
+**_has(object, path)_**
+
+Return a boolean value if the object has the path within its structure.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the has against. |
+| path | a [path](#paths) to a key within object to determine if it exists |
+
+### Examples
+
+An object has a valid key:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+main = maps.has(object, "foo")
+```
+
+An object does not have a valid key:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {}
+main = maps.has(object, "foo") is false
+```
+
+## omit
+
+**_omit(object, paths)_**
+
+Return a map, removing each path from the paths provided from the source object.
+The original structure of the map is maintained, as seen in the below examples.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the omit against. |
+| paths | a list of [paths](#paths) to be removed from the source object |
+
+### Examples
+
+Omitting a single value:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+main = maps.omit(object, ["foo"]) is {}
+```
+
+Omitting multiple values:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar", "baz": "qux", "nit": "nat"}
+main = maps.omit(object, ["foo", "baz"]) is {"nit": "nat"}
+```
+
+Omitting deep values:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {
+ "foo": "bar",
+ "baz": [
+ { "qux": 4 },
+ { "qux": 10 }
+ ],
+ "nit": {
+ "nat": "pak"
+ }
+}
+main = maps.omit(object, ["baz.1.qux", "nit.nat"]) is {"foo": "bar", "baz": [{"qux": 4}]}
+```
+
+## pick
+
+**_pick(object, paths)_**
+
+Return a map consisting of each value found in object from the paths provided.
+The original structure of the map is maintained, as seen in the below examples.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the pick against. |
+| paths | a list of [paths](#paths), each used to select a value from the object. |
+
+### Examples
+
+Picking a single value:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+main = maps.pick(object, ["foo"]) is {"foo": "bar"}
+```
+
+Picking multiple values:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar", "baz": "qux", "nit": "nat"}
+main = maps.pick(object, ["foo", "baz"]) is {"foo": "bar", "baz": "qux"}
+```
+
+Picking deep values:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {
+ "foo": "bar",
+ "baz": [
+ { "qux": 4 },
+ { "qux": 10 }
+ ],
+ "nit": {
+ "nat": "pak"
+ }
+}
+main = maps.pick(object, ["baz.1.qux", "nit.nat"]) is {"baz": [{"qux": 10}], "nit": {"nat": "pak"}}
+```
+
+## set
+
+**_set(object, path, value)_**
+
+Return a new map, assigning the provided value to the provided path. It will
+not modify the provided map in place.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the set against. |
+| path | the [path](#paths) to assign value |
+| value | the value to be assigned |
+
+### Examples
+
+Set a value on a simple path:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+object = maps.set(object, "foo", "qux")
+main = object is {"foo": "qux"}
+```
+
+Set a value on a deep path:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {}
+object = maps.set(object, "foo.bar.baz", 10)
+main = object is {"foo": { "bar": { "baz": 10 } } }
+```
+
+Set a value on an index in a list:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [ 2, 5 ] }
+object = maps.set(object, "foo.0", 10)
+main = object is {"foo": [ 10, 5 ] }
+```
+
+Set a value on every key within a list key:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [ { "bar": true }, { "bar": false } ] }
+object = maps.set(object, "foo.*.bar", true)
+main = object is {"foo": [ { "bar": true }, { "bar": true } ] }
+```
+
+## unset
+
+**_unset(object, path)_**
+
+Return a new map, removing the provided path from the source object. It will
+not modify the provided map in place.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the unset against. |
+| path | the [path](#paths) to remove |
+
+### Examples
+
+Unset on a simple path:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+object = maps.unset(object, "foo")
+main = object is {}
+```
+
+Unset on a deep path:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": { "bar": { "baz": 10, "qux": 15 } } }
+object = maps.unset(object, "foo.bar.baz")
+main = object is {"foo": { "bar": { "qux": 15 } } }
+```
+
+Unset an index in a list:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [ 2, 5 ] }
+object = maps.unset(object, "foo.0")
+main = object is {"foo": [ 5 ] }
+```
+
+Unset a list:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [ { "bar": true }, { "bar": false } ] }
+object = maps.unset(object, "foo.*", true)
+main = object is {"foo": [] }
+```
+
+## Paths
+
+Map helpers often receive a **path** argument that allows for looking up a nested
+key. Generally speaking, a path is a series of keys separated by `.`, however there
+are some additional capabilites that need to be explained.
+
+### Lists
+
+When traversing a nested map that contains a list, a specific index can be retrieved
+by providing the index as the part of the path.
+
+In the following code sample, the path will first enter the key `"foo"` within the
+map, followed by entering the first index of the list.
+
+```sentinel
+path = "foo.0"
+object = {"foo": [1]}
+```
+
+### Splat
+
+To provide advanced capabilities when using paths, you can also use the splat (`*`)
+operator to iterate through **all** elements in a list, with all parts of the path
+following the splat occuring on each entry.
+
+In the following code sample, the path will first enter the key `"foo"` within the map.
+It will then enter each item in the list, entering the `"bar"` key for each nested object.
+
+```sentinel
+path = "foo.*.bar"
+object = {"foo": [{"bar": 1}, {"bar": 2}]}
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/imports/decimal.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/imports/decimal.mdx
new file mode 100644
index 0000000000..a6633a38e0
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/imports/decimal.mdx
@@ -0,0 +1,318 @@
+---
+page_title: 'Import: decimal'
+sidebar_current: docs-imports-decimal
+description: |-
+ The decimal import provides functions for operating on numbers represented
+ as decimals.
+layout: docs
+---
+
+# Import: decimal
+
+The decimal import provides functions for operating on numbers represented
+as decimals.
+
+Binary floating-point numbers provided by the `float` type are unable to
+fully represent numbers like `1.1` and `2.2`. The decimal import can
+represent these numbers exactly, and so should be used when exactness is
+required.
+
+Decimals are created through the `new` function, which takes any type that
+can represent a number. Arguments to the decimal operators can also take
+a value of any of these types. The full list is:
+
+- float
+- int
+- string
+- existing decimal value
+
+### decimal.infinity(sign)
+
+Creates an infinite-value decimal. Infinite-value decimals are always larger
+or smaller, depending on sign, than any non-infinite decimals.
+
+```sentinel
+decimal.infinity(1) // +Infinity
+decimal.infinity(-1) // -Infinity
+```
+
+Also available as `decimal.inf`.
+
+### decimal.nan
+
+A decimal representing a "not-a-number" value. NaNs are not equal to any
+other number, even themselves.
+
+```sentinel
+decimal.new(-1).sqrt() // NaN
+decimal.new(0).mul(decimal.inf(1)) // NaN
+decimal.nan.is(decimal.nan) // false
+```
+
+### decimal.is_infinite(d)
+
+Evaluates to true if the decimal is infinite.
+
+```sentinel
+decimal.is_infinite(100) // false
+decimal.is_infinite("Infinity") // true
+```
+
+Also available as `decimal.is_inf`, `decimal.isinf` and `decimal.isinfinite`.
+
+### decimal.is_nan(d)
+
+Evaluates to true if the decimal is not-a-number.
+
+```sentinel
+decimal.is_nan("NaN") // true
+```
+
+Also available as `decimal.isnan`.
+
+### decimal.new(v)
+
+Constructs a decimal from another value.
+
+```sentinel
+decimal.new("1.1") // Constructs a decimal from a string.
+decimal.new(2.2) // Constructs a decimal from a float.
+decimal.new(3) // Constructs a decimal from an integer.
+decimal.new("1.1234E+400") // Constructs a decimal from a string using E-notation.
+```
+
+## Type: number
+
+Numbers are represented internally by a sign, coefficient, and exponent.
+The value is calculated with the formula `sign * coefficient * 10^exponent`.
+Exponent is limited to 32 bits, and the coefficient is limited by the total
+precision.
+
+The maximum precision a number can represent is 100 digits. This includes
+both before and after the decimal place.
+
+### number.string
+
+A string representation of the decimal.
+
+```sentinel
+decimal.new(1.5).string // 1.5
+```
+
+### number.sign
+
+A number between `-1` and `+1` representing the sign of the decimal.
+
+```sentinel
+decimal.new(1.5).sign // 1
+```
+
+### number.coefficient
+
+The coefficient component of the decimal.
+
+```sentinel
+decimal.new(1.5).coefficient // 15
+```
+
+### number.exponent
+
+The exponent component of the decimal.
+
+```sentinel
+decimal.new(1.5).exponent // -1
+```
+
+### number.float
+
+A floating-point representation of the decimal.
+
+```sentinel
+decimal.new(1.5).float // 1.500000
+```
+
+### number.int
+
+An integer representation of the decimal. This always rounds down to the nearest integer.
+
+```sentinel
+decimal.new(1.5).int // 1
+```
+
+### number.is(v)
+
+Test for equality with another value.
+
+```sentinel
+decimal.new(1.5).is(1) // false
+```
+
+### number.is_not(v)
+
+Test for inequality with another value.
+
+```sentinel
+decimal.new(1.5).is_not(1) // true
+```
+
+### number.less_than(v)
+
+Test that the decimal is less than another value.
+Can also be called as `number.lt(v)`
+
+```sentinel
+decimal.new(1.5).less_than(1) // false
+```
+
+### number.less_than_or_equals(v)
+
+Test that the decimal is less than or equals to another value.
+Can also be called as `number.lte(v)`
+
+```sentinel
+decimal.new(1.5).less_than_or_equals(1) // false
+```
+
+### number.greater_than(v)
+
+Test that the decimal is greater than another value.
+Can also be called as `number.gt(v)`
+
+```sentinel
+decimal.new(1.5).greater_than(1) // true
+```
+
+### number.greater_than_or_equals(v)
+
+Test that the decimal is greater than or equals to another value.
+Can also be called as `number.gte(v)`
+
+```sentinel
+decimal.new(1.5).greater_than_or_equals(1) // true
+```
+
+### number.add(v)
+
+Add another number to the decimal.
+
+```sentinel
+decimal.new(1.5).add(1) // 2.5
+```
+
+### number.subtract(v)
+
+Subtract another number from the decimal.
+Can also be called as `number.sub(v)`
+
+```sentinel
+decimal.new(1.5).subtract(1) // 0.5
+```
+
+### number.multiply(v)
+
+Multiply the decimal by a number.
+Can also be called as `number.mul(v)`
+
+```sentinel
+decimal.new(1.5).multiply(2) // 3.0
+```
+
+### number.divide(v)
+
+Divide the decimal by a number.
+Can also be called as `number.div(v)`
+
+```sentinel
+decimal.new(3.0).divide(1.5) // 2
+```
+
+### number.modulo(v)
+
+Find the remainder after dividing the decimal by a number.
+Can also be called as `mod`, `remainder`, or `rem`.
+
+```sentinel
+decimal.new(1.5).modulo(1) // 0.5
+```
+
+### number.power(v)
+
+Raise the decimal to a power.
+Can also be called as `number.pow(v)`
+
+```sentinel
+decimal.new(1.5).power(2) // 2.25
+```
+
+### number.exp()
+
+The natural exponent of the decimal. This calculates `e^number`.
+
+```sentinel
+decimal.new(1.5).exp() // 4.4816...
+```
+
+### number.loge()
+
+The natural logarithm of the decimal.
+Can also be called as `number.ln()`
+
+```sentinel
+decimal.new(1.5).loge() // 0.4054...
+```
+
+### number.log10()
+
+The base-10 logarithm of the decimal.
+Can also be called as `number.log()`
+
+```sentinel
+decimal.new(1.5).log10() // 0.1760...
+```
+
+### number.square_root()
+
+The square root of the decimal.
+Can also be called as `number.sqrt()`
+
+```sentinel
+decimal.new(1.5).square_root() // 1.2247...
+```
+
+### number.ceiling()
+
+Round the decimal to the smallest integer greater or equal to itself.
+Can also be called as `number.ceil()`
+
+```sentinel
+decimal.new(1.5).ceiling() // 2
+```
+
+### number.floor()
+
+Round the decimal to the largest integer less than or equal to itself.
+
+```sentinel
+decimal.new(1.5).floor() // 1
+```
+
+### number.absolute()
+
+The absolute value of the decimal.
+Can also be called as `number.abs()`
+
+```sentinel
+decimal.new(1.5).absolute() // 1.5
+decimal.new(-1.5).absolute() // 1.5
+```
+
+### number.negate()
+
+The negated value of the decimal. A positive decimal will become negative,
+and a negative decimal will become positive.
+Can also be called as `number.neg()`
+
+```sentinel
+decimal.new(1.5).negate() // -1.5
+decimal.new(-1.5).negate() // 1.5
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/imports/http.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/imports/http.mdx
new file mode 100644
index 0000000000..f94ae98c27
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/imports/http.mdx
@@ -0,0 +1,370 @@
+---
+page_title: 'Import: http'
+sidebar_current: docs-imports-http
+description: The http import enables the use of HTTP-accessible data from outside the runtime in Sentinel policy rules.
+layout: docs
+---
+
+# Import: http
+
+The http import enables the use of HTTP-accessible data from outside
+the runtime in Sentinel policy rules.
+
+The simplest example of the import in use would be:
+
+```sentinel
+import "http"
+
+resp = http.get("https://example.hashicorp.com")
+main = rule { resp.body contains "something" }
+```
+
+By default, any request response that is not a successful "200 OK" HTTP
+response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information and
+how to customize this behavior.
+
+For more advanced requests which require setting request headers, use a
+[`request` type](#type-request):
+
+```sentinel
+import "http"
+import "json"
+
+param token
+
+req = http.request("https://example.hashicorp.com").with_header("Authorization", "token "+token)
+resp = json.unmarshal(http.get(req).body)
+main = rule { resp["some_key"] is true }
+```
+
+The `with_header` function above returns the `request` object with the
+`Authorization` HTTP header set to value of the variable `some_value`. Many
+of the methods within the http import API are designed around this concept
+of method chaining, allowing for complex building of HTTP requests
+encapsulated in functions. The following is an idiomatic example further
+demonstrating this pattern:
+
+```sentinel
+import "http"
+import "json"
+
+param github_user
+param github_token
+
+client = func(req) {
+ return http.with_timeout(5).with_retries(3)
+}
+
+github_request = func(path) {
+ full_url = "https://api.github.com" + path
+ return http.request(full_url).
+ with_basic_auth(github_user, github_token).
+ with_header("Accept", "application/vnd.github.v3+json").
+ with_header("Custom", "foo"),
+}
+
+default_response = client.get(github_request("/example"))
+
+# Override the Custom header for this single request
+custom_header_response = json.unmarshal(
+ client.get(
+ github_request("/example").with_header("Custom", "bar"),
+ ),
+)
+
+# Override the timeout value for a known slower request
+slow_response = json.unmarshal(
+ client.with_timeout(15).get(github_request("/slow-endpoint")),
+)
+
+some_key_is_true = rule { json.unmarshal(default_response.body)["some_key"] is true }
+body_contains_text = rule { custom_header_response.body contains "something" }
+response_is_ok = rule { slow_response.status_code is 200 }
+
+main = rule { some_key_is_true and body_contains_text and response_is_ok }
+```
+
+### http.client
+
+Retrieve the base HTTP client with default settings. The returned client may be
+configured by calling configuration functions on it; all of these configuration
+functions on are also aliased as top-level functions in this namespace. See
+[`client`](#type-client) below.
+
+### http.request(url)
+
+Create a new [`request`](#type-request) to the specified `url` (string). A
+`request` type enables customization of headers and other concerns before then
+providing the constructed `request` to [`get()`](#client-get-url_or_request),
+[`post()`](#client-post-url_or_request) or [`send()`](#client-send-request).
+
+```sentinel
+req = http.request("example.hashicorp.com/foo").with_header("Foo", "bar")
+resp = http.get(req)
+```
+
+### http.methodPost
+
+Returns a string containing the accepted verb for POST requests, `"POST"`.
+
+### http.methodGet
+
+Returns a string containing the accepted verb for GET requests, `"GET"`.
+
+## Type: client
+
+All of the following configuration functions on the default client are also
+aliased as top-level functions in the import. This allows a short-hand (and
+idiomatic) way of configuring a new client without having to directly access
+`http.client` each time:
+
+```sentinel
+# The following two lines are semantically the same:
+resp = http.client.with_timeout(5).get(url)
+resp = http.with_timeout(5).get(url)
+```
+
+### url_or_request
+
+The [`get()`](#client-get-url_or_request) and [`post()`](#client-post-url_or_request)
+functions accept either a URL string or a request object, noted as
+`url_or_request` throughout the documentation.
+
+When `url_or_request` is a string, a request is made with the URL
+specified by the string. To customize HTTP headers and other settings, pass
+a request. By default, a request has a timeout value of 10 seconds and 1
+retry. These settings can be adjusted with [`with_timeout()`](#clientwith_timeoutinteger) and
+[`with_retries()`](#clientwith_retriesinteger), respectively.
+
+### Redirects
+
+If the response is one of the following redirect codes, the client follows the
+redirect, up to a maximum of 10 redirects:
+
+- 301 (Moved Permanently)
+- 302 (Found)
+- 303 (See Other)
+- 307 (Temporary Redirect)
+- 308 (Permanent Redirect)
+
+If the redirect limit is exceeded, the result is a policy error.
+
+By default, after any redirects are followed (up to the maximum), any request
+response that is not a successful "200 OK" response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information
+and how to customize this behavior.
+
+### client.get(url_or_request)
+
+Issue a GET request with a URL string or a `request` type. Returns a `response`
+type.
+
+```sentinel
+import "http"
+
+resp = http.get("https://example.hashicorp.com")
+main = rule { resp.body contains "something" }
+```
+
+### client.post(url_or_request)
+
+Issue a POST request with a URL string or a `request` type. Returns a `response`
+type.
+
+```sentinel
+import "http"
+
+req = http.request("https://example.hashicorp.com")
+ .with_body(json.marshal({"foo": "bar"}))
+resp = http.post(req)
+main = rule { resp.body contains "something" }
+```
+
+### client.send(request)
+
+Make a request using the supplied `request` type. Returns a `response`.
+
+```sentinel
+import "http"
+
+req = http.request("https://example.hashicorp.com")
+resp = http.send(req)
+main = rule { resp.body contains "something" }
+```
+
+### client.accept_status_codes(list)
+
+Configures a client to not automatically cause a policy failure if
+the resulting response's status code is in `list`. Any status code received
+that is not present in this list will trigger a runtime error.
+
+By default, any request response that is not a successful "200 OK" response
+causes a policy error. This is for convenience, as the most common scenario
+by far is to receive a response and immediately signal a policy error if the
+status code is not 200. Use this scoping to specify a list of non-redirect
+HTTP codes that you wish to accept without error and evaluate the response
+yourself.
+
+Redirects are not considered final status codes in this context. That is,
+redirects are always followed up to the maximum allowed value (see [`Redirects`](#redirects))
+and the final response code in the redirect chain is validated against
+`list`.
+
+```sentinel
+resp = http.accept_status_codes([200, 404]).get(url)
+main = rule { resp.status_code is 404 }
+```
+
+### client.accepted_status_codes
+
+Returns a list of the client's accepted status codes.
+
+```sentinel
+client = http.accept_status_codes([200, 404])
+main = rule { client.accepted_status_codes contains 404 }
+```
+
+### client.with_retries(integer)
+
+Sets the maximum number of retries, specified by `integer`, that should be
+attempted on an unsuccessful request. An unsuccessful request is considered
+to be any 5xx response except `501 (Not Implemented)` or a request timeout.
+By default, one retry is attempted.
+
+The maximum number of retries is 10, for a total of 11 request attempts.
+When a request exceeds the maximum number of retries without a response, the
+result is a policy error. Specifying a number larger than this will result
+in a runtime error.
+
+Between each retry, an exponentially increasing delay is observed, allowing
+time for the server to recover. This delay starts at 1 second for the first
+retry and increases each attempt thereafter. The maximum delay for a single
+attempt is 30 seconds.
+
+Retries can be disabled by specifying 0.
+
+### client.retries
+
+Returns the client's configured number of retries as an integer.
+
+### client.with_timeout(integer)
+
+Sets the request timeout to the number of seconds indicated by `integer`.
+
+Each individual request performed by the HTTP client will be limited by the
+given request timeout. This timeout includes connection time, any redirects,
+and reading the response body.
+
+A maximum of 30 seconds is allowed. Specifying a number larger than this
+will result in a runtime error.
+
+By default, requests will time out after 10 seconds.
+
+### client.timeout
+
+Returns the client's configured timeout in whole seconds as an integer.
+
+### client.without_certificate_verification()
+
+Skips verification of TLS certificates during secure HTTP operations,
+allowing for requests to `https://` endpoints which are secured with a TLS
+certificate signed by an untrusted certificate authority. Takes no arguments.
+
+By default, the HTTP client will trigger a runtime error if a TLS certificate
+presented by a server is from an untrusted certificate authority.
+
+Do not change this setting unless you are aware of the risks involved.
+Without verification, TLS accepts any certificate presented by the server
+and any host name in that certificate. In this mode, TLS is susceptible to
+man-in-the-middle attacks. This option should only be used for testing or in
+trusted networks with self-signed certificates.
+
+```sentinel
+http.without_certificate_verification().get(url)
+```
+
+### client.certificate_verification
+
+Returns true if the client requires TLS certificates to be verifiable.
+
+## Type: request
+
+### request.url
+
+Returns the request URL as a string.
+
+### request.headers
+
+Returns the configured request headers as a string-keyed map of strings.
+
+### request.body
+
+Returns the configured body as a string.
+
+### request.with_header(key, value)
+
+Returns a `request` with the header `key` (string) set to `value` (string).
+
+Values are overridden on subsequent calls to the same `key`. To specify
+multiple values, use the existing value when setting the new one.
+
+```sentinel
+original = http.request("http://example.hashicorp.com").with_header("Foo", "bar")
+overridden = original.with_header("Foo", "baz")
+multi_value = original.with_header("Foo", original.headers["Foo"] + ",baz")
+
+main = rule {
+ original.headers["Foo"] is "bar" and
+ overridden.headers["Foo"] is "baz" and
+ multi_value.headers["Foo"] is "bar,baz"
+}
+```
+
+### request.with_basic_auth(username, password)
+
+Returns a `request` with the `Authorization` header set to use HTTP Basic
+Authentication with the provided username and password.
+
+Note that with HTTP Basic Authentication the provided username and password
+are encoded, but not encrypted.
+
+### request.with_body(body)
+
+Returns a `request` with the `body` set. This value will then be sent as a body
+as part of the request. Commonly used along with the [`post()`](#client-post-url_or_request) function.
+
+```sentinel
+req = http.request("http://example.hashicorp.com")
+ .with_header("Content-Type", "application/json")
+ .with_body(json.marshal({ "foo": "bar" }))
+
+resp = http.post(req)
+```
+
+## Type: response
+
+### response.status_code
+
+The response status code as an integer, e.g. `200`.
+
+### response.headers
+
+The response headers as a map.
+
+### response.body
+
+The response body as a string. Callers should unmarshal the content to a useful
+representation as necessary based on the content type. For example, if the
+response is in JSON:
+
+```sentinel
+import "http"
+import "json"
+
+# This example endpoint returns {"some_key": true}
+req = http.request("https://example.hashicorp.com/some-json")
+
+resp = json.unmarshal(http.get(req).body)
+main = rule { keys(resp) contains "some_key" and resp["some_key"] is true }
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/imports/index.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/imports/index.mdx
new file mode 100644
index 0000000000..e45bf81b81
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/imports/index.mdx
@@ -0,0 +1,18 @@
+---
+page_title: Sentinel Language - Standard Imports
+sidebar_current: docs-imports
+description: >-
+ Standard Imports are the built-in imports that are available to all Sentinel
+ policies.
+layout: docs
+---
+
+# Standard Imports
+
+Standard Imports are the built-in [imports](/sentinel/language/imports)
+that are available to all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in import.
+
+Sentinel-embedded applications can choose to allow or deny certain
+standard imports. Please reference the documentation for the Sentinel-enabled
+application you're using to determine if all standard imports are available.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/imports/json.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/imports/json.mdx
new file mode 100644
index 0000000000..b664592cba
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/imports/json.mdx
@@ -0,0 +1,36 @@
+---
+page_title: 'Import: json'
+sidebar_current: docs-imports-json
+description: The json import enables a Sentinel policy to parse and access a JSON document.
+layout: docs
+---
+
+# Import: json
+
+The json import enables a Sentinel policy to parse and access a JSON
+document.
+
+### json.unmarshal(obj)
+
+Unmarshals the JSON object `obj` into a native Sentinel structure.
+
+All native JSON types can be represented perfectly as Sentinel native types.
+
+```sentinel
+// Typically the input for this would come from an external source.
+config = json.unmarshal("{ \"foo\": 42 }")
+config.foo // 42
+config.bar // undefined (as usual for accessing a non-existent map key)
+```
+
+### json.marshal(obj)
+
+Marshals the Sentinel object `obj` into a JSON object encoded as a string.
+
+Sentinel's functions cannot be natively encoded, and will cause an error if
+it is present within the provided object.
+
+Sentinel's `undefined` cannot be natively encoded as JSON. If `undefined`
+is supplied directly to `marshal`, it will return `undefined`. If `undefined`
+is present within any map or list within the provided object, the associated
+element will be removed prior to performing the JSON encoding.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/imports/runtime.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/imports/runtime.mdx
new file mode 100644
index 0000000000..6493bbbb22
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/imports/runtime.mdx
@@ -0,0 +1,16 @@
+---
+page_title: 'Import: runtime'
+sidebar_current: docs-imports-runtime
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: runtime
+
+The runtime import contains various information about the Sentinel runtime. It
+can be used to get information about the runtime as it's embedded in the CLI or
+a specific integration, such as validating a specific runtime version.
+
+### runtime.version
+
+Returns the version of Sentinel within this implementation.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/imports/sockaddr.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/imports/sockaddr.mdx
new file mode 100644
index 0000000000..a6842ba5f0
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/imports/sockaddr.mdx
@@ -0,0 +1,41 @@
+---
+page_title: 'Import: sockaddr'
+sidebar_current: docs-imports-sockaddr
+description: The sockaddr import makes working with IP addresses easy.
+layout: docs
+---
+
+# Import: sockaddr
+
+The sockaddr import makes working with IP addresses easy.
+
+### sockaddr.is_contained(outer, inner)
+
+The is_contained function returns true if `inner` is an address that
+is contained within `outer`.
+
+```sentinel
+sockaddr.is_contained("192.168.0.0/24", "192.168.0.32") // true
+sockaddr.is_contained("192.168.0.0/24", "192.174.1.1") // false
+```
+
+### sockaddr.is_equal(addr1, addr2)
+
+The is_equal function returns true if addr1 is equal to addr2.
+
+```sentinel
+sockaddr.is_equal("192.168.12.24", "192.168.12.24") // true
+```
+
+### sockaddr.is_ipv4(addr)
+
+The is_ipv4 function returns true if addr is an IPv4 address.
+
+```sentinel
+sockaddr.is_ipv4("192.168.12.24") // true
+sockaddr.is_ipv4("2001:0db8:85a3:0000:0000:8a2e:0370:7334") // false
+```
+
+### sockaddr.is_ipv6(addr)
+
+The is_ipv6 function returns true if addr is an IPv6 address.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/imports/strings.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/imports/strings.mdx
new file mode 100644
index 0000000000..e1ba9a9e2a
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/imports/strings.mdx
@@ -0,0 +1,150 @@
+---
+page_title: 'Import: strings'
+sidebar_current: docs-imports-strings
+description: The strings import exposes common string operations.
+layout: docs
+---
+
+# Import: strings
+
+The strings import exposes common string operations.
+
+### strings.has_prefix(s, prefix)
+
+Returns true if `s` starts with `prefix`.
+
+```sentinel
+strings.has_prefix("billing-id", "billing-") // true
+strings.has_prefix("bill-id", "billing-") // false
+```
+
+### strings.has_suffix(s, suffix)
+
+Returns true if `s` ends with `suffix`.
+
+```sentinel
+strings.has_suffix("billing-id", "id") // true
+strings.has_suffix("billing-name", "id") // false
+```
+
+### strings.join(a, sep)
+
+Joins a list with the string supplied by `sep`.
+
+Multi-dimensional lists are supported, and non-string primitive
+types will be converted to their string equivalents. Maps and
+other complex non-list types are not supported.
+
+```sentinel
+strings.join(["foo", "bar", "baz"], ".") // "foo.bar.baz"
+strings.join([["foo", "bar"], "baz"], ".") // "foo.bar.baz"
+strings.join(["a", 1, 1.01, true], ",") // "a,1,1.01,true"
+```
+
+### strings.replace(s, old, new, times)
+
+Replace the substring `old` with the substring `new` in the string `s`
+by how many times the int parameter `times` is specified.
+If the string `s` doesn't the substrings or if `times` is zero,
+then the string is returned without any changes applied.
+
+```
+strings.replace("foobar", "foo", "bar") // "barbar"
+strings.replace("foofoofoobar", "foo", "bar", 1) // "barfoofoobar"
+strings.replace("foofoofoobar", "foo", "bar", 2) // "barbarfoobar"
+strings.replace("foobar", "test", "bar") // "foobar"
+strings.replace("foobar", "foo", "bar", 0) // "foobar"
+```
+
+### strings.trim(s, cutset)
+
+Trim the leading and trailing characters in `cutset` from the string `s`.
+If the string doesn't have the cutset, then the string is returned
+unmodified.
+
+```
+strings.trim("iiifoobariii", "i") // "foobar"
+strings.trim("!1!bar foo!!1", "!1") // "bar foo"
+```
+
+### strings.trim_left(s, cutset)
+
+Trim the leading characters contained in `cutset` from the string `s`.
+If the string doesn't have the cutset, then the string is returned
+unmodified.
+
+```sentinel
+strings.trim_left("aaaaaaaafoo", "a") // "foo"
+strings.trim_left("!!!bar!!!", "!") // "bar!!!"
+```
+
+### strings.trim_right(s, cutset)
+
+Trim the trailing characters contained in the `cutset` from the
+string `s`. If the string doesn't have the cutset, then the string
+is returned unmodified.
+
+```sentinel
+strings.trim_right("foo_bar...", ".") // "foo_bar"
+strings.trim_right("billing---","-") // "billing"
+```
+
+### strings.trim_space(s)
+
+Trim leading and trailing white space from the string `s`. If the
+string doesn't have any surrounding white space, then the string is
+returned unmodified.
+
+```sentinel
+strings.trim_space(" foo ") // "foo"
+strings.trim_space(" bar foo ") // "bar foo"
+```
+
+### strings.trim_prefix(s, prefix)
+
+Trim the prefix from the string `s`. If the string doesn't have the
+prefix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_prefix("billing-id", "billing-") // "id"
+strings.trim_prefix("bill-id", "billing-") // "bill-id"
+```
+
+### strings.trim_suffix(s, suffix)
+
+Trim the suffix from the string `s`. If the string doesn't have the
+suffix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_suffix("billing-id", "-id") // "billing"
+strings.trim_suffix("bill-id", "-foo") // "bill-id"
+```
+
+### strings.to_lower(s)
+
+Lowercase the string s.
+
+```sentinel
+strings.to_lower("FoO") // "foo"
+```
+
+### strings.to_upper(s)
+
+Uppercase the string s.
+
+```sentinel
+strings.to_upper("FoO") // "FOO"
+```
+
+### strings.split(s, sep)
+
+Split the string 's' via a substring 'sep'. If 'sep' is not contained in
+'s', the return value will be a single-element list containing only 's'.
+As a special-case, if the input is already a list, it will be returned
+as-is. This allows calling the split function when not knowing if the input
+is already a list or not.
+
+```sentinel
+strings.split("foo,bar,baz", ",") // ["foo", "bar", "baz"]
+strings.split(["foo", "bar", "baz"], ",") // ["foo", "bar", "baz"]
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/imports/time.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/imports/time.mdx
new file mode 100644
index 0000000000..3a675e2bbd
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/imports/time.mdx
@@ -0,0 +1,257 @@
+---
+page_title: 'Import: time'
+sidebar_current: docs-imports-time
+description: The time import provides access to the execution time and time functions.
+layout: docs
+---
+
+# Import: time
+
+The time import provides access to the execution time and time functions.
+
+During the execution of a policy the time is fixed to the moment of
+execution. This gives the illusion that a policy instantly executes at a
+single moment of time.
+
+The import uses Coordinated Universal Time (UTC).
+
+As an example of this, if a policy accessed `time.now.second` multiple times,
+for each access the same value would be returned even if they are several seconds apart.
+This is the execution `timespace`, which is mapped to `time.now`.
+
+It is also possible to run functions on a custom time by using the
+`time.load()` function to create a new timespace from the specified value.
+See the documentation for available actions on timespaces.
+
+Times specified as the reference time or via the `time.load()` function can
+be specified in a `timeish` fashion. This is either one of:
+
+- The number of seconds since the Unix epoch (Thursday, 1 January 1970,
+ 00:00:00 UTC),
+- A timestamp matching the format outlined in RFC3339, either in the
+ format `2006-01-02T15:04:05+07:00`, which includes a timezone offset, or
+ `2006-01-02T15:04:05Z`, which indicates UTC.
+
+### time.now
+
+Create a `timespace` locked either to execution time or, if set, the
+reference time. In a given execution, this will always produce the same
+value.
+
+```sentinel
+time.now // {"day": 17, "hour": 23, "minute": 19, "month": 11 ... }
+```
+
+### time.load(timeish)
+
+Create a timespace using the given value. Please see the import
+documentation for valid values for "timeish".
+
+```sentinel
+time.load("2019-11-16T01:20:30Z") // {"day": 16, "hour": 1, "minute": 20, "month": 11 ... }
+```
+
+### time.nanosecond
+
+A nanosecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.nanosecond * 1) // 2019-11-16T01:20:30.000000001Z
+```
+
+### time.microsecond
+
+A microsecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.microsecond * 1) // 2019-11-16T01:20:30.000001Z
+```
+
+### time.millisecond
+
+A millisecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.millisecond * 1) // 2019-11-16T01:20:30.001Z
+```
+
+### time.second
+
+A second duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.second * 1) // 2019-11-16T01:20:31Z
+```
+
+### time.minute
+
+A minute for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.minute * 1) // 2019-11-16T01:21:30Z
+```
+
+### time.hour
+
+An hour duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+## Type: timespace
+
+### timespace.hour
+
+The hour from 0 to 23.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").hour // 1
+```
+
+### timespace.minute
+
+The minute from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").minute // 20
+```
+
+### timespace.second
+
+The second from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").second // 30
+```
+
+### timespace.day
+
+The day of the month, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").day // 16
+```
+
+### timespace.month
+
+The month of the year as an integer, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month // 11
+```
+
+### timespace.month_name
+
+The name of the month of the year, in English. Example: `January`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month_name // November
+```
+
+### timespace.weekday
+
+The weekday as an integer, where 0 is Sunday and 6 is Saturday.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday // 6
+```
+
+### timespace.weekday_name
+
+The name of the day of the week, in English. Example: `Sunday`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday_name // Saturday
+```
+
+### timespace.year
+
+The year as an integer.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").year // 2019
+```
+
+### timespace.unix
+
+The number of seconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix // 1573867230
+```
+
+### timespace.unix_nano
+
+The number of nanoseconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix_nano // 1573867230000000000
+```
+
+### timespace.zone
+
+The timezone offset, in seconds. This can be a negative number to indicate
+time west of UTC.
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone // -28800
+```
+
+### timespace.zone_string
+
+The timezone offset, in the format `+HH:MM` (example: `-08:00` for PST).
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone_string // -08:00
+```
+
+### timespace.before(timeish_or_timespace)
+
+Check if the time in the timespace is before the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").before("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.after(timeish_or_timespace)
+
+Check if the time in the timespace is after the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").after("2019-11-15T01:20:30Z") // true
+```
+
+### timespace.equal(timeish_or_timespace)
+
+Check if two times are equivalent
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").equal("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.add(duration)
+
+Add a duration in nanoseconds to the time in the timespace and return a new timespace. The
+duration can be negative. The duration should be specified as an integer
+multiplied with the correct unit.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+### timespace.sub(timeish_or_timespace)
+
+Subtract a time from the time in the timespace and return a duration in nanoseconds.
+
+```sentinel
+// Gives a duration of 3 hours.
+duration = time.load(
+ "2019-11-16T01:20:30Z",
+).sub("2019-11-16T04:20:30Z")
+
+// Returns 2019-11-16T01:20:30Z
+time.load(
+ "2019-11-16T04:20:30Z",
+).add(duration)
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/imports/types.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/imports/types.mdx
new file mode 100644
index 0000000000..fcfdbd2ae5
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/imports/types.mdx
@@ -0,0 +1,35 @@
+---
+page_title: 'Import: types'
+sidebar_current: docs-imports-types
+description: The types import enables a Sentinel policy to parse an object's type.
+layout: docs
+---
+
+# Import: types
+
+The types import enables a Sentinel policy to parse an object's type.
+
+### types.type_of(obj)
+
+Returns the object's type as a string
+
+```sentinel playground
+import "types"
+
+// Typically the inputs would come from an external source.
+is_boolean = rule { types.type_of(true) is "bool" }
+is_string = rule { types.type_of("Hello!") is "string" }
+is_integer = rule { types.type_of(42) is "int" }
+is_float = rule { types.type_of(42.123) is "float" }
+is_null = rule { types.type_of(null) is "null" }
+is_undefined = rule { types.type_of(undefined) is "undefined" }
+
+main = rule {
+ is_boolean and
+ is_string and
+ is_integer and
+ is_float and
+ is_null and
+ is_undefined
+}
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/imports/units.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/imports/units.mdx
new file mode 100644
index 0000000000..e2ad3442f9
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/imports/units.mdx
@@ -0,0 +1,39 @@
+---
+page_title: 'Import: units'
+sidebar_current: docs-imports-units
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: units
+
+The units import provides access to quick calculations for various byte
+units.
+
+Note that this import uses base-2 terminology:
+
+- One kilobyte is 1024 bytes
+- One megabyte is 1024^2 = 1048576 bytes
+- One gigabyte is 1024^3 = 1073741824 bytes
+- One terabyte is 1024^4 = 1099511627776 bytes
+- One petabyte is 1024^5 = 1125899906842624 bytes
+
+### units.kilobyte
+
+The base-2 representation of a kilobyte (1024 bytes).
+
+### units.megabyte
+
+The base-2 representation of a megabyte (1048576 bytes).
+
+### units.gigabyte
+
+The base-2 representation of a gigabyte (1073741824 bytes).
+
+### units.terabyte
+
+The base-2 representation of a terabyte (1099511627776 bytes).
+
+### units.petabyte
+
+The base-2 representation of a petabyte (1125899906842624 bytes).
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/imports/version.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/imports/version.mdx
new file mode 100644
index 0000000000..59366c1087
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/imports/version.mdx
@@ -0,0 +1,151 @@
+---
+page_title: 'Import: version'
+sidebar_current: docs-imports-version
+description: |-
+ The version import provides functions for parsing versions and version constraints,
+ and verifying versions against a set of constraints.
+layout: docs
+---
+
+# Import: version
+
+The version import provides functions for parsing versions and version constraints,
+and verifying versions against a set of constraints.
+
+Versions are created through the `new` function, which accepts a string type in dotted-tri format (i.e. 1.0.0-alpha.1+001) that can represent a version.
+
+### version.new(v)
+
+Constructs a version from string value.
+
+```sentinel
+version.new("1.0.0-alpha.1+001")
+```
+
+### version.major
+
+An integer representation of the Major number for a given version .
+
+```sentinel
+version.new("1.0.0-alpha.1+001").major // 1
+```
+
+### version.minor
+
+An integer representation of the Minor number for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").minor // 0
+```
+
+### version.patch
+
+An integer representation of the Patch number for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").patch // 0
+```
+
+### version.prerelease
+
+A string representation of the Prerelease for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").prerelease // "alpha.1"
+```
+
+### version.metadata
+
+A string representation of the Metadata for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").metadata // "001"
+```
+
+### version.version
+
+A string representation of the version including pre-release and metadata information.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").version // "1.0.0-alpha.1+001"
+```
+
+### version.greater_than(v)
+
+Tests that the version is greater than a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").greater_than("0.12.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").gt("1.0.1-alpha.1+001") // False
+```
+
+### version.greater_than_or_equals(v)
+
+Tests that the version is greater than or equal to a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").greater_than_or_equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").gte("1.0.1-alpha.1+001") // False
+```
+
+### version.less_than(v)
+
+Tests that the version is less than a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").less_than("1.0.1-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").lt("0.12.0-alpha.1+001") // False
+```
+
+### version.less_than_or_equals(v)
+
+Tests that the version is less than or equal to a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").less_than_or_equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").lte("0.12.0-alpha.1+001") // False
+```
+
+### version.less_than_or_equals(v)
+
+Tests that the version equals a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").eq("0.12.0-alpha.1+001") // False
+```
+
+### version.compare(v)
+
+Compares one version with a given version. Compare returns -1, 0, or 1 if the version is less, equal, or greater than the other version, respectively.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").compare("0.12.0-alpha.1+001") // -1
+version.new("1.0.0-alpha.1+001").compare("1.0.0-alpha.1+001") // 0
+version.new("0.12.0-alpha.1+001").cmp("1.0.0-alpha.1+001") // 1
+```
+
+### version.satisfies(v, x)
+
+Tests that a version adheres to one or more version constraints. Satisfies supports the following restriction operators:
+
+- =
+- !=
+- \>
+- <
+- \>=
+- <=
+- ~>
+
+Satisfies supports the following usage scenarios:
+
+- A constraint with a pre-release can only match a pre-release version that contains the same major, minor and patch identifiers.
+- A constraint without a pre-release can only match a version without a pre-release.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").satisfies("> 1.0.0-alpha.1+001") // false
+version.new("1.0.0-alpha.1+001").satisfies(">= 1.0.0-alpha.1+001") // true
+version.new("1.0.0-alpha.1+001").satisfies(">= 1.0.0-alpha.1+001, < 1.0.0-beta+001") // true
+version.new("1.0.0").sat("= 1.0.0") // true
+version.new("0.12.7").sat("~> 0.12.0") // true
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/index.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/index.mdx
new file mode 100644
index 0000000000..c68ccc265b
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/index.mdx
@@ -0,0 +1,31 @@
+---
+layout: 'docs'
+page_title: 'Documentation'
+sidebar_current: 'docs-home'
+description: |-
+ Sentinel is a language and framework for policy built to be embedded in existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+["Why Sentinel?"](/sentinel/why) to understand what Sentinel is, how it
+compares to other software, and more.
+
+
\ No newline at end of file
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/intro.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/intro.mdx
new file mode 100644
index 0000000000..b9a2675f2b
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/intro.mdx
@@ -0,0 +1,75 @@
+---
+page_title: Introduction to Sentinel
+sidebar_current: docs-intro
+description: >-
+ Sentinel is a language and framework for policy built to be embedded in
+ existing software to enable fine-grained, logic-based policy decisions.
+layout: docs
+---
+
+# Introduction to Sentinel
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+["Why Sentinel?"](/sentinel/docs/why) to understand what Sentinel is, how it
+compares to other software, and more.
+
+## What is Sentinel?
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+Sentinel provides a language for writing policy and a workflow for developing
+and testing policies independent of the software they'll be written to. We
+also provide a framework for developers to build plugins that allow a
+Sentinel-enabled system to access external information to make policy
+decisions.
+
+Most systems today have some degree of access control. You are able to define
+identities and what they have access to. These ACL systems solve an immediate
+and necessary problem of locking down a system in very broad strokes. Sentinel
+is a reusable system for more advanced software policy decisions. Sentinel
+enables:
+
+- **Fine-Grained Policy**: Most ACL systems only enable coarse-grained
+ behaviors: "read", "write", etc. Sentinel enables fine-grained behavior such
+ as disallowing a certain API call when specific parameters are present.
+
+- **Logic-Based Policy**: You can write policy using full
+ conditional logic. For example, you may only allow a certain application behavior
+ on Monday to Thursday unless there is a manager override.
+
+- **Accessing External Information**: Sentinel can source external information
+ to be used in policy decisions. For example, a policy that restricts the size
+ of a payload may read data from [Consul](https://www.consul.io) to determine
+ the payload size limit.
+
+- **Enforcement levels**: Sentinel allows policies to be defined along
+ with an "enforcement level" that dictates the pass/fail behavior of a policy.
+ Advisory policies warn if they fail, soft mandatory policies can have their
+ failures overridden, and hard mandatory policies must pass under all
+ circumstances. Having this as a built-in concept enables you to model
+ policy more accurately and completely for your organization.
+
+
+
+
+## Next steps
+
+Refer the page on [Why Sentinel?](/sentinel/why) to learn more about the origins of Sentinel.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/language/arithmetic.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/language/arithmetic.mdx
new file mode 100644
index 0000000000..c49f2fd0ce
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/language/arithmetic.mdx
@@ -0,0 +1,67 @@
+---
+page_title: Sentinel Language - Arithmetic
+sidebar_current: docs-language-arith
+description: >-
+ Sentinel supports arithmetic operators for integers and floats. Sentinel
+ supports sum, difference, product, quotient, and remainder.
+layout: docs
+---
+
+# Language: Arithmetic
+
+Sentinel supports arithmetic operators for integers and floats. Sentinel
+supports sum, difference, product, quotient, and remainder.
+
+```plaintext
++ sum
+- difference
+* product
+/ quotient
+% remainder
+```
+
+These operators work in a typical infix style:
+
+```sentinel
+4 + 8 // 12
+8 * 2 // 16
+8 / 4 // 2
+8 / 5 // 1
+8 % 5 // 3
+```
+
+## Order of Operations
+
+Arithmetic follows a standard mathematical order of operations.
+Grouping with parentheses can be used to affect ordering.
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+(4 + 5) * 2 // 18
+```
+
+A full table of operator precendence can be found on the
+[boolean expressions page](/sentinel/language/boolexpr). This
+shows how arithmetic operators relate to other operators.
+
+## Integer Division
+
+Integer division with a remainder rounds down to the nearest integer.
+Example: `8 / 3 is 2`.
+
+If the divisor is zero, an error occurs.
+
+## Mixed Numeric Operations
+
+Mixed numeric operations between integer and floating-point values are
+permitted. The result is a floating-point operation with the integer converted
+to a floating-point value for purposes of calculation.
+
+```sentinel
+import "types"
+
+a = 1.1 + 1 // 2.1
+types.type_of(a) // "float"
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/language/boolexpr.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/language/boolexpr.mdx
new file mode 100644
index 0000000000..f1c518c3bf
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/language/boolexpr.mdx
@@ -0,0 +1,225 @@
+---
+page_title: Sentinel Language - Boolean Expressions
+sidebar_current: docs-language-boolexpr
+description: >-
+ Boolean expressions are expressions that evaluate to a boolean value from the
+ result of a combination of one or more comparisons and logical operators.
+layout: docs
+---
+
+# Language: Boolean Expressions
+
+Boolean expressions are expressions that evaluate to a boolean value
+from the result of a combination of one or more comparisons and logical
+operators. Boolean expressions form the basis of policy since policy can be broken
+down to a set of logical decisions that turn into true or false.
+
+A single boolean expression is the body of a [rule](/sentinel/language/rules).
+Additionally, boolean expressions are used with [conditionals](/sentinel/language/conditionals),
+and more.
+
+## Order of Operations
+
+The precedence of operators is shown below. Operators with higher
+precedence values (larger numbers) are evaluated first. Operators with
+the same precedence value are evaluated left-to-right.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Examples of this precedence are shown below:
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+```
+
+## Logical Operators
+
+Logical operators are used to change or combine logical expressions.
+There are three binary operators `and`, `or`, and `xor`. There is
+a single unary operator `not` (which can also be specified as `!`).
+
+The binary operators require two boolean operands and the unary
+operator requires a single boolean operand. In both case, the operands
+are values or expressions that are boolean types.
+
+Below is a basic explanation of the logical operators directly from
+the specification:
+
+```sentinel
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+Using parentheses to group boolean expressions, you can combine
+boolean expressions to become more complex or affect ordering:
+
+```sentinel
+(p and q) or r
+p and (q or r)
+```
+
+## Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+For any comparison, the two operands must be equivalent types. The one
+exception are integers and floats. When an integer is compared with
+a float, the integer is promoted to a floating point value.
+
+The available comparison operators are:
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+The behavior of `is` with `==` and `is not` with `!=` is identical.
+
+Example comparisons:
+
+```sentinel
+name is "Mitchell"
+idnumber > 42
+idnumber <= 12
+```
+
+Using the logical operators, these can be combined:
+
+```sentinel
+name is "Mitchell" and idnumber > 42
+```
+
+The language specification provides more detail on exactly
+[how comparison behaves](/sentinel/language/spec#comparison-operators).
+
+## Set Operators
+
+The set operators `contains` and `in` test for inclusion in a collection
+(a list or map).
+
+Set operators may 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.
+
+`contains` tests if the left-hand collection contains the right-hand value.
+`in` tests if the right-hand collection contains the left-hand value. For
+maps, "contains" looks at the keys, not the values.
+
+Examples:
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+```
+
+## Matches Operator
+
+The `matches` operator tests if a string matches a regular expression.
+The `matches` operator can be negated by prefixing the operator with
+`not`, such as `not matches`.
+
+The left-hand value is the string to test. The right-hand value is
+a string value representing a regular expression. The syntax of the regular
+expression is the same general syntax used by Python, Ruby, and other languages.
+The precise syntax accepted is the syntax accepted by RE2.
+
+Regular expressions are not anchored by default; any anchoring must be
+explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+## Any, All Expressions
+
+`any` and `all` expressions allow testing that any or all elements of a
+collection match some boolean expression. The result of an `any` and `all`
+expression is a boolean.
+
+`any` returns the boolean value `true` if any value in the collection expression
+results in the body expression evaluating to `true`. If the body expression
+evalutes to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression
+result in the body expression evaluating to `true`. If any value in the
+collection expression result in the body expression evaluating to `false`,
+the `all` expression returns `false`.
+
+For empty collections, `any` returns `false` and `all` returns `true`.
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" }
+any group.tasks as t { t.driver is "vmware" }
+```
+
+Since `any` and `all` expressions are themselves boolean expressions, they
+can be combined with other operators:
+
+```sentinel
+any ["a", "b"] as char { char is "a" } or
+other_value is "another"
+```
+
+## Emptiness Comparison
+
+The expressions `is empty` and `is not empty` provide a convenience
+method for determining the emptiness of a value. It supports the same types
+as the [built-in length](/sentinel/functions/length), collections and strings.
+
+```sentinel
+[] is empty // true
+[] is not empty // false
+["foo"] is empty // false
+["foo"] is not empty // true
+undefined is empty // undefined
+undefined is not empty // undefined
+```
+
+## Defined Comparison
+
+The expressions `is defined` and `is not defined` provide a convenience
+method for determining if a value has been defined. In other words, any value
+other than `undefined` can be considered as `defined`.
+
+```sentinel
+[] is defined // true
+4 is defined // true
+true is defined // true
+{} is defined // true
+undefined is defined // false
+[] is not defined // false
+4 is not defined // false
+true is not defined // false
+undefined is not defined // true
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/language/collection-operations.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/language/collection-operations.mdx
new file mode 100644
index 0000000000..41da8f3584
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/language/collection-operations.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Sentinel Language - Collection Operations
+sidebar_current: docs-language-collection-operations
+description: |-
+ Operations for interacting with collections (list, map).
+layout: docs
+---
+
+# Language: Collection Operations
+
+Collection operations are expressions that are performed on a list or map to
+return a variation of the initial data.
+
+At the moment, `filter` is the only collection operation available.
+
+## Filter Expression
+
+`filter` is a [quantifier
+expression](/sentinel/language/spec#quantifier-expressions-any-all-filter-map) that
+returns a subset of the provided collection. Only elements whose filter body
+returns true will be returned. If any of the elements filter body returns
+undefined, the final result will be undefined.
+
+Filter uses the same syntax as the `any` and `all` [boolean
+expressions](/sentinel/language/boolexpr#any-all-expressions):
+
+```sentinel
+filter list as value { condition } // Single-iterator, list
+filter list as idx, value { condition } // Double-iterator, list
+
+filter map as key { condition } // Single-iterator, map
+filter map as key, value { condition } // Double-iterator, map
+```
+
+Examples:
+
+```sentinel
+l = [1, 1, 2, 3, 5, 8]
+evens = filter l as v { v % 2 is 0 } // [2, 8]
+
+m = { "a": "foo", "b": "bar" }
+matched_foo = filter m as _, v { v is "foo" } // { "a": "foo" }
+```
+
+## Map Expression
+
+`map` is a [quantifier
+expression](/sentinel/language/spec#quantifier-expressions-any-all-filter-map) that
+returns a list, regardless of the input collection type. Each element within the
+input collection is evaluted according to the map expression body and appended
+to the result.
+
+```sentinel
+l = [1, 2]
+r = map l as v { v % 2 } // [false, true]
+
+m = { "a": "foo", "b": "bar" }
+r = map m as k, v { v } // ["foo", "bar"]
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/language/conditionals.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/language/conditionals.mdx
new file mode 100644
index 0000000000..1736795dbb
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/language/conditionals.mdx
@@ -0,0 +1,161 @@
+---
+page_title: Sentinel Language - Conditionals
+sidebar_current: docs-language-conditional
+description: |-
+ Conditional statements allow your policy to behave differently depending on a condition.
+layout: docs
+---
+
+# Language: Conditionals
+
+Conditional statements allow your policy to behave differently depending
+on a condition.
+
+Conditional statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## If Statements
+
+`if` statements only execute their bodies if a condition is met.
+The syntax of an `if` statement is:
+
+```sentinel
+if condition {
+ // ... this is executed if condition is true
+}
+```
+
+The `condition` must result in a boolean, such as by calling a function
+or evaluating a [boolean expression](/sentinel/language/boolexpr). If
+the `condition` is `true`, the body (within the `{}`) is executed. Otherwise,
+the body is skipped.
+
+Examples:
+
+```sentinel
+// This would execute the body
+value = 12
+if value is 18 {
+ print("condition met")
+}
+
+// Direct boolean values can be used
+value = true
+if value {
+ print("condition met")
+}
+
+// This would not execute the body since the boolean expression will
+// result in undefined.
+value = {}
+if value["key"] > 12 {
+ print("condition met")
+}
+```
+
+### Else, Else If
+
+An `else` clause can be given to an `if` statement to execute a body
+in the case the condition is _not met_. By putting another `if` statement
+directly after the `else`, multiple conditions can be tested for.
+The syntax is:
+
+```sentinel
+if condition {
+ // ...
+} else {
+ // ...
+}
+
+if condition {
+ // ...
+} else if other_condition {
+ // ...
+} else {
+ // ...
+}
+```
+
+### Scoping
+
+The body of an `if` statement does not create a new
+[scope](/sentinel/language/scope). Any variables assigned within the body
+of an if statement will modify the scope that the `if` statement itself
+is in.
+
+Example:
+
+```sentinel
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+```sentinel
+a = 18
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+## Case Statements
+
+`case` statements are a selection control mechanism that execute a clause based
+on matching expressions. It is worth noting that the expression for `case` is
+optional. When no expression is provided, it defaults the expression to `true`.
+Additionally, the order of clauses is important, as they are evaluated from top
+to bottom, executing the first match.
+The syntax of a case statement is:
+
+```sentinel
+case expression {
+ when clause_expression:
+ // executed when clause_expression and expression are equal
+ else:
+ // executed if no clause matches expression
+}
+```
+
+### When Clause
+
+Any clause that has an expression for comparison must use the `when` keyword.
+It accepts a list of expressions, seperated by a `,`.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+}
+```
+
+```sentinel
+case {
+ when x > 40:
+ return true
+}
+```
+
+### Else Clause
+
+The `else` keyword allows for capturing any expressions that have no matching
+`when` clause.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+ else:
+ return false
+}
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/language/functions.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/language/functions.mdx
new file mode 100644
index 0000000000..8214078b6b
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/language/functions.mdx
@@ -0,0 +1,229 @@
+---
+page_title: Sentinel Language - Functions
+sidebar_current: docs-language-functions
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Functions
+
+Functions allow you to create reusable code to perform computations.
+
+Below is a trivial example function that doubles a number and shows
+the main rule using the function:
+
+```sentinel playground
+double = func(x) {
+ return x * 2
+}
+
+main = rule { double(12) is 24 }
+```
+
+Functions may contain any expressions,
+[conditionals](/sentinel/language/conditionals),
+and [loops](/sentinel/language/loops). They must return a value
+at the end of their execution. If no reasonable return value exists,
+the function should return [undefined](/sentinel/language/undefined).
+
+## Function Types
+
+### Named Functions
+
+-> **NOTE:** Named functions must be created within the [package scope](/sentinel/language/scope#package-scope).
+
+Named functions are declared using the `func` keyword as its own statement.
+They provide a safe method of creating functions and have additional
+restrictions that do not apply to anonymous functions.
+
+Firstly, named functions cannot be re-assigned, and also cannot use a name
+that is already used elsewhere. For instance, the below example will error due
+to the attempt to reassign the named function identifier to a new value:
+
+```sentinel
+func sum(a, b) {
+ return a + b
+}
+
+sum = 4
+```
+
+Additionally, the following will error due to the named function attempting
+to make use of an already assigned identifier:
+
+```sentinel
+sum = 4
+
+func sum(a, b) {
+ return a + b
+}
+```
+
+Named functions are helpful for policy authors to declare critical functions
+whose value or implementation should not be changed.
+
+### Anonymous Functions
+
+An anonymous function is created by assigning a variable to a `func`. The
+variable can be reassigned at any time including to different value types.
+Anonymous functions are helpful for use cases like closures, where a function
+can return another function.
+
+```sentinel
+func makeAdder(a) {
+ return func(b) {
+ return a + b
+ }
+}
+```
+
+## Creating a Function
+
+A function can have zero or more parameters. These parameters are specified
+within parentheses `()` separated by commas. If a function has no parameters,
+the parentheses must still be present.
+
+A function must terminate with a `return` statement to return a value back to
+the caller. Only a single value can be returned. The type of a return can
+vary between return statements.
+
+Anonymous function example:
+
+```sentinel
+add1 = func(x) { return x + 1 }
+```
+
+Named function example:
+
+```sentinel
+func add1(x) {
+ return x + 1
+}
+```
+
+Both examples create a function that adds 1 to the parameter `x`.
+
+## Calling a Function
+
+Functions are called by accessing their name followed by parentheses with
+a list of comma-separated values for the parameters. If the function takes no
+parameters, an empty set of parentheses must still be used to denote a
+function call.
+
+To call the function in the example above:
+
+```sentinel
+x = 1
+y = add1(x)
+```
+
+## Scoping
+
+The body of a `func` creates a new [scope](/sentinel/language/scope).
+
+The parent scope is the scope in which the function is created. This allows
+for the creation of functions within functions that can access their outer
+scopes.
+
+Example:
+
+```sentinel
+f = func() {
+ a = 42
+ print(a) // 42
+ return undefined
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+f = func() {
+ a = 42
+ return undefined
+}
+
+print(a) // 18
+```
+
+And below is an example of creating a function in a function which uses
+outer values:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double()
+}
+
+print(f()) // 84
+```
+
+A more complex example below shows how scoping works when passing
+functions around as arguments or results:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double
+}
+
+double = f()
+print(double()) // 84
+```
+
+## Pass By Value
+
+The parameters to a function are passed by value, but not deep copied.
+This means that elements of collections can still be modified but the
+root value cannot be changed.
+
+Example:
+
+```sentinel
+f = func(x) {
+ x = "value"
+ return x
+}
+
+x = "outside"
+f(x)
+print(x) // "outside"
+```
+
+```sentinel
+f = func(x) {
+ append(x, "value")
+ return x
+}
+
+x = []
+f(x)
+print(x) // ["value"]
+```
+
+## Recursion
+
+A function is allowed to call other functions, including itself.
+This can be used to implement recursion. For example, the fibonacci sequence
+is implemented below:
+
+```sentinel
+fib = func(x) {
+ if x <= 0 {
+ return undefined
+ }
+
+ if x == 1 {
+ return 1
+ } else {
+ return x + fib(x - 1)
+ }
+}
+```
+
+Note that this example also shows using
+[undefined](/sentinel/language/undefined)
+as a return value in cases where a function has undefined behavior.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/language/imports.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/language/imports.mdx
new file mode 100644
index 0000000000..0ed1541b85
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/language/imports.mdx
@@ -0,0 +1,116 @@
+---
+page_title: Sentinel Language - Imports
+sidebar_current: docs-language-imports
+description: >-
+ Imports enable a Sentinel policy to access reusable libraries and external
+ data and functions.
+layout: docs
+---
+
+# Language: Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external
+data and functions. The exact list of available imports is dependent on the
+host system. Host systems may also make the available imports configurable
+via plugins.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that
+the import `calendar` allows access to engineer calendars. This import
+only exists for the purpose of this example and at the time of writing is
+not a real available import.
+
+```sentinel
+import "calendar"
+
+// Get the calendar for Bob for today
+bob_calendar = calendar.for("bob").today
+
+// Allow this policy to pass if Bob is not on vacation.
+main = rule { not bob_calendar.has_event("vacation") }
+```
+
+This example shows an import being used to deny a behavior if Bob is on
+vacation. Hopefully real policies do not depend on a single person being
+in the office, but the example shows the power of imports.
+
+In addition to consuming imports, you can also
+[extend Sentinel by writing custom imports](/sentinel/extending)
+This allows you to add any arbitrary behavior to your Sentinel-protected
+systems.
+
+## Importing
+
+Whether accessing a built-in library or an external plugin, the syntax
+for an import is the same:
+
+```sentinel
+import "name"
+```
+
+This adds the import with the name `name`. It can be referenced using
+the identifier `name`. For example, if the import above exposed first
+and last name data, you could access it via `name.first` or `name.last`.
+
+You can shadow imports by assigning a variable. In the example below,
+the import `name` becomes unavailable within the function because it is
+shadowed by a variable of the same name.
+
+Shadowing an import is not the same as overwriting a variable. Imports
+are not part of the [scope](/sentinel/language/scope), meaning that
+the shadow only exists for the scope it is created within. For everything
+else, the import works even after it is shadowed. The example below shows that
+as well.
+
+```sentinel
+import "name"
+
+f = func() {
+ name = "jane"
+ return name
+}
+
+print(f()) // "jane"
+print(name.first) // "bob"
+```
+
+## Aliases
+
+An import can be aliased to another name using the syntax below:
+
+```sentinel
+import "name" as other
+```
+
+This would make the import "name" available as `other`.
+
+An import may only be imported once, regardless of aliases. The following
+would be **invalid** and would result in an error:
+
+```sentinel
+import "name" as one
+import "name" as two
+```
+
+## Accessing Import Data
+
+Imports are accessed with dot-separated identifiers, such as `name.first` or
+`name.stage.first`. These are called _selector expressions_. An import may
+return nested data that further can be accessed via selector expressions.
+
+In the first example on this page with the "calendar" import, we see this:
+
+```sentinel
+import "calendar"
+
+a = calendar.for("bob") // selector expression calendar.for
+b = a.today // selector expression on the result
+c = b.vacation // accessing further nested data
+```
+
+The exact structure of an import is dependent on the import. Please reference
+the documentation for an import to learn more.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/language/index.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/language/index.mdx
new file mode 100644
index 0000000000..7f588c94cd
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/language/index.mdx
@@ -0,0 +1,114 @@
+---
+page_title: Sentinel Language
+sidebar_current: docs-language-basics
+description: |-
+ Sentinel policies are written using the Sentinel language. This language
+ is easy to learn and easy to write. You can learn the Sentinel language
+ and be productive within an hour. Learning Sentinel doesn't require any
+ formal programming experience.
+layout: docs
+---
+
+# Sentinel Language
+
+Sentinel policies are written using the Sentinel language. This language
+is easy to learn and easy to write. You can learn the Sentinel language
+and be productive within an hour. Learning Sentinel doesn't require any
+formal programming experience.
+
+This language guide will contain many details that are not necessary to
+be productive with Sentinel or are targeted to those who are looking for
+exact information about the language (such as what types of line endings are
+allowed). You can safely ignore these sentences.
+
+You may also view the
+[official language specification](/sentinel/language/spec)
+This is a specific and detailed document on the syntax and behavior of
+the language primarily intended for implementation creators and to disambiguate
+the language.
+
+## Simplest Example
+
+The example below is about the simplest practical example of Sentinel.
+It is reasonable to imagine this as a realistic policy. This shows that
+in most cases, Sentinel will be extremely simple:
+
+```sentinel
+main = rule { request.method is "GET" and request.headers contains "X-Key" }
+```
+
+## Files
+
+Sentinel policies are single files that end in the `.sentinel` file extension.
+There is currently no built-in mechanism to Sentinel for merging multiple
+files. This is purposefully done to make Sentinel policies easy to submit
+to systems that support Sentinel policies.
+
+Sentinel policy files must be UTF-8 encoded and can end in both
+Unix (LF) or Windows (CRLF) line breaks. When running the
+[auto-formatter](#),
+line endings will always use Unix line breaks.
+
+## Ordering
+
+Sentinel policies are executed top-down. For example:
+
+```sentinel
+a = 1 // a = 1 here
+b = a + 1 // b = 2 here
+a = 3 // a = 3, b = 2
+```
+
+In this example, the value of `a` and `b` is shown at each line. Since Sentinel
+executes values top-down, the final value of `a` is 3 and `b` is 2. `b` _does
+not_ become 4.
+
+## Main
+
+Sentinel expects there to be a `main` [rule](/sentinel/language/rules).
+The value of this rule is the result of the entire policy.
+
+The result of the policy depends on the evaluated contents of the `main` rule.
+For booleans, a policy passes on a `true` value, and fails on a `false` value.
+Other types generally follow a zero or zero-length pattern for determining
+success.
+
+| Type | Passing Condition | Failing Condition |
+| ------- | ----------------- | -------------------------- |
+| Boolean | `true` | `false` |
+| String | `""` | Any non-zero length string |
+| Integer | `0` | Any non-zero value |
+| Float | `0.0` | Any non-zero value |
+| List | `[]` | Any non-zero length list |
+| Map | `{}` | Any non-zero length map |
+
+A value of main that falls outside of the above types will result in a policy
+error. As a special case, if `main` evaluates to an undefined value, the error
+message will indicate as such, with a reference to where the undefined value was
+encountered.
+
+## More Complex Example
+
+The simple example above is a full working example. In our experience with
+Sentinel, many policies can be representing using this simple form. However,
+to show more features of the language, a more complex example is shown below.
+This example is also a realistic example of what Sentinel may be used for.
+
+```sentinel
+import "units"
+
+memory = func(job) {
+ result = 0
+ for job.groups as g {
+ for g.tasks as t {
+ result += t.resources.memory else 0
+ }
+ }
+
+ return result
+}
+
+main = rule {
+ memory(job) < 1 * units.gigabyte
+}
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/language/lists.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/language/lists.mdx
new file mode 100644
index 0000000000..bc5269d8eb
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/language/lists.mdx
@@ -0,0 +1,135 @@
+---
+page_title: Sentinel Language - Lists
+sidebar_current: docs-language-lists
+description: Lists are a collection of zero or more values.
+layout: docs
+---
+
+# Language: Lists
+
+Lists are a collection of zero or more values.
+
+Lists can be created using by wrapping values in `[]` and separating them
+by commas. An optional trailing comma is allowed. List elements can be
+differing types. Examples:
+
+```sentinel
+[] // An empty list
+["foo"] // Single element list
+["foo", 1, 2, true] // Multi element list with different types
+["foo", [1, 2]] // List containing another list
+```
+
+A list can be [sliced](/sentinel/language/slices) to create sublists.
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for value inclusion in a list.
+
+## Accessing Elements
+
+List elements can be accessed with the syntax `name[index]` where
+`index` is zero-indexed.
+
+A negative index accesses the list in reverse. It is the same as
+reversing a list and then using a positive index. Similar to a positive
+index, it is bounded by the length of the list as a negative value.
+
+Accessing beyond the length of the list results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+a = ["foo", 1, true, [1, 2]]
+
+a[0] // "foo"
+a[2] // true
+a[4] // undefined
+a[-2] // true
+a[-4] // "foo"
+a[-5] // undefined
+a[3][1] // 2
+```
+
+## List Append
+
+Values can be appended to a list using the built-in
+[append](/sentinel/functions/append) function.
+
+This modifies the list in-place and returns
+[undefined](/sentinel/language/undefined). For more information on
+why `append` behaves this way, please read the full documentation for the
+[append](/sentinel/functions/append) function.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append([1,2], "foo") // [1, 2, "foo"]
+append([1,2], [3]) // [1, 2, [3]]
+append(1, 3) // error()
+```
+
+## List Concatenation
+
+Two lists can be concatenated using the `+` operator or the shorthand
+`+=` assignment operator. For the `+` operator, a new list is returned.
+For `+=`, the left-hand list is modified in place.
+
+Examples:
+
+```sentinel
+[1] + [2] // [1, 2]
+[1] + [[1]] // [1, [1]]
+[1] + 1 // error
+
+a = [1]
+a += [2] // a = [1, 2]
+a += 3 // error
+```
+
+## List Length
+
+The length of a list can be retrieved using the
+[length](/sentinel/functions/length) function.
+
+Examples:
+
+```sentinel
+length([]) // 0
+length(["foo"]) // 1
+```
+
+## Removing Items From a List
+
+You can use a combination of [list concatenation](#list-concatenation) and
+[slicing](/sentinel/language/slices) to remove elements from a list.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+a = a[:2] + a[3:] // [1, 2, 4, 5]
+```
+
+The shorthand shown here is effectively the same as `a[0:2] + a[3:length(a)]`,
+which creates a new list out of the concatenation two sub-lists composed of the
+first two elements, and the rest of the list starting at index 3. This
+effectively removes the 3rd element from the list (index 2).
+
+## List Comparison
+
+Lists may be [compared](/sentinel/language/boolexpr#comparison-operators)
+for equality. Lists are equal if they are of equal length and their
+corresponding elements are comparable and equal. Lists with the same elements
+but in a different order are not equal.
+
+```sentinel
+[1, 2] is [1, 2] // true
+[1, 2] is [2, 1] // false
+["a"] is ["a", "b"] // false
+["a", ["b", "c"]] is ["a", ["b", "c"]] // true
+```
+
+List comparison speed is O(N), meaning that the speed of the comparison is
+_linearly_ proportional to the number of elements in the list. The more
+elements, the more iterations that are necessary to verify equality.
+
+-> The N-value quoted above should account for the sum of the elements of _all_
+lists in the subjects of comparison, as list comparison will recurse into these
+lists to check for equality.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/language/logging-errors.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/language/logging-errors.mdx
new file mode 100644
index 0000000000..705a72cbd4
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/language/logging-errors.mdx
@@ -0,0 +1,52 @@
+---
+page_title: Sentinel Language - Logging and Errors
+sidebar_current: docs-language-log
+description: The built-in functions `print` and `error` can be used for logging and errors.
+layout: docs
+---
+
+# Language: Logging and Errors
+
+The built-in functions `print` and `error` can be used for logging
+and errors. Logging is helpful to understand how a policy is executing
+in development. And errors are useful to halting execution immediately in
+certain scenarios.
+
+## Print
+
+The `print` function is used to output debug information. The exact location
+where this print statements go is dependent on the host application and
+the Sentinel runtime itself.
+
+The `print` function takes zero or more Sentinel values as arguments.
+Each value is formatted for human-friendly output and space-separated.
+Example:
+
+```sentinel
+value = 42
+print("the value is", value) // the value is 42
+
+map = { "foo": false }
+print(map) // { "foo": false }
+
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // false
+```
+
+## Errors
+
+Certain actions in Sentinel, such as accessing an undefined variable,
+attempting to add two incompatible types, etc. result in _runtime errors_.
+These errors halt the execution of a policy immediately. The pass/fail result
+of a policy is considered fail.
+
+Runtime errors can be manually triggered using the `error` function.
+The `error` function behaves exactly like the `print` function except that
+execution is halted immediately.
+
+This should only be used in scenarios where the policy _must fail_ due to
+some unexpected or invalid condition. The line between `error` and
+[undefined](/sentinel/language/undefined) can sometimes be unclear. Undefined
+is recommended when a policy can conceivably recover or safely ignore the
+undefined behavior. An error should be used when the policy should fail and
+notify someone regardless.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/language/loops.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/language/loops.mdx
new file mode 100644
index 0000000000..622732bd35
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/language/loops.mdx
@@ -0,0 +1,92 @@
+---
+page_title: Sentinel Language - Loops
+sidebar_current: docs-language-loops
+description: >-
+ Loop statements allow you to execute a body of code for each element in a
+ collection or for some fixed number of times.
+layout: docs
+---
+
+# Language: Loops
+
+Loop statements allow you to execute a body of code for each element in
+a collection or for some fixed number of times.
+
+Loop statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## For Statements
+
+`for` statements allow repeated execution of a block for each
+element in a collection.
+
+Example:
+
+```sentinel
+// A basic sum
+count = 0
+for [1, 2, 3] as num {
+ count += num
+}
+```
+
+The syntax is `for COLLECTION as value`. This will iterate over the
+collection, assigning each element to `value`. In the example above,
+each element is assigned to `num`. The body is executed for each element.
+In the example above, the body adds `num` to the `count` variable. This
+creates a basic sum of all values in the collection.
+
+For a map, the assigned element is the key in the map. In the example
+below, `name` would be assigned map keys.
+
+```sentinel
+list = []
+for { "a": 1, "b": 2 } as name {
+ append(list, name)
+}
+
+print(list) // ["a" "b"]
+```
+
+An alternate syntax is `for COLLECTION as key, value`. This will assign
+both the key and value to a variable. For a list, the key is the element
+index. For a map, it is the key and value is assigned the element value.
+Example:
+
+```sentinel
+count = 0
+for { "a": 1, "b": 2 } as name, num {
+ count += num
+}
+
+print(count) // 3
+```
+
+## Scoping
+
+The body of a `for` statement creates a new
+[scope](/sentinel/language/scope). If a variable is assigned within
+the body of a for statement that isn't assigned in a parent scope,
+that variable will only exist for the duration of the body execution.
+
+Example:
+
+```sentinel
+for list as value {
+ a = 42
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+for list as value {
+ a = 42
+}
+
+print(a) // 18
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/language/maps.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/language/maps.mdx
new file mode 100644
index 0000000000..298bf9a567
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/language/maps.mdx
@@ -0,0 +1,121 @@
+---
+page_title: Sentinel Language - Maps
+sidebar_current: docs-language-maps
+description: >-
+ Maps are a collection of zero or more key/value pairs. These are useful for
+ storing values that are looked up by a unique key.
+layout: docs
+---
+
+# Language: Maps
+
+Maps are a collection of zero or more key/value pairs. These are useful
+for storing values that are looked up by a unique key.
+
+Maps can be created using `{}` and specifying key/value pairs. Keys and
+values can be differing types. An optional trailing comma is allowed.
+Examples:
+
+```sentinel
+// Empty map
+{}
+
+// Map with a single value on one line
+{ "key": "value" }
+
+// Map with multiple values with differing types on multiple lines
+{
+ "key": "value",
+ 42: true,
+}
+```
+
+Maps are **unordered**. When
+[looping](/sentinel/language/loops)
+over a map, the key/value pairs can be returned in any order.
+
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for key inclusion in a map.
+
+## Accessing Elements
+
+Map elements can be accessed with the syntax `name[key]`. This looks up
+a value by a key.
+
+Accessing a key that doesn't exist results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+map = { "key": "value", 42: true, }
+
+map["key"] // "value"
+map[42] // true
+map[0] // undefined
+```
+
+## Modifying or Adding Elements
+
+Elements can be added or modified in a map by assigning to `name[key]`.
+If the key doesn't exist, the value is added. If the key already exists,
+the value is overridden.
+
+```sentinel
+map = { "key": "value" }
+
+map[42] = true // Add a new key/value
+map["key"] = 12 // Modify the value of "key"
+```
+
+## Deleting Elements
+
+An element can be deleted from a map using the
+[delete](/sentinel/functions/delete) function.
+
+Examples:
+
+```sentinel
+map = { "key": "value" }
+delete(map, "key") // map is now empty
+delete(map, "other") // no effect for non-existent key
+```
+
+## Keys and Values
+
+The keys and values of a map can be retrieved as
+[lists](/sentinel/language/lists) using the
+[keys](/sentinel/functions/keys) and
+[values](/sentinel/functions/values) functions.
+
+Because maps are unordered, the keys and values are returned in an
+unspecified order. It should not be assumed that keys and values will be
+returned in a consistent order.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+## Map Comparison
+
+Maps may be [compared](/sentinel/language/boolexpr#comparison-operators) for
+equality. Maps are equal if they are of equal length and both their
+corresponding keys and values are comparable and equal.
+
+```sentinel
+{"foo": "bar"} is {"foo": "bar"} // true
+{"foo": "bar"} is {"baz": "bar"} // false
+{"foo": "bar"} is {"foo": "baz"} // false
+{"foo": "bar"} is {"foo": "bar", "baz": "qux"} // false
+{1: "a"} is {1.0: "a"} // true (int/float comparable)
+
+// also true (maps are not ordered):
+{"m": {"a": "b"}, "l": ["a"]} is {"l": ["a"], "m": {"a": " b"}}
+```
+
+-> The current worst-case comparison speed for maps is O(N²), or _quadratic
+time_. This is the square of the cumulative elements or the parent and all child
+maps contained within the map. Consider this when making comparisons on larger,
+more complex maps. This may be optimized in the future.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/language/parameters.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/language/parameters.mdx
new file mode 100644
index 0000000000..2e008d469b
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/language/parameters.mdx
@@ -0,0 +1,146 @@
+---
+page_title: Sentinel Language - Parameters
+sidebar_current: docs-language-parameters
+description: >-
+ Parameteres allow a Sentinel policy to describe variables that are expected
+ to be supplied by the calling application, in addition to any default value.
+layout: docs
+---
+
+# Language: Parameters
+
+Sentinel allows a policy author to supply parameters to help facilitate policy
+reuse and ensure sensitive values do not need to be hard-coded in a policy.
+
+Parameters are supplied by using the `param` keyword, followed by an identifier.
+A default value can also be supplied by using the `default` keyword.
+
+```sentinel
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+```
+
+Once declared, parameters can be used like any other variable, including being
+re-assigned.
+
+```sentinel
+param foo default 1 // 1 (default)
+
+foo = foo + 1 // 2
+```
+
+## Variable Descriptions
+
+You can supply a description to a parameter by adding a comment at the top of
+it. This value can be communicated to a specific implementation of Sentinel to
+provide information about what the parameter is for during configuration.
+
+```sentinel
+// An example parameter. Must be supplied or the policy will fail.
+param foo
+```
+
+## Supplying Parameter Values Using the Sentinel CLI
+
+In a production implementation, supplying parameters to a policy is an
+implementation-specific detail - see the documentation for your particular
+implementation for details.
+
+Using the [Sentinel CLI](/sentinel/commands), you can supply parameters one of
+four ways.
+
+### Supplying Parameter Values Using the Configuration File
+
+You can supply parameters using the
+[`param`](/sentinel/configuration#parameters) section of the
+[configuration file](/sentinel/configuration).
+
+```hcl
+param "foo" {
+ value = "bar"
+}
+```
+
+This method works for both `sentinel apply` and `sentinel test`.
+
+In addition to the above, you can supply targeted parameters to each
+[policy block](/sentinel/configuration#policies) in the configuration file.
+
+```hcl
+policy "foo" {
+ source = "foo.sentinel"
+ enforcement_level = "hard-mandatory"
+ params = {
+ "name" = "Sample"
+ }
+}
+```
+
+### Supplying Parameter Values Using the Environment
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can supply a value using environment variables - prefix the parameter with
+`SENTINEL_PARAM_`, using the name of the parameter to supply.
+
+```plaintext
+SENTINEL_PARAM_foo=bar sentinel apply policy.sentinel
+```
+
+### Supplying Parameter Values Using CLI Arguments
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can also use the `-param` CLI argument to supply parameter in a `key=value`
+pair.
+
+```plaintext
+sentinel apply -param foo=bar policy.sentinel
+```
+
+### Interactive CLI Prompting
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+If a required value has not been supplied when a policy is run with `sentinel apply`, it will be prompted for, along with its description:
+
+
+
+ $ sentinel apply policy.sentinel
+
+ policy.sentinel:2:7: requires value for parameter foo
+
+ An example parameter. Must be supplied or the policy will fail.
+
+
+ Values can be strings, floats, or JSON array or object values. To
+ force
+
+ strings, use quotes.
+
+
+ Enter a value: bar
+
+
+
+ Pass
+
+
+
+
+### CLI Value Format
+
+-> **NOTE:** This section contains details for the parameter features supported by `sentinel apply`.
+
+The CLI takes either strings, or JSON numbers, arrays, or maps. If you need a
+literal string value, quote the value.
+
+```sentinel
+foo // string
+42 // number (float)
+"42" // string ("42", without quotes)
+[1, 2] // array (list)
+{"a": "b"} // object (map)
+```
+
+-> **NOTE:** Boolean values are not supported by this method.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/language/rules.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/language/rules.mdx
new file mode 100644
index 0000000000..e31924d414
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/language/rules.mdx
@@ -0,0 +1,204 @@
+---
+page_title: Sentinel Language - Rules
+sidebar_current: docs-language-rules
+description: >-
+ Rules form the basis of a policy by representing behavior that is either
+ passing or failing (true or false).
+layout: docs
+---
+
+# Language: Rules
+
+Rules form the basis of a policy by representing behavior that is either passing
+or failing. A policy can be broken down into a set of rules. Breaking down a
+policy into a set of rules can make it more understandable and aids with
+testing.
+
+An example is shown below:
+
+```sentinel playground
+weather = "sunny"
+day = "wednesday"
+is_sunny = rule { weather is "sunny" }
+is_wednesday = rule { day is "wednesday" }
+
+main = rule {
+ is_sunny and
+ is_wednesday
+}
+```
+
+A rule contains a single expression. This can be a simple [boolean
+expression](/sentinel/language/boolexpr), or an expression representing the
+discovery of a set of violations using more in-depth expressions like [`filter`
+and `map`](/sentinel/language/collection-operations). You can split expressions
+into multiple lines for readability, as you would with any other expression
+within Sentinel.
+
+A rule is also _lazy_ and _memoized_. _Lazy_ means that the rule is only
+evaluated when it is actually required, not at the point that it is
+created. This is covered in more detail in the [lazy](#lazy-and-memoized) section.
+_Memoized_ means that the value is only computed once and then saved. Once
+a rule is evaluated, the result of it is reused anytime the rule is referenced
+again.
+
+## Making rules easier to understand
+
+Rules are an abstraction that make policies much more understandable
+by making it easier for humans to see what the policy is trying to do.
+
+For example, consider the policy before which doesn't abstract into rules:
+
+```sentinel
+main = rule {
+ ((day is "saturday" or day is "sunday") and homework is "") or
+ (day in ["monday", "tuesday", "wednesday", "thursday", "friday"] and
+ not school_today and homework is "")
+}
+```
+
+Assume that `day`, `homework`, and `school_day` are available.
+
+In plain English, this policy is trying to say: you can play with your friends
+only on the weekend as long as there is no homework, or during the week if
+there is no school and no homework.
+
+Despite the relatively simple nature of this policy (a few lines, about 5
+logical expressions), it is difficult to read and understand, especially if
+you've not seen it before.
+
+The same policy using rules as an abstraction:
+
+```sentinel
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+is_valid_weekend = rule { is_weekend and homework is "" }
+is_valid_weekday = rule { not is_weekend and not school_today and homework is "" }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+By reading the names of the rules, its much clearer to see what each individual
+part of the policy is trying to achieve. The readability is improved even
+further when adding comments to the rules to explain them further, which is
+a recommended practice:
+
+```sentinel
+// A weekend is Sat or Sun
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+// A valid weekend is a weekend without homework
+is_valid_weekend = rule { is_weekend and homework is "" }
+
+// A valid weekday is a weekday without school
+is_valid_weekday = rule { not is_weekend and not school_today and homework is "" }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+## "When" Predicates
+
+A rule may have an optional `when` predicate attached to it. When this is
+present, the rule is only evaluated when the predicate results in `true`.
+Otherwise, the rule is not evaluated and the result of the rule is always
+`true`.
+
+"When" predicates should be used to define the context in which the rule
+is semantically meaningful. It is common when translating human language
+policy into Sentinel to have scenarios such as "when the key has a prefix
+of `/account/`, the remainder must be numeric." In that example, when the
+key _does not_ have the specified prefix, the policy doesn't apply.
+
+Assume you have rules `is_prefix` and `is_numeric` to check both the
+conditions in the example above. An example is shown with and without
+the "when" predicate:
+
+```sentinel
+example_no_when = rule { (is_prefix and is_numeric) or not is_prefix }
+
+example_when = rule when is_prefix { is_numeric }
+```
+
+The rules are equivalent in behavior for all values of `is_prefix` and
+`is_numeric`, but the second rule is more succint and easier to understand.
+
+## Testing
+
+To verify a policy works correct, the
+[built-in Sentinel test framework](/sentinel/writing/testing)
+uses rules as the point where you can assert behavior. You say that
+you expect certain rules to be true or false. By doing so, you ensure that
+the policy results in the value you expect using the rule flow that you
+expect.
+
+Therefore, in addition to readability, we recommend splitting policies into
+rules to aid testability.
+
+## Non-Boolean Values
+
+Sentinel supports non-boolean values in rules. This can be useful for passing
+more detail along to a calling integration when more detail is required than a
+boolean value would be able to communicate. This data shows up in the [policy
+trace](/sentinel/writing/tracing) and can be utilized in different ways
+depending on the integration you are using Sentinel with.
+
+Consider a different take on our policy above:
+
+```sentinel
+// A policy to check a set of scheduled days to determine what days you can't
+// come out and play, based on the day not being a weekend day and there being
+// homework to do.
+
+param days
+
+main = rule {
+ filter days as d {
+ d.day not in ["saturday", "sunday"] and
+ d.homework is not ""
+ }
+}
+```
+
+When given a value in the set of `days` that has a `day` that is a weekday, and
+`homework` present, the policy will fail. Additional, when tracing was enabled,
+the days that were detected as violating the policy would be given in the trace
+for the `main` rule.
+
+Generally, for non-boolean values, a policy fails on non-zero data. See the
+details on [the `main` rule](/sentinel/language#main) for more details.
+
+The result of a rule must be either a boolean, string, integer, float, list, or
+map. All other types will result in a runtime error.
+
+## Lazy and Memoized
+
+A rule is _lazy_ and _memoized_.
+
+_Lazy_ means that the rule is only evaluated when it is actually required,
+not at the point that it is created. And _memoized_ means that the value is
+only computed once and then saved. Once a rule is evaluated, the result of it
+is reused anytime the rule is referenced again.
+
+Both of these properties have important implications for Sentinel
+policies:
+
+**Performance:** Rules are a way to improve the performance of a Sentinel
+policy. If you have a boolean expression that is reused a lot, a rule will
+only be evaluated once. In the example shown above, `is_weekend` is used
+multiple times, but will only have to be evaluated once.
+
+**Behavior:** Rules accessing variables see the value of those variables
+at _the time they are evaluated_. This can lead to surprising behavior
+in some cases. For example:
+
+```sentinel playground
+a = 1
+b = rule { a == 1 }
+a = 2
+main = b
+```
+
+In this example, `main` will actually result to `false`. It is evaluated
+when it is needed, which is when the policy executes `main`. At this point,
+`a` is now 2 and `b` has not been evaluated yet. Therefore, `b` becomes `false`.
+All future references to `b` will return `false` since a rule is memoized.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/language/scope.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/language/scope.mdx
new file mode 100644
index 0000000000..f498e26dc2
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/language/scope.mdx
@@ -0,0 +1,47 @@
+---
+page_title: Sentinel Language - Scope
+sidebar_current: docs-language-scope
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Scope
+
+Scope is the context in which variables are created and accessed. If a
+variable exists in a parent scope, it is accessed and can be modified.
+Otherwise, the variable is created in your current scope.
+
+Scopes are created with _blocks_. A block is a possibly empty sequence
+of statements within matching brace brackets `{}`. You may nest blocks
+to create nested scopes.
+
+## Package Scope
+
+The package scope is the top level scope within a policy file and encapsulates
+the entire file contents. Imports, parameters and named functions must be
+declared within the package scope.
+
+## Implicit Scopes
+
+Each `any`, `all`, and `for` statement is considered to be in its own block.
+Note that `if` statements _do not_ create their own block.
+
+## Examples
+
+The example policy below shows various effects of scopes:
+
+```sentinel
+a = 1
+print(a) // 1
+
+f = func() {
+ print(a) // 1
+ a = 12
+ b = 1
+ return undefined
+}
+f()
+
+print(a) // 12
+print(b) // undefined
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/language/slices.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/language/slices.mdx
new file mode 100644
index 0000000000..82dc7f4c18
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/language/slices.mdx
@@ -0,0 +1,45 @@
+---
+page_title: Sentinel Language - Slices
+sidebar_current: docs-language-slices
+description: >-
+ Slices are an efficient way to create a substring or sublist from an existing
+ string or list, respectively.
+layout: docs
+---
+
+# Language: Slices
+
+Slices are an efficient way to create a substring or sublist from an
+existing string or list, respectively.
+
+The syntax for creating a slice is:
+
+```sentinel
+a[low : high]
+```
+
+`low` is the lowest index to slice from. The resulting slice includes
+the value at `low`. `high` is the index to slice to. The resulting slice
+will not include the value at `high`. The length of the resulting list
+or string is always `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+
+a = "hello"
+b = a[1:4] // "ell"
+```
+
+## Convenience Shorthands
+
+Some convenience shorthands are available by omitting the value for either the
+low or high index in the slice expression: omitting `low` implies a low index of
+0, and omitting `high` implies a high index of the length of the list.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+
+b = a[:2] // [1, 2] (same as a[0:2])
+b = a[2:] // [3, 4, 5] (same as a[2:length(a)])
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/language/spec.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/language/spec.mdx
new file mode 100644
index 0000000000..d8efb378cd
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/language/spec.mdx
@@ -0,0 +1,1333 @@
+---
+page_title: Sentinel Language Specification
+sidebar_current: docs-language-spec
+description: This is the specification for the Sentinel policy language.
+layout: docs
+---
+
+# Sentinel Language Specification
+
+This is the specification for the Sentinel policy language.
+
+The Sentinel language is designed with policy enforcement in mind. It is dynamically typed and garbage collected and has explicit support for _rule_ construction representing boolean logic.
+
+The language is designed to be easy to learn and use by non-programmers. It is expected to be embedded within applications.
+
+## Table of Contents
+
+
+
+
+- [Source code representation](#source-code-representation)
+- [Declarations and Scope](#declarations-and-scope)
+- [Blocks](#blocks)
+- [Lexical Elements](#lexical-elements)
+ - [Comments](#comments)
+ - [Identifiers](#identifiers)
+ - [Keywords](#keywords)
+ - [Operators and Delimiters](#operators-and-delimiters)
+ - [Integer Literals](#integer-literals)
+ - [Floating-point Literals](#floating-point-literals)
+ - [String Literals](#string-literals)
+ - [Implicit Line Joining](#implicit-line-joining)
+ - [Whitespace](#whitespace)
+ - [Semicolons](#semicolons)
+- [Variables](#variables)
+- [Undefined](#undefined)
+- [Expressions](#expressions)
+ - [Operand](#operand)
+ - [Primary Expressions](#primary-expressions)
+ - [Null](#null)
+ - [Booleans](#booleans)
+ - [Boolean Literals](#boolean-literals)
+ - [Boolean Expressions](#boolean-expressions)
+ - [List Literals](#list-literals)
+ - [Map Literals](#map-literals)
+ - [Function Literals](#function-literals)
+ - [Rule Expressions](#rule-expressions)
+ - [Index Expressions](#index-expressions)
+ - [Selectors](#selectors)
+ - [Slice Expressions](#slice-expressions)
+ - [Calls](#calls)
+ - [Operators](#operators)
+ - [Operator Precedence](#operator-precedence)
+ - [Arithmetic Operators](#arithmetic-operators)
+ - [Integer operators](#integer-operators)
+ - [Integer overflow](#integer-overflow)
+ - [Floating-point operators](#floating-point-operators)
+ - [String Concatenation](#string-concatenation)
+ - [List Concatenation](#list-concatenation)
+ - [Comparison Operators](#comparison-operators)
+ - [Logical Operators](#logical-operators)
+ - [Set Operators](#set-operators)
+ - [Matches Operator](#matches-operator)
+ - [Else Operator](#else-operator)
+ - [Quantifier Expressions (any, all, filter, map)](#quantifier-expressions-any-all-filter-map)
+- [Statements](#statements)
+ - [Expression Statements](#expression-statements)
+ - [Assignments](#assignments)
+ - [If Statements](#if-statements)
+ - [Case Statements](#case-statements)
+ - [For Statements](#for-statements)
+ - [Break Statements](#break-statements)
+ - [Continue Statements](#continue-statements)
+ - [Return Statements](#return-statements)
+- [Imports](#imports)
+- [Parameters](#parameters)
+- [Built-in Functions](#built-in-functions)
+ - [Length](#length)
+ - [Collections](#collections)
+ - [List Append](#list-append)
+ - [Map Delete](#map-delete)
+ - [Keys and Values](#keys-and-values)
+ - [Range](#range)
+ - [Type Conversion](#type-conversion)
+ - [Printing](#printing)
+ - [Errors](#errors)
+- [Program Execution](#program-execution)
+
+
+
+## Source code representation
+
+Source code is Unicode text encoded in UTF-8. A "character" referenced in this document refers to a Unicode code point. Each code point is distinct: upper and lower case letters are different characters.
+
+The underscore character \_ (U+005F) is considered a "letter".
+
+```ebnf
+letter = unicode_letter | "_" .
+decimal_digit = "0" … "9" .
+octal_digit = "0" … "7" .
+hex_digit = "0" … "9" | "A" … "F" | "a" … "f" .
+```
+
+## Declarations and Scope
+
+A declaration binds an identifier to a value. An identifier is declared at the point it is first assigned. An assignment is considered a first assignment if the identifier hasn't already been previously declared in the current scope or any parent scopes.
+
+The scope of an identifier is the extent of source text in which the identifier denotes the specified value. Sentinel is lexically scoped using blocks.
+
+An identifier declared in a block may be redeclared in an inner block. While the identifier of the inner declaration is in scope, it denotes the value assigned in the inner declaration.
+
+## Blocks
+
+A block is a possibly empty sequence of statements within matching brace brackets.
+
+```ebnf
+Block = "{" StatementList "}" .
+StatementList = { Statement ";" } .
+```
+
+Blocks nest and affect scoping.
+
+In addition to explicit blocks in the source code, there are implicit blocks:
+
+1. The universe block encompasses all source text.
+2. Each program has a program block containing all source text for that program.
+3. Each "any", "all", and "for" statements is considered to be in its own implicit block. "if" does not create an implicit block.
+
+## Lexical Elements
+
+### Comments
+
+Comments are sections of source text used for documentation.
+
+```plaintext
+# Single line comment
+// Single line comment
+/* multi
+line
+ comment */
+```
+
+Three forms of comments are supported: two single-line forms and one multi-line form. A single-line comment begins with the token `//` or `#`. Everything between the starting token and the end of the line is ignored.
+
+A multi-line comment begins with the token `/*` and ends with the token `*/`. Everything between `/*` and `*/` is ignored.
+
+A comment may not start inside a string literal or inside another comment.
+
+A multi-line comment containing no newlines acts like a space.
+
+### Identifiers
+
+Identifiers name program entities such as rules, variables, and functions. An identifier is a sequence of one or more letters and digits. The first character in an identifier must be a letter.
+
+```ebnf
+identifier = letter { letter | unicode_digit } .
+```
+
+```sentinel
+a
+_a
+_A
+αβ
+```
+
+#### Pre-Declared Identifiers
+
+The following identifiers are implicitly declared in the [universe block](#blocks):
+
+```plaintext
+Constants:
+ false null true undefined
+
+Functions:
+ append bool delete error float int keys length print range string values
+```
+
+### Keywords
+
+The following keywords are reserved and may not be used as identifiers:
+
+```plaintext
+all
+any
+as
+break
+case
+continue
+default
+else
+empty
+filter
+for
+func
+if
+import
+map
+param
+return
+rule
+when
+```
+
+### Operators and Delimiters
+
+The following character sequences represent operators, delimiters, and other special tokens:
+
+```plaintext
++
+-
+*
+/
+%
++=
+-=
+*=
+/=
+%=
+==
+<
+>
+=
+!
+!=
+<=
+>=
+( )
+[ ]
+{ }
+,
+.
+:
+;
+and
+contains
+else
+in
+is
+matches
+not
+or
+xor
+```
+
+As a special case, `else` is both a keyword and operator, depending on the context. See [Else Operator](#else-operator) for more details.
+
+### Integer Literals
+
+An integer literal is a sequence of digits representing an integer constant. An optional prefix sets a non-decimal base: `0` for octal, `0x` or `0X` for hexadecimal. In hexadecimal literals, letters `a-f` and `A-F` represents values 10 through 15.
+
+Integers are signed 64-bit values (-9223372036854775808 to 9223372036854775807).
+
+```ebnf
+int_lit = decimal_lit | octal_lit | hex_lit .
+decimal_lit = ( "1" … "9" ) { decimal_digit } .
+octal_lit = "0" { octal_digit } .
+hex_lit = "0" ( "x" | "X" ) hex_digit { hex_digit } .
+```
+
+```
+42
+0600
+0xBadFace
+170141183460469231731687303715884105727
+```
+
+### Floating-point Literals
+
+A floating-point literal is a decimal representation of a floating-point constant. It has an integer part, a decimal point, a fractional part, and an exponent part. The integer and fractional part comprise decimal digits; the exponent part is an e or E followed by an optionally signed decimal exponent. One of the integer part or the fractional part may be elided; one of the decimal point or the exponent may be elided.
+
+Floating-point numbers are IEEE-754 64-bit floating numbers.
+
+```ebnf
+float_lit = decimals "." [ decimals ] [ exponent ] |
+ decimals exponent |
+ "." decimals [ exponent ] .
+decimals = decimal_digit { decimal_digit } .
+exponent = ( "e" | "E" ) [ "+" | "-" ] decimals .
+```
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+### String Literals
+
+String literals are character sequences between double quotes, as in "bar". Within the quotes, any character may appear except newline and unescaped double quote. The text between the quotes forms the value of the literal.
+
+A multi-character sequence beginning with a backslash encode values in various formats.
+
+Backslash escapes allow arbitrary values to be encoded as ASCII text. There are four ways to represent the integer value as a numeric constant: `\x` followed by exactly two hexadecimal digits; `\u` followed by exactly four hexadecimal digits; `\U` followed by exactly eight hexadecimal digits, and a plain backslash `\` followed by exactly three octal digits. In each case the value of the literal is the value represented by the digits in the corresponding base.
+
+After a backslash, certain single-character escapes represent special values:
+
+```plaintext
+\a U+0007 alert or bell
+\b U+0008 backspace
+\f U+000C form feed
+\n U+000A line feed or newline
+\r U+000D carriage return
+\t U+0009 horizontal tab
+\v U+000b vertical tab
+\\ U+005c backslash
+\" U+0022 double quote
+```
+
+The three-digit octal (\nnn) and two-digit hexadecimal (\xnn) escapes represent individual bytes of the resulting string; all other escapes represent the (possibly multi-byte) UTF-8 encoding of individual characters. Thus inside a string literal \377 and \xFF represent a single byte of value 0xFF=255, while ÿ, \u00FF, \U000000FF and \xc3\xbf represent the two bytes 0xc3 0xbf of the UTF-8 encoding of character U+00FF.
+
+A string value is a (possibly empty) sequence of bytes. Strings are immutable: once created, it is impossible to change the contents of a string.
+
+The length of a string s (its size in bytes) can be discovered using the built-in function `length`. An individual character (of type string) can be accessed by integer indices 0 through `length(s)-1`.
+
+```ebnf
+string_lit = `"` { unicode_value | byte_value } `"` .
+unicode_value = unicode_char | little_u_value | big_u_value | escaped_char .
+byte_value = octal_byte_value | hex_byte_value .
+octal_byte_value = `\` octal_digit octal_digit octal_digit .
+hex_byte_value = `\` "x" hex_digit hex_digit .
+little_u_value = `\` "u" hex_digit hex_digit hex_digit hex_digit .
+big_u_value = `\` "U" hex_digit hex_digit hex_digit hex_digit
+ hex_digit hex_digit hex_digit hex_digit .
+escaped_char = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | `"` ) .
+```
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+### Implicit Line Joining
+
+Expressions can be split over more than one line. For example:
+
+```sentinel
+a or
+b or # This is a comment
+c
+```
+
+Implicitly continued lines can have trailing comments. Blank continued lines are allowed.
+
+### Whitespace
+
+Whitespace is needed to separate tokens, but no distinction is made between the number and combination of whitespace characters. Whitespace characters can be space (U+0020), horizontal tabs (U+0009), carriage returns (U+000D), and newlines (U+000A).
+
+```sentinel
+a or b
+
+a or b
+
+a or
+ b
+
+rule { a or b }
+
+rule {
+ a or b }
+
+rule {
+ a or
+ b
+}
+```
+
+### Semicolons
+
+The formal grammar uses semicolons ";" as terminators in a number of productions.
+It is idiomatic Sentinel source to omit semicolons in most cases.
+
+A semicolon is automatically inserted into the token stream immediately after
+a line's final token if that token is:
+
+- An identifier
+- An integer, float, or string literal
+- The keyword `break`, `continue`, or `return`
+- The delimiter `)`, `]`, or `}`
+
+## Variables
+
+A variable is a storage location for holding a value.
+
+A variable has a dynamic type, which is the concrete type of the value assigned to the variable at run time. The dynamic type may vary during execution and change as a result of further assignments.
+
+A variable's value is retrieved by referring to the variable in an expression; it is the most recent value assigned to the variable. If a variable has not yet been declared (initially assigned), it is an error.
+
+```sentinel
+x = 7 // x is type int
+x = "foo" // x is now type string
+
+x = y // error if y is not previously assigned
+```
+
+## Undefined
+
+The value denoted by the keyword `undefined` represents undefined behavior or values. It can be created directly using the keyword `undefined`. It is also returned as a result of expressions in specified cases.
+
+`undefined` is a valid operand for any operations. Only `undefined or true` will result in true. All other operations result in `undefined`. An exception is if `undefined` is not reached as a result of short-circuit operations.
+
+```sentinel
+undefined OR true = true
+undefined OR false = undefined
+undefined OR undefined = undefined
+undefined AND true = undefined
+undefined AND false = undefined
+undefined AND undefined = undefined
+undefined XOR true = undefined
+undefined XOR false = undefined
+undefined XOR undefined = undefined
+
+// Short-circuit examples
+false OR true OR undefined = true
+false OR undefined OR true = true
+true AND false AND undefined = false
+true AND undefined AND false = undefined
+
+// Non-logical operators
+undefined + 5 = undefined
+-undefined = undefined
+!undefined = undefined
+```
+
+If the result of the main rule is `undefined`, it is treated as `false` but is indicative of erroneous logic.
+
+## Expressions
+
+### Operand
+
+Operands denote the elementary values in an expression. An operand may be a literal, an identifier denoting a variable, rule, or function, or a parenthesized expression.
+
+```ebnf
+Operand = Literal | OperandName | MethodExpr | "(" Expression ")" .
+Literal = BasicLit | MapLit | ListLit | FunctionLit | RuleLit .
+BasicLit = int_lit | float_lit | string_lit .
+OperandName = identifier .
+```
+
+### Primary Expressions
+
+Primary expressions are the operands for unary and binary expressions.
+
+```ebnf
+PrimaryExpr =
+ Operand |
+ PrimaryExpr Selector |
+ PrimaryExpr Index |
+ PrimaryExpr Slice |
+ PrimaryExpr Arguments .
+
+Selector = "." identifier .
+Index = "[" Expression "]" .
+Slice = "[" [ Expression ] ":" [ Expression ] "]" .
+Arguments = "(" [ Expression { "," Expression } ] ")" .
+```
+
+```sentinel
+x
+2
+(s + ".txt")
+f(3.1415, true)
+m["foo"]
+s[i : j + 1]
+obj.color
+f.p[i].x()
+x is empty
+x is not empty
+```
+
+### Null
+
+The reserved word `null` denote the singleton value `null`. Null represents the explicit absence of a value. Behavior of `null` within expressions is specified explicitly for each expression.
+
+### Booleans
+
+### Boolean Literals
+
+The reserved words `true` and `false` denote objects that represent the boolean values `true` and `false`, respectively. These are boolean literals.
+
+```ebnf
+BoolLit = "true" | "false" .
+```
+
+#### Boolean Expressions
+
+Boolean expressions are expressions that must result in a boolean value. Any other value type becomes the `undefined` value.
+
+```ebnf
+BoolExpr = Expression .
+```
+
+### List Literals
+
+A list literal denotes a list, which is an integer indexed collection of values.
+
+```ebnf
+ListLit = "[" [ ElementList [ "," ] ] "]" .
+ElementList = Element { "," Element } .
+Element = Expression | LiteralValue .
+```
+
+A list may contain zero or more values. The number of values in a list is its length.
+
+A list has a set of indices. An empty list has an empty set of indices. A non-empty list has the index set `{0...n - 1}` where `n` is the length of the list. Attempting to access a list using an index that is not a member of its set of indices results in the `undefined` value.
+
+### Map Literals
+
+A map literal denotes a map, which is an unordered group of elements indexed by a set of unique keys.
+
+```ebnf
+MapLit = "{" [ KeyedElementList [ "," ] ] "}" .
+KeyedElementList = KeyedElement { "," KeyedElement } .
+KeyedElement = Element ":" Element .
+```
+
+Keys can only be a boolean, numeric, or string type.
+
+The value of a non-existent key is the `undefined` value.
+
+### Function Literals
+
+A function literal represents a function.
+
+```ebnf
+FunctionLit = "func" Function .
+Function = Parameters FunctionBody .
+FunctionBody = Block .
+Parameters = "(" [ IdentifierList [ "," ] ] ")" .
+IdentifierList = identifier { "," identifier } .
+```
+
+```sentinel
+func(a, b) { return b }
+```
+
+Function literals are only allowed in the file scope. They may refer to variables defined in surrounding blocks.
+
+A function must terminate with a return statement. If a function has no meaningful return value, it should return `undefined`.
+
+### Rule Expressions
+
+A rule is an expression that is evaluated lazily and the result is memoized.
+
+If the optional "when" predicate is present, the rule is evaluated only when
+the "when" boolean expression results in true. If the predicate is false,
+the rule is not evaluated and returns true. The predicate is evaluated when
+the rule would be evaluated; it is also lazy and memoized in the same way.
+
+```ebnf
+RuleExpr = "rule" [ "when" BoolExpr ] "{" Expr "}" .
+```
+
+```sentinel
+rule { x is y }
+
+rule {
+ x == "value" or
+ y == "other"
+}
+
+rule when x is y { y > 42 }
+
+rule { map ["a", "b", "c"] as _, id {
+ { "id": id }
+}}
+```
+
+### Index Expressions
+
+A primary expression of the form `a[x]` denotes the element of a list or map indexed by x. The value x is called the index or key.
+
+For `a` of type map:
+
+- If `a` contains a key `x`, `a[x]` is the map value with key `x`.
+- If `a` does not contain key `x`, `a[x]` is the `undefined` value.
+
+For `a` of type list:
+
+- `x` must be an integer
+- `x` must be in the range `[-1 * length(a), length(a)-1]`
+- If `x` is contained in the set of indices of `a`, `a[x]` is the list element at index `x`. If `x` is negative, `a[x]` is equivalent to `a[length(a)+x]`.
+- If `x` is not contained in the set of indices of `a`, `a[x]` is the `undefined` value.
+
+For `a` of value `null`:
+
+- `a[x]` is the `undefined` value.
+
+Otherwise `a[x]` is an error.
+
+### Selectors
+
+For a primary expression x, the selector expression `x.f` denotes the field `f` of the value `x`. The identifier `f` is called the selector. The type of the selector expression is the type of the selector.
+
+As a special case, selectors can be [reserved words](#keywords) and keyword [operators](#operators-and-delimiters), but cannot be any other non-identifier element.
+
+Selectors are used to access data from [imports](#imports). The first primary expression `x` in `x.f` denotes the import name. The field `f` is the selector to access data from the import.
+
+Selectors may also be used to access map data with static keys. They are syntactic sugar over index expressions. `math.pi` is equivalent to `math["pi"]` and exhibit the same limitations and behavior as an index expression. Selectors cannot, however, be used to assign values to map data.
+
+Selectors on undefined result in undefined.
+
+```sentinel
+math.pi
+time.pst.hour
+```
+
+### Slice Expressions
+
+Slice expressions construct a substring or list from a string or list.
+
+The primary expression
+
+```sentinel
+a[low : high]
+```
+
+constructs a substring or list. The indices `low` and `high` select which elements of operand `a` appear in the result. The result has indices starting at 0 and length equal to `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+```
+
+For convenience, any of the indices may be omitted. A missing `low` index defaults to zero; a missing `high` index defaults to the length of the sliced operand:
+
+```sentinel
+a[2:] // same as a[2 : length(a)]
+a[:3] // same as a[0 : 3]
+a[:] // same as a[0 : length(a)]
+```
+
+The indices are in range if `0 <= low <= high <= length(a)`, otherwise they are out of range. If the indices are out of range at run time, the result is the `undefined` value.
+
+If `a` is the value `null`, the result is the `undefined` value.
+
+If `a` is any other value type, it is an error.
+
+### Calls
+
+```ebnf
+CallExpr = identifier Arguments .
+```
+
+Given an expression `f` where `f` is a function value:
+
+```sentinel
+f(a1, a2, … an)
+```
+
+calls `f` with arguments `a1, a2, ... an`. The type of the expression is the result of `f`. The arguments are evaluated left to right. Arguments are passed by value.
+
+### Operators
+
+```ebnf
+Expression = UnaryExpr | Expression binary_op Expression .
+UnaryExpr = PrimaryExpr | unary_op UnaryExpr .
+
+binary_op = logical_op | set_op | rel_op | add_op | mul_op | else_op .
+logical_op = "and" | "or" | "xor" .
+set_op = ["not"] ( "contains" | "in" ).
+rel_op = "==" | "!=" | "<" | "<=" | ">" | ">=" |
+ "is" | "is not" | "matches" | "not matches" .
+add_op = "+" | "-" .
+mul_op = "*" | "/" | "%" .
+else_op = "else" .
+unary_op = "+" | "-" | "!" | "not" | empty_op | defined_op .
+empty_op = "is empty" | "is not empty" .
+defined_op = "is defined" | "is not defined" .
+```
+
+#### Operator Precedence
+
+Unary operators have the highest precedence.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Binary operators of the same precedence associate from left to right. For instance, x / y _ z is the same as (x / y) _ z.
+
+### Arithmetic Operators
+
+Arithmetic operators apply to numeric values and yield a result of the same type when both operands are the same.
+
+Arithmetic operations between integer and floating-point types result in a floating-point value with the integer operand treated as a floating-point value for purposes of calculation.
+
+Arithmetic operations between numeric values (integer, floating-point) and non-numeric values (strings, lists) are not permitted.
+
+All five supported arithmetic operators (+, -, \*, /, %) apply to both integer and floating-point types; + also applies to strings.
+
+```plaintext
++ sum integers, floats, strings, lists
+- difference integers, floats
+* product integers, floats
+/ quotient integers, floats
+% remainder integers, floats
+```
+
+#### Integer operators
+
+For two integer values x and y, the integer quotient `q = x / y` and remainder `r = x % y` satisfy the following relationships:
+
+```sentinel
+x = q*y + r and |r| < |y|
+```
+
+with x / y truncated towards zero.
+
+```plaintext
+ x y x / y x % y
+ 5 3 1 2
+-5 3 -1 -2
+ 5 -3 -1 2
+-5 -3 1 -2
+```
+
+As an exception to this rule, if the dividend x is the most negative value for the int type of x (-9223372036854775808), the quotient `q = x / -1` is equal to x (and r = 0).
+
+If the divisor is a constant, it must not be zero. If the divisor is zero at run time, an error occurs.
+
+#### Integer overflow
+
+For signed integers, the operations `+`, `-`, and `*` may legally overflow and the resulting value exists and is deterministically defined by the signed integer representation, the operation, and its operands. No exception is raised as a result of overflow.
+
+#### Floating-point operators
+
+For floating-point numbers, +x is the same as x, while -x is the negation of x. The result of a floating-point division by zero is not specified beyond the IEEE-754 standard.
+
+#### String Concatenation
+
+Strings can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = "hi"
+y = "hello"
+x = x + ", " + y // "hi, hello"
+x += " and good bye" // "hi, hello and good bye"
+```
+
+String addition creates a new string by concatenating the operands.
+
+#### List Concatenation
+
+Lists can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = [1, 2]
+x = x + [2, 3] // [1, 2, 2, 3]
+x += [4] // [1, 2, 2, 3, 4]
+```
+
+List addition creates a new list by concatenating the operands.
+
+### Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+In any comparison, the two operands must be equivalent types. The only exception are integers and floats, which are considered numeric and may be compared. Any comparison between other non-matching types results in the `undefined` value.
+
+The equality operators `==`, `!=`, `is`, and `is not` apply to operands that are comparable. The ordering operators `<`, `<=`, `>`, and `>=` apply to operands that are ordered. The behavior of `is` with `==` and `is not` with `!=` is identical. These terms and the result of the comparisons are defined as follows:
+
+- Boolean values are comparable. Two boolean values are equal if they are either both true or both false.
+- Integer values are comparable and ordered, in the usual way.
+- Floating point values are comparable and ordered, as defined by the IEEE-754 standard.
+- An integer compared with floating point value treats the integer as the converted floating point value.
+- String values are comparable and ordered, lexically byte-wise.
+- Lists are comparable. Lists are equal if they are of equal length and their
+ corresponding elements are comparable and equal.
+- Maps are comparable. Maps are equal if they are of equal length and both their
+ corresponding keys and values are comparable and equal.
+
+If either operand is the `undefined` value, the result of the expression is the `undefined` value.
+
+### Emptiness Comparisons
+
+Checking the emptiness of an object can be achieved by using one of the emptiness expressions, `is empty` or `is not empty`. Although similar to [Comparison Operators](#comparison-operators) in that they yield a boolean value and read as though a comparison is taking place, both `is empty` and `is not empty` are evaluated as a multi-word expression.
+
+An emptiness comparison can only be performed against collections, strings or `undefined`, with the same rules as the [built-in length](/sentinel/functions/length) function.
+
+```sentinel
+"" is empty // true
+"foo" is empty // false
+[] is empty // true
+[1] is empty // false
+{} is empty // true
+{"a": "b"} is empty // false
+
+"" is not empty // false
+"foo" is not empty // true
+[] is not empty // false
+[1] is not empty // true
+{} is not empty // false
+{"a": "b"} is not empty // true
+
+undefined is empty // undefined
+undefined is not empty // undefined
+```
+
+### Logical Operators
+
+Logical operators apply to boolean values and yield a boolean result.
+
+Logical operators are evaluated left-to-right and perform short-circuit logic. The right-hand side is not guaranteed to be evaluated if a short-circuit can be performed.
+
+```plaintext
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+### Set Operators
+
+The set operators `contains` and `in` test for set inclusion for lists
+and maps, and substring inclusion for strings.
+
+Set operators may be negated by prefixing the operator with `not`: `not contains` and `not in`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+`contains` tests if the left-hand collection contains the right-hand value. For lists, this tests equality of any value. For maps, this tests equality of any key. For strings, this tests for substring existence.
+
+`in` tests if the right-hand collection contains the left-hand value. The behavior is equivalent to `contains`.
+
+The collection must be a list, map, or string. If it is the `undefined` value, the result is the `undefined` value. For any other value, the result is an error.
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+
+"test" contains "est" // true
+"test" contains "best" // false
+"test" in "testing" // true
+"best" in "testing" // false
+```
+
+### Matches Operator
+
+The matches operator tests if a string matches a regular expression.
+
+The matches operators may be negated by prefixing the operator with `not`: `not matches`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+The left-hand value is the string to test. The right-hand value is a string value representing a regular expression.
+
+The syntax of the regular expression is the same general syntax used by Python, Ruby, and other languages. More precisely, it is the syntax accepted by RE2. The regular expression is not anchored by default; any anchoring must be explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+If either operand is `undefined`, the result is the `undefined` value. For any other non-string value, the result is an error.
+
+### Else Operator
+
+Else operators can be used to provide a default value for an expression that may evaluate to the `undefined` value. Else operators return their left-hand value unless it is `undefined`, otherwise they return their right-hand value.
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else null
+```
+
+### Quantifier Expressions (any, all, filter, map)
+
+Quantifiers are expressions that apply a predicate to a collection (list or map). The purpose of the quantifier is to produce a result based on its particular type.
+
+`any` and `all` expressions are existential and universal quantifiers, respectively. `any` expressions are equivalent to a chain of `or` and `all` expressions are equivalent to a chain of `and`. Both expressions implement short-circuiting equivalent to logical operators.
+
+The `map` expression is an apply-to-all quantifier and returns a new list for all values in the input collection. The output list will be the same length as the input collection.
+
+The `filter` expression is a subset quantifier and returns the subset of all values in the input collection for which the supplied predicate applies. This will be zero or more elements, up to the length of the input.
+
+The body of a quantifier expression is a boolean expression.
+
+`any` returns the boolean value `true` if any value in the collection expression results in the body expression evaluating to `true`. If the body expression evaluates to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression result in the body expression evaluating to `true`. If any value in the collection expression result in the body expression evaluating to `false`, the all expression returns `false`.
+
+For empty collections, `any` returns false and `all` returns `true`.
+
+`map` always returns a list, regardless of if the input collection is a list itself; maps (as in the data type) also return lists when processed through `map`. Each element is the result of the supplied expression body.
+
+`filter` returns a list or map, the same type as its input collection. Only the elements for which the expression body evaluated to `true` will be returned. If an iteration of the expression body results in `undefined`, the entire result set is `undefined`.
+
+When the collection is a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When the collection is a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+QuantExpr = QuantOp Expression "as" [ identifier "," ] identifier "{" BoolExpr "}" .
+QuantOp = "all" | "any" | "filter" | "map" .
+```
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" } // true if every iterations is true
+any group.tasks as t { t.driver is "vmware" } // true if any iteration is true
+map group.tasks as t { { "driver": t.driver } } // [{"driver": "vmware"}, ...]
+filter group.tasks as t { t.driver is "vmware" } // subset of tasks that is true
+```
+
+## Statements
+
+### Expression Statements
+
+Function call expressions may exist in the statement context.
+
+```ebnf
+ExpressionStmt = Expression .
+```
+
+### Assignments
+
+Assignments set the value of an expression to the value of another expression.
+
+```ebnf
+Assignment = AssignExpr assign_op Expression .
+AssignExpr = identifier | IndexExpr .
+assign_op = [ add_op | mul_op ] "=" .
+```
+
+The assignment `x op= y` is equivalent to `x = x op (y)` for supported values of `op`.
+
+```sentinel
+a = 1
+b[12] = 42
+c["key"] = "value"
+
+a *= 12
+b[12] += 8
+```
+
+Assignments to lists and maps can be carried out using [index expressions](#index-expressions), with the operands of the index expression being evaluated alongside the right hand side expression in a right-to-left (RHS, LHS) order.
+
+In addition to the rules detailed in the section describing their use, the following rules apply when assigning to maps or lists using index expressions:
+
+- The [variable](#variables) must exist and be a list or map. Attempts to use an index assignment on an unknown variable, or a variable that not a list or map, results in a runtime error.
+- Assigning to a list or map index that already exists overwrites that index's data with evaluated right hand side's value.
+- Assigning to an unknown key in a map creates a key for that map with the evaluated right hand side's value assigned to that key.
+- Attempting to assign a value to a list index that is out of range results in a runtime error.
+
+### If Statements
+
+If statements specify the conditional execution of two branches according to the value of a boolean expression. If the expression evaluates to true, the "if" branch is executed, otherwise, if present, the "else" branch is executed.
+
+```ebnf
+IfStmt = "if" BoolExpr Block [ "else" ( IfStmt | Block ) ] .
+```
+
+```sentinel
+if x < y {
+ return x
+} else if x > z {
+ return z
+} else {
+ return y
+}
+```
+
+### Case Statements
+
+Case statements specify a selection control mechanism to conditionally execute a branch based on expression equality.
+
+Each clause that wishes to provide an expression must use the "when" keyword. Multiple expressions can be provided, separated with a comma (",").
+
+There can be one, optional, "else" clause within a `case` statement. The "else" clause is executed if all other clauses failed to run.
+
+The clauses are evaluated in the order they are listed, with the first successful evaluation being executed.
+
+A `case` statement can optionally provide an expression. If no expression is provided, it is the equivalent of writing `case true {`.
+
+```ebnf
+CaseStmt = "case" [ Expression ] "{" { CaseWhenClause } "}" .
+CaseWhenClause = CaseWhenCase ":" StatementList .
+CaseWhenCase = "when" Expression { "," Expression } | "else" .
+```
+
+```sentinel
+case x {
+when y, z:
+ return true
+else:
+ return false
+}
+
+case {
+when x > 42:
+ return true
+else:
+ return false
+}
+```
+
+### For Statements
+
+A `for` statement specifies repeated execution of a block.
+
+The expression must evaluate to a list or a map.
+
+When iterating over a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When iterating over a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+ForStmt = "for" Expressions "as" [ identifier "," ] identifier Block .
+```
+
+```sentinel
+for [1, 2, 3] as v { count += v } // basic sum
+
+for [1, 2, 3] as idx, v {
+ if idx > 1 { count += v }
+}
+
+data = { "a": 12, "b": 32 }
+for data as k { count += data[k] } // sum map values
+for data as k, v { count += v }
+```
+
+### Break Statements
+
+A `break` statement terminates execution of the innermost `for` statement
+within the same function.
+
+```ebnf
+BreakStmt = "break" .
+```
+
+```sentinel
+// Will only print "1"
+for [1, 2, 3] as v {
+ print(v)
+ break
+}
+```
+
+### Continue Statements
+
+A `continue` statement begins the next iteration of the innermost `for` loop.
+The `for` loop must be within the same function.
+
+```ebnf
+ContinueStmt = "continue" .
+```
+
+```sentinel
+// Will print 1, 3
+for [1, 2, 3] as v {
+ if v == 2 {
+ continue
+ }
+
+ print(v)
+ break
+}
+```
+
+### Return Statements
+
+A return statement in a function F terminates the execution of F and provides the result value.
+
+```ebnf
+ReturnStmt = "return" Expression .
+```
+
+A function must terminate with a return statement.
+
+The return statement can be invoked at any time during a function to terminate execution.
+
+## Imports
+
+An import adds an assignment to the global scope to external definitions.
+
+The import declarations must only appear at the top of the source file, before any other statements. Comments may appear before imports.
+
+```ebnf
+ImportDecl = "import" Name ["as" identifier] .
+Name = string_lit .
+```
+
+An import name and identifier must be distinct. Two names may not repeated even if they're declared with different identifiers.
+
+If an identifier is not specified, the identifier is the name of the import itself.
+
+An import is not a valid value. The identifier representing the import may not be used as an expression, such as in assignment statements or call expressions.
+
+Values in imports are not assignable directly via their selectors. Function calls may be used to alter the state of the external data represented by the import. Whether or not this is supported is specific to the import implementation. The exception to this assignment restriction is the memoized data returned by a call to an import (see below).
+
+Data returned by imports can be memoized, meaning that data is returned by the import in the form of a map which is then used as much as possible without having to return to the import for more data. For example, `t = time.now` returns a map so that `t.hour` will be able to be looked up without having to go back to the import for that data.
+
+Data returned by imports may be callable, meaning that methods may be called on the memoized data returned by an import. For example, given `t = time.now`, `t.after(some_previous_time)` is valid, with the receiver data being set to the local memoized data stored in `t`. It is a valid case to accept modifications to the receiver data (example: assignment to the memoized data via index expressions), as long as all data in the receiver continues to be string-keyed. It is an invalid case to accept receiver data with non-string-typed keys. Methods may also alter the contents of receiver data so that it is different after the call is finished.
+
+As with methods, imports may also have callable keys where the data may not be memoized. Example: in the event of `v = foo.bar`, if `v.baz` is not included in the memoized data, a call will be made to the import to fetch it. The same rules and restrictions to receiver data apply to callable keys as do to method calls.
+
+The support for callable methods and non-memoized keys on data returned by imports is specific to the import implementation.
+
+The handling of import loading is specific to the runtime implementation.
+
+Implicit imports may be added by the runtime, for example for standard library definitions. An explicit import of the same name should override a matching implicit import. For example, if "time" is an implicit import and the program specifies `import "time" as cal`, then only `cal` is defined.
+
+```sentinel
+import "time"
+import "math" as m
+```
+
+## Parameters
+
+```ebnf
+ParamDecl = "param" identifier [ "default" Expression ]
+```
+
+A parameter is a way for a policy to describe variables that are expected to be supplied by the calling application, in addition to any default value.
+
+Parameter declarations must appear at the top of the source file, following imports.
+
+Like imports, comments may appear before parameters; this is mainly pertinent when the policy is not importing anything, which would make parameters the first declarations that appear in a policy.
+
+A parameter declaration describes a valid variable that is added to the scope of a policy at runtime. This variable has the same properties and rules as other variables assigned during the course of policy evaluation as defined by the specification. This includes having a dynamic type and being able to be re-assigned during execution.
+
+Parameters may only be strings, integers, floating point numbers, booleans, lists, or maps. More complex types such as rules or functions are not allowed.
+
+A parameter can specify a default value by adding one in the declaration via use of the "default" keyword, and supplying a literal for the default value. The literal for a default is a very limited expression, comprising of:
+
+- For strings, a simple string literal;
+- For integers and floating-point numbers, either a literal denoting the value, or a unary expression with the literal, and the operators - or +;
+- For booleans, the pre-declared identifiers true or false;
+- For lists and maps, their respective literals composed of values and keys (for maps) of the above values only.
+
+Any other type of expression or identifier is not allowed.
+
+A parameter that does not have a default value defined is required by the policy. Evaluating a policy with a required parameter that has not been supplied at execution results in a runtime error.
+
+Parameters must not conflict with imports or any other identifiers currently defined in the global scope, including any reserved or pre-declared identifiers. Attempting to assign a parameter to an existing value results in a runtime error.
+
+```sentinel
+import "time"
+
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+param time default "11:00" // error, conflicts with "time" import
+param undefined // error, reserved identifier
+```
+
+## Built-in Functions
+
+Built-in functions are predeclared. They are called like any other function.
+
+### Length
+
+The built-in function `length` returns the length of a collection of string.
+
+The length of a string is the number of bytes in the string. It is not necessarilly the number of characters.
+
+The length of a collection is the number of elements in that collection.
+
+The length of `undefined` is `undefined`.
+
+### Collections
+
+#### List Append
+
+The built-in function `append` appends a value to the end of a list in-place.
+
+Appending to a non-list value results in an immediate `fail`.
+
+The return value of `append` is always the `undefined` value.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+#### Map Delete
+
+The built-in function `delete` deletes elements from a map by key. The map is modified in-place.
+
+Deleting a key that does not exist does nothing.
+
+Calling delete for a non-map value results in an immediate `fail`.
+
+The return value of `delete` is always the `undefined` value.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
+
+#### Keys and Values
+
+The built-in function `keys` and `values` return the keys and values of a map, respectively. The return value is a list. Both functions return values in an unspecified order.
+
+The `keys` or `values` of `undefined` is `undefined`.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+### Range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it defaults to `1`.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4]
+range(1, 5, 2) // [1,3]
+range(0, -3, -1) // [0,-1,-2]
+```
+
+### Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a value of that type according to the rules below.
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
+
+### Printing
+
+The built-in function `print` can be used to output formatted debug information. The runtime may omit print function calls depending on its execution mode. The runtime may choose where the output of a print function goes.
+
+`print` is a variadic function, accepting one or more values to be printed; values are separated by a single whitespace character (`" "`).
+
+The return value of `print` is always `true` to allow it to be used in boolean expressions.
+
+```sentinel
+print("hello") // "hello"
+print("hello", "world") // "hello world"
+print("The", "number", "is", 42) // "The number is 42"
+print([1, 2, 3]) // "[1, 2, 3]"
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // "false"
+```
+
+### Errors
+
+The built-in function `error` is equivalent to `print` but immediately causes execution to halt and the main rule to evaluate to `false`. The program execution is considered to have failed.
+
+## Program Execution
+
+Program execution begins by evaluating the source top to bottom. If evaluation is successful, the `main` rule is evaluated and its result is returned. Execution can be interrupted at any time by an error, which can be called explicitly or implicitly through illegal or undefined behavior.
+
+---
+
+> The content of this page is licensed under the [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
+>
+> Based on [The Go Programming Language Specification](https://golang.org/ref/spec) licensed under [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/language/undefined.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/language/undefined.mdx
new file mode 100644
index 0000000000..dc51531783
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/language/undefined.mdx
@@ -0,0 +1,97 @@
+---
+page_title: Sentinel Language - Undefined
+sidebar_current: docs-language-undefined
+description: >-
+ The value `undefined` is a special value representing undefined behavior or an
+ unknown value. Any operation on an `undefined` value results in `undefined`.
+layout: docs
+---
+
+# Language: Undefined
+
+The value `undefined` is a special value representing undefined behavior
+or an unknown value.
+
+Undefined is not an error because there are situations where the undefined
+value can be safely ignored and the language also provides construts for
+recovering from an undefined value.
+
+The undefined value can be created manually with `undefined`. In most cases,
+undefined is created by accessing a non-existent list element or value.
+The undefined value is created in a number of other circumstances. The documentation
+will note when an undefined value may be created.
+
+Almost any operation with `undefined` results in `undefined`. For example,
+performing math on undefined, attempting to call a function that is undefined,
+etc.
+
+## Main and Undefined
+
+If the value `undefined` is returned from the top-level `main` rule, then
+the policy is considered `false`. However, the runtime provides mechanisms
+for the host application to determine the policy failure was caused by
+an undefined value and report that to the user.
+
+The `main` rule returning the undefined value should be considered a logical
+error in most cases and should probably be corrected by the policy writer.
+
+## Debugging Undefined
+
+When an `undefined` value is first created (either explicitly or implicitly),
+the runtime will record the position where the undefined was created. This
+location can be used to find logic that could be erroneous.
+
+## Boolean Logic and Undefined
+
+Boolean logic is one way to safely recover from `undefined`. If boolean
+logic can safely determine a result despite an undefined value, that result
+will be returned.
+
+For example: `undefined or true` will result in `true`, because no matter
+what actual value `undefined` could've been if the policy behaved properly,
+the boolean expression would still be true.
+
+Another scenario where `undefined` can be safely ignored is short-circuit
+logic with `and`. `false and undefined` will result in `false`.
+
+The full table of logical operations on `undefined` is below:
+
+```sentinel
+undefined or true = true
+undefined or false = undefined
+undefined or undefined = undefined
+undefined and true = undefined
+undefined and false = undefined
+undefined and undefined = undefined
+undefined xor true = undefined
+undefined xor false = undefined
+undefined xor undefined = undefined
+
+// Short-circuit examples
+false or true or undefined = true
+false or undefined or true = true
+true and false and undefined = false
+true and undefined and false = undefined
+```
+
+## Else Expressions
+
+The `else` expression allows explicit recovery from undefined and is useful
+for setting default values.
+
+`else` is an operator where the left and right hand side are values. If the
+left-hand value is `undefined`, the right-hand value is returned. Otherwise,
+the left-hand value is returned.
+
+Examples:
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else "default"
+
+// In more complex scenarios
+
+foo() else 42 == 12
+a = config.value else "default"
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/language/values.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/language/values.mdx
new file mode 100644
index 0000000000..f932480340
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/language/values.mdx
@@ -0,0 +1,210 @@
+---
+page_title: Sentinel Language - Values
+sidebar_current: docs-language-values
+description: Values are the data that Sentinel policies operate on.
+layout: docs
+---
+
+# Language: Values
+
+Values are the _data_ that Sentinel policies operate on. You can create this
+data yourself, for example by just typing the number `42`, or you may access
+this data from an external source to make policy decisions.
+
+Values have a _type_, which determines what kind of operations can be performed
+on the data. Example types are booleans (true and false), numbers,
+and strings (text).
+
+This page documents all the available value types. You can also
+[convert between types](#type-conversion).
+
+## Boolean
+
+A boolean is a value that is either true or false.
+
+A boolean value is created literally with `true` or `false`.
+
+As a policy language, booleans are central to the behavior of Sentinel.
+Booleans are used as conditions in [if statements](/sentinel/language/conditionals),
+are the result of [rules](/sentinel/language/rules), and more. The ultimate
+result of a Sentinel policy is true or false.
+
+## Integer
+
+An integer is a whole number.
+
+An integer is a 64-bit value. This means it can represent numbers from
+-9,223,372,036,854,775,808 to 9,223,372,036,854,775,808.
+
+Integers are created by typing them out literally with no
+separators (such as a comma). An optional prefix can set a non-decimal base:
+`0` for octal, and `0x` for hexadecimal. In hexadecimal literals, letters
+`a-f` and `A-F` represent values 10 to 15.
+
+Example integers are shown below:
+
+```sentinel
+42
+0600
+0xBadFace
+170141183460469
+```
+
+Integers are used for math and numerical comparison.
+
+## Float
+
+A float is a number with a decimal point. It has an integer part, a decimal
+point, a fractional part, and optionally an exponent part. Example
+floats are shown below:
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+Floats are IEEE-754 64-bit floating point numbers.
+
+## String
+
+Strings are text values.
+
+Strings are created by wrapping text in double quotes, such as `"hello"`.
+Within the quotes, any character may appear except newline and an unescaped
+double quote. The text between the quotes is the value.
+
+Because Sentinel policies must be UTF-8 encoded text, strings themselves are
+UTF-8 encoded text. This means you can put any value UTF-8 character into
+a string, such as `"日本語"`.
+
+You can have a string with a literal double-quote by _escaping_ it with
+a backslash. For example: `"they said \"hello\""` turns into the value
+`they said "hello"`.
+
+Backslash escapes can be used for much more than only escaping a double quote.
+Newlines are `\n`, a literal backslash is `\\`, and many more. The full list
+of available backslash escapes can be found
+[in the language specification](/sentinel/language/spec#string-literals).
+
+Example strings:
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+Strings do not support indexing, however it is possible to achieve a similar
+result through the combination of the [strings](/sentinel/imports/strings)
+standard import and list indexing.
+
+```sentinel playground
+import "strings"
+
+asciistr = "Hello, world!"
+utf8str = "日本語"
+
+utf8split = rule {
+ strings.split(utf8str, "")[2] == "語"
+}
+
+asciisplit = rule {
+ strings.split(asciistr, "")[5] == ","
+}
+
+main = rule { utf8split and asciisplit }
+```
+
+Strings support [slicing](/sentinel/language/slices) to efficiently create
+substrings.
+
+## List
+
+See [Lists](/sentinel/language/lists).
+
+## Map
+
+See [Maps](/sentinel/language/maps).
+
+## Rule
+
+See [Rules](/sentinel/language/rules).
+
+## Function
+
+See [Functions](/sentinel/language/functions).
+
+## Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a
+value of that type. Some examples are shown below followed by a list of exact
+rules for type conversion.
+
+```sentinel
+int(42) // 42
+int("42") // 42
+int(42.8) // 42
+int(true) // 1
+
+float(1.2) // 1.2
+float(1) // 1.0
+float("4.2") // 4.2
+float(true) // 1.0
+
+string("foo") // "foo"
+string(88) // "88"
+string(0xF) // "15"
+string(true) // "true"
+
+bool("true") // true
+bool(1) // true
+bool(-1) // true
+bool(0.1) // true
+bool("false") // false
+bool(0) // false
+```
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/language/variables.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/language/variables.mdx
new file mode 100644
index 0000000000..7516890212
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/language/variables.mdx
@@ -0,0 +1,99 @@
+---
+page_title: Sentinel Language - Variables
+sidebar_current: docs-language-vars
+description: Variables store values that can be used later.
+layout: docs
+---
+
+# Language: Variables
+
+Variables store values that can be used later.
+
+Most notably, a variable assignment of `main` is required for all
+Sentinel policies. This is the main [rule](/sentinel/language/rules) that
+is executed to determine the result of a policy. The `main` rule may
+use other variables that are assigned in the policy.
+
+Example:
+
+```sentinel
+a = 1
+b = a + 1
+```
+
+In this example, two variables are assigned: `a` and `b`. `a` is given
+the value of "1" and `b` uses the `a` to calculate its own value.
+
+## Syntax
+
+The syntax for assigning a variable is:
+
+```sentinel
+IDENTIFIER = VALUE
+```
+
+On the left of the equal sign is an _identifier_. This is a name for your
+variable and will be how you reference it later. An identifier is any
+combination of letters and digits, but must start with a letter. An
+underscore `_` is also a valid letter.
+
+On the right is any valid Sentinel value or expression. A value is a
+literal value such as a number or string. An expression is some computed
+value such as doing math, calling a function, etc.
+
+## Assignment and Reassignment
+
+A variable is _assigned_ when it is given a value. You can also _reassign_
+variables at any time by setting it to a new value. The new value takes
+effect for any subsequent use of that variable. A variable can be reassigned
+to a different type.
+
+For example:
+
+```sentinel
+a = 1 // a = 1
+b = a // b = 1
+a = "value" // a = "value", b = 1
+c = a // c = "value", b = 1
+```
+
+In the above example you can see that the variables are set and reassigned.
+Notice that the value of a variable is the _current_ value, and that reassigning
+a variable only affects _future_ uses of that variable. You can see this with
+`c` and `b` being different values.
+
+## Unassigned Variables
+
+Using a variable that is _unassigned_ is an error.
+
+In the example below, the first line would result in the policy erroring.
+Sentinel is executed top-down and the value of `c` is not available yet
+on the first line.
+
+```sentinel
+a = c // Error!
+c = 1
+```
+
+## Type Conversion
+
+While a topic more related to [values][lang-values], variables can be assigned
+(or-reassigned) a different value type via type conversion.
+
+[lang-values]: /sentinel/language/values
+
+```sentinel
+s = "1.1"
+a = int(s) // a = 1
+a = float(s) // a = 1.1
+
+a = 1
+s = string(a) // s = "1"
+```
+
+For more details, see the type conversion sections in the
+[values][lang-values-type-conversion] page, or the [Sentinel Language
+Specification][lang-spec-type-conversion].
+
+[lang-values-type-conversion]: /sentinel/language/values#type-conversion
+[lang-spec-type-conversion]: /sentinel/language/spec#type-conversion
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/nomad.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/nomad.mdx
new file mode 100644
index 0000000000..a105b0337d
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/nomad.mdx
@@ -0,0 +1,51 @@
+---
+page_title: Nomad and Sentinel
+sidebar_current: docs-app-nomad
+description: >-
+ Nomad Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies can currently execute on job
+ submission (creation, update).
+layout: docs
+---
+
+# Nomad
+
+Nomad is a simple, flexible scheduler and workload orchestrator.
+[Nomad Enterprise](https://www.hashicorp.com/products/nomad/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies can currently execute on job submission (creation, update).
+
+Sentinel policies have full access to the job structure. This allows the
+Sentinel policy to control behavior based on any attribute within a job,
+such as the driver, resource requests, network configuration, volume
+configuration, and more. The information that Sentinel policies have access
+to will expand over time.
+
+Nomad fully supports all [enforcement levels](/sentinel/concepts/enforcement-levels).
+For soft mandatory policies, the `sentinel-override` capability must be
+available on the user's ACL policy to allow override. Overrides are always
+logged.
+
+The Nomad integration with Sentinel is documented in depth in the
+[Nomad Enterprise documentation](https://www.nomadproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Only allow Docker-based jobs.**
+
+```sentinel
+# Test policy only allows Docker based tasks
+main = rule { all_drivers_docker }
+
+# all_drivers_docker checks that all the drivers in use are Docker
+
+all_drivers_docker = rule {
+ all job.task_groups as tg {
+ all tg.tasks as task {
+ task.driver is "docker"
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/terraform.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/terraform.mdx
new file mode 100644
index 0000000000..9e4c430ddb
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/terraform.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Terraform and Sentinel
+sidebar_current: docs-app-terraform
+description: >-
+ Use Sentinel with HCP Terraform and Terraform Enterprise to enforce policy
+ on Terraform configurations, states, and plans.
+layout: docs
+---
+
+# Terraform
+
+[HCP Terraform and Terraform Enterprise](https://www.hashicorp.com/products/terraform/) use Sentinel to enforce
+policy on [Terraform](https://www.terraform.io) configurations, states, and plans.
+
+The Sentinel integration with Terraform runs within
+HCP Terraform and Terraform Enterprise
+after a `terraform plan` and before a `terraform apply`. The policies
+have access to the created plan, the state at the time of the plan,
+and the configuration at the time of the plan.
+
+The Terraform integration with Sentinel is documented in depth in the [HCP Terraform and Terraform Enterprise documentation](/terraform/cloud-docs/policy-enforcement/sentinel).
+Please read that page for full documentation. This page will only show basic examples.
+
+### Examples
+
+**Example: All AWS instances must have a tag**
+
+```sentinel
+import "tfplan"
+
+main = rule {
+ all tfplan.resources.aws*instance as *, instances {
+ all instances as \_, r {
+ (length(r.applied.tags) else 0) > 0
+ }
+ }
+}
+```
+
+**Example: Only allow GCP instance sizes smaller than `n1-standard-16`**
+
+```sentinel
+import "tfplan"
+
+allowed_machine_types = [
+ "n1-standard-1",
+ "n1-standard-2",
+ "n1-standard-4",
+ "n1-standard-8",
+]
+
+main = rule {
+ all tfplan.resources.google_compute_instance as _, instances {
+ all instances as _, r {
+ r.applied.machine_type in allowed_machine_types
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/vault.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/vault.mdx
new file mode 100644
index 0000000000..6db2f8af22
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/vault.mdx
@@ -0,0 +1,64 @@
+---
+page_title: Vault and Sentinel
+sidebar_current: docs-app-vault
+description: >-
+ Vault Enterprise uses Sentinel to augment the built-in policy system to
+ provide Role Governing Policies (RGPs) and Endpoint Governing Policies (EGPs)
+ to enable complex, flexible policies across identities and endpoints.
+layout: docs
+---
+
+# Vault
+
+[Vault Enterprise](https://www.hashicorp.com/products/vault/) uses Sentinel
+to augment the built-in policy system to provide Role Governing Policies (RGPs)
+and Endpoint Governing Policies (EGPs) to enable complex, flexible policies
+across identities and endpoints.
+
+**Role Governing Policies (RGPs)** are Sentinel policies that are tied to
+particular tokens, Identity entities, or Identity groups. They have access to
+a rich set of controls across various aspects of Vault. These are evaluated
+whenever a token they're attached to is used.
+
+**Endpoint Governing Policies (EGPs)** are Sentinel policies that are tied to
+particular paths instead of tokens. They have access to as much request
+information as possible, but they can take effect even on unauthenticated
+paths, such as login paths.
+
+The Vault integration with Sentinel is documented in depth in the
+[Vault Enterprise documentation](https://www.vaultproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Endpoint policy that requires MFA authentication from a corporate network.**
+
+```sentinel
+import "sockaddr"
+
+# We expect logins to come only from our private IP range
+cidrcheck = rule {
+ sockaddr.is_contained(request.connection.remote_addr, "10.20.0.0/16")
+}
+
+# Require Ping MFA validation to succeed
+ping_valid = rule {
+ mfa.methods.ping.valid
+}
+
+main = rule when request.path is "auth/ldap/login" {
+ ping_valid and cidrcheck
+}
+```
+
+**Example: Endpoint policy that disallows tokens created before a certain time.**
+
+```sentinel
+import "time"
+import "strings"
+
+main = rule when not strings.has_prefix(request.path, "auth/ldap/login") {
+ time.load(token.creation_time).unix > time.load("2017-09-17T13:25:29Z").unix
+}
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/why.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/why.mdx
new file mode 100644
index 0000000000..7e0d42e1ee
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/why.mdx
@@ -0,0 +1,47 @@
+---
+page_title: Why Sentinel?
+sidebar_current: docs-why
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: docs
+---
+
+# Why Sentinel?
+
+The growth of infrastructure and applications has been enabled in part
+by an increasing trend towards automation everywhere.
+Configuration as code (such as Chef or Puppet with [Packer](https://www.packer.io))
+has enabled automated machine configuration. Infrastructure
+as code (such as [Terraform](https://www.terraform.io)) has enabled
+automatic infrastructure creation. And schedulers (such as
+[Nomad](https://www.nomadproject.io) and Kubernetes) have enabled
+automatic application deployment.
+Sentinel enables guardrails to be put in place
+on automation while allowing the codification and automatic enforcement
+of business requirements in critical areas of your infrastructure.
+
+Meanwhile, businesses have business requirements and sometimes legal
+requirements which must be expressed in policies. Traditionally, these
+policies are enforced by humans. But in a highly automated world, the
+automation is only as fast as its slowest component. In many cases, this
+is the human verification step.
+
+As an example: before infrastructure as code and autoscaling, if an order
+came through for 5,000 new machines, a human would likely respond to the
+ticket verifying that the user really intended to order 5,000 new machines.
+Today, automation can almost always freely order 5,000 new compute instances
+without any hesitation, which can result in unintended expense or system
+instability.
+
+Sentinel introduces [policy as code](/sentinel/concepts/policy-as-code)
+and a powerful framework built-in to HashiCorp tooling to allow automation
+guardrails, business requirements, legal compliance, and more to be
+actively enforced by the running systems in realtime.
+
+With Sentinel, you can require an override to create certain numbers of
+infrastructure resources. You can disallow unsafe deployment configurations
+with Nomad. You can enforce certain key/value formats in Consul. You
+can restrict secret access by time in Vault. And more.
+
+Sentinel is available today in HashiCorp enterprise products.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/writing/basic.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/writing/basic.mdx
new file mode 100644
index 0000000000..ebcebf2788
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/writing/basic.mdx
@@ -0,0 +1,111 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-basic
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Policy Basics
+
+Sentinel policies are easy to write while still supporting advanced constructs
+for creating complex policies. This page will explain the basics of writing
+Sentinel policies to get started. You don't need any prior Sentinel knowledge,
+but we recommend reading the [language guide](/sentinel/language) after this.
+
+Sentinel policies are text files written using the [Sentinel language](/sentinel/language).
+The policies are evaluated top-to-bottom. The value of `main` after execution
+determines whether a policy passes or fails.
+
+## The Simplest Policy
+
+Sentinel only requires that a policy have a `main` variable that evaluates to
+a boolean value.
+
+A valid example is shown below:
+
+```sentinel playground
+main = 10 > 5
+```
+
+This type of minimal policy is not purely academic. In practice, simple
+policies can often be reduced to a single line logical statement resulting
+in `true` or `false`. However, the expression is usually wrapped in a
+[rule](/sentinel/language/rules) for testing reasons.
+
+You can verify Sentinel will execute this minimal policy using the CLI:
+
+```
+$ sentinel apply minimal.sentinel
+Pass
+```
+
+## Logical Expressions
+
+Policy is at its core a set of logic: you can or can not perform some action
+under a certain set of circumstances. Those circumstances are logical
+expressions. Therefore, Sentinel policies primarily translate into logical
+expressions.
+
+Detailed documentation on boolean expressions is
+[available in the language guide](/sentinel/language/boolexpr).
+
+A simple numerical comparison was seen in the first example on this page.
+Sentinel also provides inclusion operators such as `contains`, `any`, `all`, and
+more. Sentinel allows some operators to have aliases to promote readability
+while remaining programmer-familiar, such as `==` which can equivalently be `is`.
+
+The example below verifies that all numbers in a list are even:
+
+```sentinel playground
+numbers = [6, 22, 8, 4, 12]
+main = all numbers as n { n % 2 is 0 }
+```
+
+## Variables
+
+A policy will very often use variables. Applications such as
+[Nomad](https://www.nomadproject.io) inject variables into the global
+scope of a policy for making policy decisions. For example, Nomad injects
+the job that is being run into the policy scope. Knowing how to use
+variables is critical to effectively using Sentinel.
+
+Detailed documentation on how to define and access variables is
+[available in the language guide](/sentinel/language/variables).
+
+Variables can be defined and used explicitly. For example:
+
+```sentinel playground
+value = 10
+main = value > 5
+```
+
+But they may also be introduced implictly by the host system. Nomad
+injects `job` into policies to describe the job that is being run. The
+policy below is a valid policy that requires a job have two task groups.
+Notice that `job` is not defined anywhere. It is implicitly inserted by
+the host application (in this case Nomad). Refer to the application you're
+writing policy for to determine if it implicitly inserts values.
+
+```json sentinel
+{
+ "policy": "main = length(job.task_groups) is 2",
+ "globals": {
+ "job": {
+ "task_groups": ["foo", "bar"]
+ }
+ }
+}
+```
+
+## And more!
+
+The Sentinel language supports many more features such as functions,
+loops, and more. You can learn about all of this in the
+[complete language guide](/sentinel/language).
+
+The other pages in the [writing policy](/sentinel/writing)
+will cover other information you need to know about writing Sentinel
+policies that isn't simply a language reference.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/writing/debugging.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/writing/debugging.mdx
new file mode 100644
index 0000000000..7dc284d222
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/writing/debugging.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-debugging
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Debugging
+
+As policies become more complex, it becomes harder to understand exactly why
+a policy may be behaving differently than you expect.
+
+Prior to actively debugging, there are multiple best practices we recommend
+following. We recommend breaking your policy down into a set of
+[rules](/sentinel/writing/rules). This will make it easier to understand
+[traces](/sentinel/writing/tracing) and make it easier to
+[test](/sentinel/writing/testing) your policies.
+This should help to debug policies up to a significant complexity.
+
+## Print Logging
+
+The built-in [print function](/sentinel/language/logging-errors#print)
+can be used to log data. The print output is only shown during failures
+or when [tracing is explicitly enabled](/sentinel/writing/tracing).
+
+The `print` function outputs each argument, which can be of any type, converted
+to a human-friendly string format. Arguments are separated with a single space.
+
+Example:
+
+```sentinel playground
+value = 42
+print("the number is", value) // the number is 42
+
+object = { "foo": false }
+print(object) // { "foo": false }
+
+main = rule { true }
+```
+
+The `print` function returns the value `true` so that it can also be
+used within rule expressions. Note that the `print` function should be
+used at the beginning of statements otherwise they may never be
+reached. This is due to internal performance techniques within Sentinel
+like [short-circuiting](https://en.wikipedia.org/wiki/Short-circuit_evaluation).
+
+Example:
+
+```sentinel playground
+// rule_a and rule_b will evaluate to false
+rule_a = rule { print("rule_a is printed") and false } // "rule_a is printed"
+rule_b = rule { false and print("rule_b is printed") } // Nothing is printed
+
+// rule_c and rule_d will evaluate to true
+rule_c = rule { print("rule_c is printed") or true } // "rule_c is printed"
+rule_d = rule { true or print("rule_d is printed") } // Nothing is printed
+
+main = rule { rule_a or rule_b or rule_c and rule_d }
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/writing/imports.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/writing/imports.mdx
new file mode 100644
index 0000000000..74ac751ee4
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/writing/imports.mdx
@@ -0,0 +1,117 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom import](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only local
+context for making policy decisions.
+
+Sentinel also comes with a set of [standard imports](/sentinel/imports).
+Standard imports are available to every Sentinel policy to help policy writers
+with common tasks such as working with the time, network addresses, and more.
+
+-> **This page is about writing policies that _use_ imports.** If you're
+interested in _creating_ a new import, please see the section on [extending
+Sentinel](/sentinel/extending) for information on how to write modules and
+import plugins.
+
+## Using Imports
+
+To use an import, you use the `import` keyword at the top of your policy. This
+specifies the name of the import you want to use. The application you're writing
+the policy for must already be
+[configured](/sentinel/configuration#imports) to provide that
+import.
+
+Details on imports can be found in the
+[import section in the language reference](/sentinel/language/imports).
+
+In the example below, we use the [time](/sentinel/imports/time) import:
+
+```sentinel playground
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+There are two options to develop a policy locally when using imports:
+configure Sentinel to launch the import on `apply`, or mock the import
+using `mock`. The former requires access to the import while the
+latter is faster (doesn't have to launch a process for plugins) and
+doesn't require the import.
+
+## Mocking Imports
+
+The first option to developing policies locally is to mock the import values.
+When mocking an import, you don't need the import to be available. This can be
+useful since some imports may not be available as a plugin and may only be
+available to the application the policy runs in.
+
+Mocks are specified via the [configuration
+file](/sentinel/configuration). Mocks can also be used for
+[testing](/sentinel/writing/testing).
+
+You can supply mock configuration one of two ways, depending on your use case:
+
+- **[Using static data](/sentinel/configuration#mocking-static-data):**
+ Use this method when you can accurately represent your mock data in JSON and
+ do not need to mock complex Sentinel features such as functions.
+- **[Using Sentinel code](/sentinel/configuration#mocking-with-sentinel-code):**
+ Use this method when using a static JSON object is insufficient, such as when
+ you need to mock functions or other complex Sentinel features.
+
+Our example does not require complex data to be mocked, so a static object
+is sufficient:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ hour = 12
+ weekday_name = "Tuesday"
+ }
+ }
+}
+```
+
+This can be used via the CLI:
+
+```
+$ sentinel apply -config=config.hcl policy.sentinel
+Pass
+```
+
+## Launching Import Plugins
+
+If you have access to the plugin binary, you can launch the import. The
+benefit of this is that it is really using the import to test your
+policy. If the import changes, your policies may start failing. If you
+only use mock data and the import changes, your policies will still
+appear to work.
+
+Imports are configured in the configuration file:
+
+```hcl
+import "plugin" "custom_time" {
+ source = "/path/to/sentinel-time-import"
+}
+```
+
+This would require the `sentinel-time-import` binary. For this example
+this doesn't currently exist. We plan on writing one to provide for this
+section of the documentation.
+
+## Custom Imports
+
+You can also [create your own imports](/sentinel/extending).
+
+If your policy decisions could benefit from accessing external information,
+then you can use custom imports as a way to do this.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/writing/index.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/writing/index.mdx
new file mode 100644
index 0000000000..eec0b61fb5
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/writing/index.mdx
@@ -0,0 +1,34 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing
+description: >-
+ Sentinel provides a language and workflow for building policy across any
+ system that embeds Sentinel. By learning Sentinel once, you are able to
+ effectively control access to many systems using Sentinel's powerful features.
+ Basic concepts that are important to understand for Vault usage.
+layout: docs
+---
+
+# Writing Sentinel Policy
+
+This section covers how to write Sentinel policies.
+
+We recommend you complete the Get Started tutorials prior to
+reading this section of the documentation. The Get Started tutorials will
+explain all the basics of writing and testing policies. This section of the
+documentation will serve as more of a reference guide to all available
+features of Sentinel.
+
+Sentinel provides a language and workflow for building policy across any system
+that embeds Sentinel. By learning Sentinel once, you are able to effectively
+control access to many systems using Sentinel's powerful features.
+
+Sentinel also provides a [local CLI](/sentinel/commands) for developing and testing
+Sentinel policies. This CLI can be integrated into a daily developer's workflow
+along with CI to treat [policy as code](/sentinel/concepts/policy-as-code).
+
+Sentinel uses its own [language](/sentinel/language) for writing
+policies. You can view a [language reference](/sentinel/language)
+as well as the [specification](/sentinel/language/spec) for details. You
+don't have to read those documents immediately, since the language should
+be easy enough to pick up throughout this section.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/writing/rules.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/writing/rules.mdx
new file mode 100644
index 0000000000..ab4fc69d08
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/writing/rules.mdx
@@ -0,0 +1,65 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-rules
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Rules
+
+Rules form the basis of a policy by representing behavior that is either passing
+or failing. Rules are a first class language construct in Sentinel. A policy can
+and should be broken down into rules to aid with readability, testability, and
+performance.
+
+Rules provide:
+
+- **Readability:** A policy that is broken down into rules can be read
+ more easily. The logic becomes clearer to see for policy writers when
+ they come back to it.
+
+- **Reportability:** When [tracing](/sentinel/writing/tracing) is enabled,
+ rules form the foundation of the data returned. All evaluated rules are added
+ to the trace with the data the rule evaluated to, and their position within
+ policy or module source files.
+
+- **Testability:** [Policy testing](/sentinel/writing/testing) is
+ built on asserting the values of rules. By breaking logic down into
+ rules, you're able to more effectively test your policies.
+
+- **Performance:** Rules are only evaluated on demand, and are also only
+ evaluated once. This means that a rule referenced multiple times only has a
+ one-time performance cost. For complex logic, this could result in improved
+ performance.
+
+An example usage of rules is shown below:
+
+```json sentinel
+{
+ "policy": "is_sunny = rule { weather is \"sunny\" }\nis_wednesday = rule { day is \"wednesday\" }\nmain = rule { is_sunny and is_wednesday }",
+ "globals": {
+ "weather": "sunny",
+ "day": "wednesday"
+ }
+}
+```
+
+Rules can also contain non-boolean data. This can be useful for passing more
+detail to the caller than an opaque boolean value would be able to communicate.
+
+```sentinel playground
+days = [{"weather": "sunny"}]
+main = rule {
+ filter days as d {
+ d.weather is not "sunny"
+ }
+}
+```
+
+Details about rules and their behavior can be found in
+[the language reference](/sentinel/language/rules). Rules have important
+behavior so we recommend reading the language reference on rules.
+Rules will be used throughout the remainder of the documentation.
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/writing/testing.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/writing/testing.mdx
new file mode 100644
index 0000000000..5d8a522c28
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/writing/testing.mdx
@@ -0,0 +1,495 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-testing
+description: >-
+ Sentinel has a built-in test framework to validate a policy behaves as
+ expected.
+layout: docs
+---
+
+# Testing
+
+Sentinel has a built-in test framework to validate a policy behaves as expected.
+
+With an ever-increasing amount of automation surrounding technology,
+the guardrails provided by policies are a critical piece towards ensuring
+expected behavior. As a reliance on correct policy increases, it is important
+to test and verify policies.
+
+Testing is a necessary step to fully realize
+[policy as code](/sentinel/concepts/policy-as-code). Just as good software
+is well tested, a good set of policies should be equally well tested.
+
+## Test Folder Structure
+
+Policies are tested by asserting that [rules](/sentinel/writing/rules)
+are the expected values given a pre-configured input. Tests are run
+by executing the [test command](/sentinel/commands/test).
+
+Sentinel is opinionated about the folder structure required for tests.
+This opinionated structure allows testing to be as simple as running
+`sentinel test` with no arguments. Additionally, it becomes simple to test
+in a CI or add new policies.
+
+The structure Sentinel expects is `test//*.[hcl|json]` where ``
+is the name of your policy file without the file extension. Within that folder
+is a list of HCL or JSON files. Each file represents a single test case.
+Therefore, each policy can have multiple tests associated with it.
+
+-> Note that the primary configuration file format for Sentinel is HCL. While
+you can write configuration files in a HCL-equivalent JSON, we only discuss the
+use of HCL on this page.
+
+## Test Case Format
+
+Each HCL file within the test folder for a policy is a single test case.
+
+The file is the same configuration format as the [CLI configuration
+file](/sentinel/configuration). The format lets you define mock data, imports to
+use, and more. This mock data is the key piece in being able to test policies:
+you craft a specific scenario and assert your policy behaves as you expect.
+
+Test cases also use the `test` block within the configuration file to assert the
+value of [rules](/sentinel/writing/rules). If the `test` key is omitted, the
+policy is expected to pass. If the test key is specified, only the rules
+specified in the map will be asserted. This means if you omit `main`, then the
+final policy result is not asserted.
+
+Example with assertions:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+
+test {
+ rules = {
+ main = false
+ is_open_hours = false
+ is_weekday = true
+ }
+}
+```
+
+The configuration above specifies some parameter data, and asserts the result of
+some rules. This is the same configuration used in the example section below.
+
+## Example
+
+Lets use the following file as an example. Save this file to a directory
+and name it `policy.sentinel`. It can be named anything with the `sentinel`
+extension, but by naming it `policy.sentinel` your output should match
+the example output on this page.
+
+```sentinel
+// The day of the week.
+param day
+
+// The hour of the day.
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+### A Passing Test
+
+Next, let's define a single test case. Relative to where you saved
+the policy, create a file at the path `test/policy/good.hcl`.
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 14
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/good.hcl
+```
+
+This passed because the policy passed. We didn't assert any specific
+rules. By not specifying any assertions, `test` expects the policy itself
+to fully pass.
+
+### A Failing Test
+
+Define another test case to fail. We want to verify our policy fails when expected, too.
+
+Save the following as `test/policy/7-am.hcl`:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+FAIL - policy.sentinel
+ FAIL - test/policy/7-am.hcl
+ expected "main" to be true, got: false
+
+ trace:
+ policy.sentinel:9:1 - Rule "main"
+ bool: false
+
+ policy.sentinel:8:1 - Rule "is_open_hours"
+ bool: false
+
+ PASS - test/policy/good.hcl
+```
+
+As you can see, the test fails because "main" is false. This is good
+because the policy _should_ have failed since we specified an invalid
+hour. But, we expect main to be false and don't want our test to fail!
+Update `7-am.hcl` to add test assertions:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+
+test {
+ rules = {
+ main = false
+ is_open_hours = false
+ }
+}
+```
+
+And when we run the tests:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/7-am.hcl
+ PASS - test/policy/good.hcl
+```
+
+The test passes. We asserted that we expect the `main` rule to be false,
+the `is_open_hours` rule to be false, and the `is_weekday` rule to be
+true. By asserting some rules are true, we can verify that our policy
+is failing for reasons we expect.
+
+## Mocking
+
+The above example demonstrates how to test by supplying different
+[parameters](/sentinel/language/parameters). Parameters in a policy can be
+specifically useful when you want to control user-defined input values to a
+policy.
+
+However, generally, when testing, you will need mimic the conditions you will
+see in production. Production implementations of Sentinel will supply data using
+one of two methods:
+
+- **Global data:** Data is injected directly into the policy's scope and is
+ accessible using normal identifiers, similar to variables.
+- **Imports:** Data is stored behind an [import](/sentinel/language/imports)
+ and loaded on demand as needed by the policy author.
+
+Proper testing of a policy requires that these values be able to be _mocked_ -
+or, in other words, simulated in a way that allows the accurate testing of the
+scenarios that a policy could reasonably pass or fail under.
+
+Mocking both globals and imports can be done by setting various parts of the
+configuration file.
+
+### Mocking Globals
+
+Demonstrating the mocking of globals can be seen by making a few modifications to
+our example policy, removing the `param` declarations:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Then, change the `param` section in the configuration file to
+[`global`](/sentinel/configuration#global).
+
+```json
+global "day" {
+ value = "monday"
+}
+
+global "hour" {
+ value = 14
+}
+```
+
+This test should still pass, as if nothing had happened, although what we've
+done is shifted our parameters to globals, simulating an environment where `day`
+and `hour` are already defined for us.
+
+### Mocking Imports
+
+To mock imports, we need to use the
+[`mock`](/sentinel/configuration#mock-imports) section of the
+configuration file.
+
+Let's say the above example is behind an import named `time`.
+
+-> **NOTE:** [`time`](/sentinel/imports/time) is a valid standard import.
+This example may not be accurate to the
+import's syntax.
+
+The code now looks like this:
+
+```sentinel
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To mock this import, we can [mock it as static
+data](/sentinel/configuration#mocking-static-data). The configuration
+file now looks like, without assertions:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ weekday_name = "Monday"
+ hour = 14
+ }
+ }
+}
+```
+
+The policy will now pass, with the `time` import mocked.
+
+Data can also be [mocked as Sentinel
+code](/sentinel/configuration#mocking-with-sentinel-code). In this case,
+the above configuration file would look like:
+
+```hcl
+mock "time" {
+ module {
+ source = "mock-time.sentinel"
+ }
+}
+```
+
+And a file named `mock-time.sentinel` would now hold your mock values:
+
+```sentinel
+day = "monday"
+hour = 14
+```
+
+Mocking as Sentinel code allows more complex details to be mocked as well, such
+as functions. Say we wanted to mock the `time.load()` function. To mock this,
+just add it to the `mock-time.sentinel` file:
+
+```sentinel
+load = func(_) {
+ return {
+ "weekday_name": "Monday",
+ "hour": 14,
+ }
+}
+```
+
+Your code can now be written as:
+
+```sentinel
+import "time"
+
+t = time.load("a_mock_timestamp")
+
+is_weekday = rule { t.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { t.hour > 8 and t.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To see more details, see the [Mock
+Imports](/sentinel/configuration#mock-imports) section in the
+configuration file.
+
+## Asserting Non-Boolean Rules
+
+Non-boolean rules can also be asserted by `sentinel test`.
+
+To assert non-boolean values, simply enter the expected value into the rule
+contents:
+
+```hcl
+param "maintenance_days" {
+ value = [
+ {
+ day = "wednesday"
+ hour = 9
+ },
+ {
+ day = "friday"
+ hour = 1
+ },
+ {
+ day = "sunday"
+ hour = 1
+ },
+ ]
+}
+
+test {
+ rules = {
+ main = [
+ {
+ day = "wednesday"
+ hour = 9
+ },
+ ]
+ }
+}
+```
+
+This would assert a policy checking for violations of a maintenance policy,
+saying that maintenance hours should happen before 6AM on any given day:
+
+```sentinel
+param maintenance_days
+
+main = rule {
+ filter maintenance_days as d {
+ d.hour >= 6
+ }
+}
+```
+
+## Test JSON Output
+
+Running `sentinel test` with the `-json` flag will give you the test results in
+a JSON output format, suitable for parsing by reporting software.
+
+-> **Note:** The JSON output format is currently under development and the
+format may change at a later time. Additionally, support for other well-known
+formats, such as JUnit, may become available in the future.
+
+The current format is an object with the top-level keys being `policies` and
+`duration`, with each test grouped up by policy being run. `duration` represents
+time taken in milliseconds for all policies to run.
+
+The policy result fields are:
+
+- `path`: The path of the policy and the index of the test result in the
+ `policies` field in the root object.
+- `status`: A string representation of the policy's test status as a whole. Can
+ be one of `PASS`, `FAIL`, `ERROR`, or `?`. The final status, `?`, represents a
+ policy that has no tests to process, and acts like a passing test.
+- `errors`: An array of any error messages encountered during processing the
+ policy for testing. Usually reserved for policy file or parser-related errors.
+ For case-specific errors, see the error field for the particular case.
+- `cases`: A map of case results, indexed by case path.
+- `duration`: Time taken in milliseconds for the policy to run.
+
+The case result fields are:
+
+- `path`: The path of the test case and the result's index in the `cases` field
+ in the policy object.
+- `status`: A string representation of the case's test status. Can be one of
+ `PASS`, `FAIL` or `ERROR`.
+- `errors`: An array of any error messages encountered during running this test
+ case.
+- `trace`: The trace for this policy in JSON format. See the
+ [tracing](/sentinel/writing/tracing) page for more details.
+- `rule_detail`: When the `status` of this test case is `FAIL`, contains an
+ object of assertion failure detail, indexed by rule. Only failures are counted
+ here; any rules not found here can be assumed to have passed or not asserted.
+- `config_warnings`: An array of strings denoting any configuration warnings
+ found while processing the configuration for this test case.
+- `config_legacy`: Denotes whether or not the configuration is a legacy JSON
+ configuration and needs to be modernized. This field may be removed in future
+ releases.
+
+A passing example is shown below:
+
+```json
+{
+ "policies": {
+ "policy.sentinel": {
+ "path": "policy.sentinel",
+ "status": "PASS",
+ "errors": null,
+ "duration": 5,
+ "cases": {
+ "test/policy/pass.hcl": {
+ "path": "test/policy/pass.hcl",
+ "status": "PASS",
+ "errors": null,
+ "trace": {
+ "description": "A very basic policy to determine if a run is within working hours.",
+ "error": null,
+ "print": "",
+ "result": true,
+ "rules": {
+ "is_open_hours": {
+ "desc": "Passes if run during business hours.",
+ "ident": "is_open_hours",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 290,
+ "line": 13,
+ "column": 1
+ },
+ "value": true
+ },
+ "is_weekday": {
+ "desc": "Passes if the day does not fall on the weekend.",
+ "ident": "is_weekday",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 193,
+ "line": 10,
+ "column": 1
+ },
+ "value": true
+ },
+ "main": {
+ "desc": "",
+ "ident": "main",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 338,
+ "line": 14,
+ "column": 1
+ },
+ "value": true
+ }
+ }
+ },
+ "rule_detail": {},
+ "config_warnings": null,
+ "config_legacy": false
+ }
+ }
+ }
+ },
+ "duration": 10
+}
+```
diff --git a/content/sentinel/v0.29.x/content/sentinel/docs/writing/tracing.mdx b/content/sentinel/v0.29.x/content/sentinel/docs/writing/tracing.mdx
new file mode 100644
index 0000000000..e98767da00
--- /dev/null
+++ b/content/sentinel/v0.29.x/content/sentinel/docs/writing/tracing.mdx
@@ -0,0 +1,403 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-tracing
+description: >-
+ Sentinel provides execution traces to better understand the execution of a
+ policy.
+layout: docs
+---
+
+# Traces
+
+Sentinel provides execution traces to better understand the execution of a policy.
+
+When using the Sentinel [CLI](/sentinel/commands), traces are shown
+on policy failure during `apply` or `test`, or when the `-trace` flag is
+explicitly specified. For Sentinel-enabled applications, traces are optional
+and may require special configuration to enable.
+
+The availability of the trace may vary from application to application. While
+some applications may only log traces on failure, others, such as [Terraform
+Cloud](https://www.terraform.io/cloud), log the trace on every execution. For
+more details, consult your implementation's documentation.
+
+-> **Note:** We're actively improving Sentinel tracing in each release
+to provide better data and improve readability. The exact format of a
+trace may not match the Sentinel version embedded in the application you're
+using, but the data should be similar. The canonical format for a trace should
+get more stable as we approach a 1.0 release.
+
+## Analyzing a Trace
+
+To show traces, let's use the example policy below with the CLI:
+
+```sentinel
+param day
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+
+main = rule { is_open_hours and is_weekday }
+```
+
+Let's cause the policy to fail so the trace is more interesting.
+If you're on a terminal that supports it, the trace output will be colored
+so you can more clearly see passes, failures, and rules.
+
+
+
+ $ sentinel apply main.sentinel{'\n'}
+
+ main.sentinel:1:7: requires value for parameter day
+
+ {'\n'}
+ {'\n'}
+ {'\n'}
+ {' '}Values can be strings, floats, or JSON array or object values. To force
+ {'\n'}
+ {' '}strings, use quotes.{'\n'}
+ {'\n'}
+ {' '}Enter a value: sunday{'\n'}
+ {'\n'}
+
+ main.sentinel:2:7: requires value for parameter hour
+
+ {'\n'}
+ {'\n'}
+ {'\n'}
+ {' '}Values can be strings, floats, or JSON array or object values. To force
+ {'\n'}
+ {' '}strings, use quotes.{'\n'}
+ {'\n'}
+ {' '}Enter a value: 14{'\n'}
+ {'\n'}
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+
+ Fail - main.sentinel
+
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:7:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}
+ false
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:5:1 - Rule "is_open_hours"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}true{'\n'}
+ {'\n'}
+
+ main.sentinel:4:1 - Rule "is_weekday"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}false{'\n'}
+
+
+
+We can see all of our rules neatly and clearly displayed along with the result
+of the policy. If you are getting the trace due to a failed policy and not
+because you explicitly used `-trace`, an informational message is displayed.
+
+As we can see from our basic example, the `main` rule has failed and its result
+is highlighted in red. All peripheral rules that were also evaluated as part of
+the policy are also displayed below the main rule. Source positions are also
+displayed so that you can find the references to the rules in your code.
+
+Via the trace, we can clearly see that while `is_open_hours` returned a true
+result, `is_weekday` did not, and per the logic in our code, the policy is
+rejected.
+
+## Non-Boolean Rules and the Trace
+
+The trace is particularly important when working with rules that return
+non-boolean data, such as rules where you want to see violations instead of a
+simple opaque boolean value. Let's take our example from the [rules](./rules)
+section, and write a small static policy for it:
+
+```sentinel playground
+days = [
+ {"weekday": "Sunday", "weather": "clouds"},
+ {"weekday": "Monday", "weather": "rain"},
+ {"weekday": "Tuesday", "weather": "sunny"},
+ {"weekday": "Wednesday", "weather": "sunny"},
+ {"weekday": "Thursday", "weather": "clouds"},
+ {"weekday": "Friday", "weather": "rain"},
+ {"weekday": "Saturday", "weather": "sunny"},
+]
+
+main = rule {
+ filter days as d {
+ d.weather is not "sunny"
+ }
+}
+```
+
+Here is the output of our policy:
+
+
+
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+ {'\n'}
+
+ Fail - weather.sentinel
+
+ {'\n'}
+ {'\n'}
+
+ weather.sentinel:11:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+
+ {' [\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Sunday"'}
+ {'\n'}
+ {' "weather": "clouds"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Monday"'}
+ {'\n'}
+ {' "weather": "rain"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Thursday"'}
+ {'\n'}
+ {' "weather": "clouds"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Friday"'}
+ {'\n'}
+ {' "weather": "rain"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' ]'}
+
+
+
+
+We can now see all of the weekdays with non-sunny weather.
+
+Note that to prevent formatting issues and excessive output, the human-readable
+trace is limited in the volume of output it will display for collections.
+
+## JSON Output
+
+The trace can also be displayed in JSON by adding `-json` to `sentinel apply`
+and `sentinel test`, and will display the trace in its entirety, unlike the
+standard CLI output.
+
+Here is the result of running `sentinel apply -json` against our weather policy:
+
+```json
+{
+ "can_override": false,
+ "error": null,
+ "duration": 12,
+ "policies": [
+ {
+ "allowed_failure": false,
+ "error": null,
+ "duration": 12,
+ "policy": "weather.sentinel",
+ "result": false,
+ "trace": {
+ "description": "",
+ "error": null,
+ "print": "",
+ "result": false,
+ "rules": {
+ "main": {
+ "desc": "",
+ "ident": "main",
+ "position": {
+ "filename": "weather.sentinel",
+ "offset": 328,
+ "line": 11,
+ "column": 1
+ },
+ "value": [
+ {
+ "weather": "clouds",
+ "weekday": "Sunday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Monday"
+ },
+ {
+ "weather": "clouds",
+ "weekday": "Thursday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Friday"
+ }
+ ]
+ }
+ }
+ }
+ }
+ ],
+ "result": false
+}
+```
+
+Note that the trace is always included with this output, regardless of whether
+or not the policy passes or fails; using `-trace` is unnecessary.
+
+Additionally, with `sentinel apply`, you can display the value of a single rule
+in JSON, instead of the entire trace. This is done with the `-json-rule` flag.
+
+-> **Note:** `sentinel apply -json-rule` needs to be run either against a single
+on-disk policy, or against a single policy within a policy set. It is ignored
+in full policy set executions and functions exactly like `-json` in these
+scenarios.
+
+Here is the result of executing `sentinel apply -json-rule main weather.sentinel`:
+
+```json
+[
+ {
+ "weather": "clouds",
+ "weekday": "Sunday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Monday"
+ },
+ {
+ "weather": "clouds",
+ "weekday": "Thursday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Friday"
+ }
+]
+```
+
+## Other Trace Details
+
+There are some other details that are good to know when working with the trace:
+
+- Only rules that get executed are added to the trace. Considering our first
+ example where the `main` rule is the expression `is_open_hours and is_weekday`,
+ if our expression was using an `or` operator instead of `and`, in addition to
+ the policy passing, `is_open_hours` would be present in the trace, but
+ `is_weekday` would not be, as the policy would have never evaluated that rule.
+- Descriptions for both rules and policies will show up in the trace as well if
+ present. Here is the output to our first policy with the appropriate
+ annotations:
+
+
+
+ ...{'\n\n'}
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+
+ Fail - main.sentinel
+
+ {'\n'}
+ {'\n'}
+ Description:{'\n'}
+ {' A very basic policy to determine if a run is within working\n'}
+ {' hours.\n'}
+ {'\n'}
+
+ main.sentinel:7:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}
+ false
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:5:1 - Rule "is_open_hours"
+
+ {'\n'}
+ {' '}
+ Description:
+ {'\n'}
+ {' Passes if run during business hours.\n'}
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}true{'\n'}
+ {'\n'}
+
+ main.sentinel:4:1 - Rule "is_weekday"
+
+ {'\n'}
+ {' '}
+ Description:
+ {'\n'}
+ {' Passes if the day does not fall on the weekend.\n'}
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}false{'\n'}
+
+
diff --git a/content/sentinel/v0.29.x/data/docs-nav-data.json b/content/sentinel/v0.29.x/data/docs-nav-data.json
new file mode 100644
index 0000000000..dcd6d9f835
--- /dev/null
+++ b/content/sentinel/v0.29.x/data/docs-nav-data.json
@@ -0,0 +1,395 @@
+[
+ {
+ "title": "What is Sentinel?",
+ "path": "intro"
+ },
+ {
+ "title": "Why Sentinel?",
+ "path": "why"
+ },
+ {
+ "title": "Release Notes",
+ "path": "changelog"
+ },
+ {
+ "title": "Basic Concepts",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "concepts"
+ },
+ {
+ "title": "Policy as Code",
+ "path": "concepts/policy-as-code"
+ },
+ {
+ "title": "Policy Language",
+ "path": "concepts/language"
+ },
+ {
+ "title": "Imports",
+ "path": "concepts/imports"
+ },
+ {
+ "title": "Enforcement Levels",
+ "path": "concepts/enforcement-levels"
+ }
+ ]
+ },
+ {
+ "title": "Configuration File Syntax",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "configuration"
+ },
+ {
+ "title": "Override Files",
+ "path": "configuration/overrides"
+ },
+ {
+ "title": "Remote Sources",
+ "path": "configuration/remote-sources"
+ }
+ ]
+ },
+ {
+ "title": "Commands (CLI)",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "commands"
+ },
+ {
+ "title": "apply",
+ "path": "commands/apply"
+ },
+ {
+ "title": "fmt",
+ "path": "commands/fmt"
+ },
+ {
+ "title": "test",
+ "path": "commands/test"
+ }
+ ]
+ },
+ {
+ "title": "Writing Policy",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "writing"
+ },
+ {
+ "title": "Basics",
+ "path": "writing/basic"
+ },
+ {
+ "title": "Rules",
+ "path": "writing/rules"
+ },
+ {
+ "title": "Traces",
+ "path": "writing/tracing"
+ },
+ {
+ "title": "Testing",
+ "path": "writing/testing"
+ },
+ {
+ "title": "Imports",
+ "path": "writing/imports"
+ },
+ {
+ "title": "Debugging",
+ "path": "writing/debugging"
+ }
+ ]
+ },
+ {
+ "title": "Extending Sentinel",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "extending"
+ },
+ {
+ "title": "Modules",
+ "path": "extending/modules"
+ },
+ {
+ "title": "Plugins",
+ "path": "extending/plugins"
+ },
+ {
+ "title": "Static Imports",
+ "path": "extending/static-imports"
+ },
+ {
+ "title": "Internals",
+ "path": "extending/internals"
+ }
+ ]
+ },
+ {
+ "title": "Features",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "features"
+ },
+ {
+ "title": "terraform",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "features/terraform"
+ },
+ {
+ "title": "tfplan/v1",
+ "path": "features/terraform/tfplan-v1"
+ },
+ {
+ "title": "tfplan/v2",
+ "path": "features/terraform/tfplan-v2"
+ },
+ {
+ "title": "tfconfig/v1",
+ "path": "features/terraform/tfconfig-v1"
+ },
+ {
+ "title": "tfconfig/v2",
+ "path": "features/terraform/tfconfig-v2"
+ },
+ {
+ "title": "tfstate/v1",
+ "path": "features/terraform/tfstate-v1"
+ },
+ {
+ "title": "tfstate/v2",
+ "path": "features/terraform/tfstate-v2"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Language",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "language"
+ },
+ {
+ "title": "Variables",
+ "path": "language/variables"
+ },
+ {
+ "title": "Values",
+ "path": "language/values"
+ },
+ {
+ "title": "Lists",
+ "path": "language/lists"
+ },
+ {
+ "title": "Maps",
+ "path": "language/maps"
+ },
+ {
+ "title": "Rules",
+ "path": "language/rules"
+ },
+ {
+ "title": "Imports",
+ "path": "language/imports"
+ },
+ {
+ "title": "Parameters",
+ "path": "language/parameters"
+ },
+ {
+ "title": "Boolean Expressions",
+ "path": "language/boolexpr"
+ },
+ {
+ "title": "Arithmetic",
+ "path": "language/arithmetic"
+ },
+ {
+ "title": "Slices",
+ "path": "language/slices"
+ },
+ {
+ "title": "Conditionals",
+ "path": "language/conditionals"
+ },
+ {
+ "title": "Loops",
+ "path": "language/loops"
+ },
+ {
+ "title": "Collection Operations",
+ "path": "language/collection-operations"
+ },
+ {
+ "title": "Functions",
+ "path": "language/functions"
+ },
+ {
+ "title": "Scope",
+ "path": "language/scope"
+ },
+ {
+ "title": "Undefined",
+ "path": "language/undefined"
+ },
+ {
+ "title": "Logging and Errors",
+ "path": "language/logging-errors"
+ },
+ {
+ "title": "Specification",
+ "path": "language/spec"
+ }
+ ]
+ },
+ {
+ "title": "Builtin Functions",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "functions"
+ },
+ {
+ "title": "append",
+ "path": "functions/append"
+ },
+ {
+ "title": "compare",
+ "path": "functions/compare"
+ },
+ {
+ "title": "delete",
+ "path": "functions/delete"
+ },
+ {
+ "title": "error",
+ "path": "functions/error"
+ },
+ {
+ "title": "keys",
+ "path": "functions/keys"
+ },
+ {
+ "title": "length",
+ "path": "functions/length"
+ },
+ {
+ "title": "print",
+ "path": "functions/print"
+ },
+ {
+ "title": "range",
+ "path": "functions/range"
+ },
+ {
+ "title": "values",
+ "path": "functions/values"
+ }
+ ]
+ },
+ {
+ "title": "Standard Imports",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "imports"
+ },
+ {
+ "title": "collection",
+ "routes": [
+ {
+ "title": "Reference",
+ "path": "imports/collection"
+ },
+ {
+ "title": "collection/maps",
+ "path": "imports/collection/maps"
+ },
+ {
+ "title": "collection/lists",
+ "path": "imports/collection/lists"
+ }
+ ]
+ },
+ {
+ "title": "base64",
+ "path": "imports/base64"
+ },
+ {
+ "title": "decimal",
+ "path": "imports/decimal"
+ },
+ {
+ "title": "http",
+ "path": "imports/http"
+ },
+ {
+ "title": "json",
+ "path": "imports/json"
+ },
+ {
+ "title": "runtime",
+ "path": "imports/runtime"
+ },
+ {
+ "title": "sockaddr",
+ "path": "imports/sockaddr"
+ },
+ {
+ "title": "strings",
+ "path": "imports/strings"
+ },
+ {
+ "title": "time",
+ "path": "imports/time"
+ },
+ {
+ "title": "types",
+ "path": "imports/types"
+ },
+ {
+ "title": "units",
+ "path": "imports/units"
+ },
+ {
+ "title": "version",
+ "path": "imports/version"
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Consul",
+ "path": "consul"
+ },
+ {
+ "title": "Nomad",
+ "path": "nomad"
+ },
+ {
+ "title": "Terraform",
+ "path": "terraform"
+ },
+ {
+ "title": "Vault",
+ "path": "vault"
+ }
+]
diff --git a/content/sentinel/v0.29.x/img/sentinel-import-topology.svg b/content/sentinel/v0.29.x/img/sentinel-import-topology.svg
new file mode 100644
index 0000000000..36f7c79bc9
--- /dev/null
+++ b/content/sentinel/v0.29.x/img/sentinel-import-topology.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/content/sentinel/v0.29.x/redirects.jsonc b/content/sentinel/v0.29.x/redirects.jsonc
new file mode 100644
index 0000000000..1c7c77f138
--- /dev/null
+++ b/content/sentinel/v0.29.x/redirects.jsonc
@@ -0,0 +1,7 @@
+[
+ {
+ "source": "/sentinel/commands/config",
+ "destination": "/sentinel/configuration",
+ "permanent": true,
+ },
+]
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/changelog.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/changelog.mdx
new file mode 100644
index 0000000000..c729a39105
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/changelog.mdx
@@ -0,0 +1,944 @@
+---
+page_title: Sentinel Runtime Release Notes
+sidebar_current: docs-changelog
+description: >-
+ These are the public release notes for the Sentinel runtime. See this document
+ for the latest updates.
+layout: docs
+---
+
+# Sentinel Runtime Release Notes
+
+These are the release notes for the Sentinel runtime.
+
+Each version of the runtime is released with a corresponding version of
+[Sentinel CLI](/sentinel/commands). To download the CLI, see the [install
+page](/sentinel/install).
+
+Sentinel integrations and embedded runtimes may not always have the latest
+version installed, depending on the product's individual release cycle. For more
+information, contact the support team for your specific integration.
+
+
+## 0.30.0 (February 17, 2025)
+
+ENHANCEMENTS:
+
+- `imports/json`: Added the ability to check if a value is valid JSON through
+the `valid` method.
+
+
+## 0.29.0 (November 26, 2024)
+
+ENHANCEMENTS:
+
+- `imports/collection/lists`: Added `difference`, `union` and
+`intersection` helpers to the `collection/lists` import.
+
+BUG FIXES:
+
+- `imports/json`: Any list element or map key/value that is `undefined`
+will now be ignored within the `marshal` method instead of returning `{}`.
+Additionally, directly passing `undefined` will return `undefined` as
+documented.
+
+## 0.28.0 (September 26, 2024)
+
+BUG FIXES:
+
+- `imports/collection`: The `matches` helper now returns no results if there
+are no matches found in the collection.
+
+ENHANCEMENTS:
+
+- `cmd/apply`: A new argument, `-timeout`, has been added to the apply command.
+This argument will allow the apply command to exit after a certain time has been
+reached.
+
+NOTES:
+
+- This release introduces a EULA and Terms Of Evaluation to the release artifacts,
+which will be included in all releases going forward.
+
+## 0.27.0 (August 7, 2024)
+
+ENHANCEMENTS:
+- `imports/collection/maps`: Added an `unset` helper to the `collection/maps`
+import, providing the ability to remove keys using a provided path.
+- `imports/collection/maps`: Added an `omit` helper to the `collection/maps`
+import, which returns a map with all provided paths removed from the source
+map.
+- `imports/strings`: Added the `replace`, `trim`, `trim_left`, `trim_right` and
+`trim_space` helpers to the `strings` import.
+
+## 0.26.3 (July 19, 2024)
+
+ENHANCEMENTS:
+
+- `imports/collection/maps`: Added a `set` helper to the `collection/maps`
+import, providing the ability to set values deeply within a map.
+- `imports/collection/maps`: Added a `pick` helper to the `collection/maps`
+import, providing the ability to select a series of values from a map.
+
+## 0.26.2 (June 19, 2024)
+
+ENHANCEMENTS:
+
+- `imports/collection`: Added a `filter` helper to the `collection` import,
+providing a functional approach to filtering collections.
+
+## 0.26.1 (May 22, 2024)
+
+NOTES:
+
+- This release changes lower-level components and there are no user-facing changes.
+
+## 0.26.0 (May 15, 2024)
+
+FEATURES:
+
+- `imports/collection`, `imports/collection/lists` and `import/collection/maps`:
+Adding new helper imports for dealing with collections (lists, maps), helping
+avoid complexities within policy code.
+- `sentinel/eval`: Fixed an issue with per-policy parameters where parameters
+may leak from one policy into the following policy.
+
+## 0.25.1 (Apr 18, 2024)
+
+BREAKING CHANGES:
+
+- `imports/http`: The default `Accept` header has been changed to `*/*`, removing a redundant
+extension that some servers may not accept by default.
+
+## 0.25.0 (Apr 8, 2024)
+
+ENHANCEMENTS:
+
+- `cmd/apply`: JSON results will return an additional `duration` field for individual policies as well as the evaluation as a whole.
+- `cmd/test`: `sentinel test` will return an additional `duration` field in the JSON output.
+
+BUG FIXES:
+
+- `cmd`: Removed redundant debug logging for custom plugins.
+
+## 0.24.4 (Mar 21, 2024)
+
+ENHANCEMENTS:
+
+- `runtime/format`: Null values will now print correctly for rule value outputs.
+- `config`: Some internal changes to configuration parsing workflow.
+
+## 0.24.3 (Feb 9, 2024)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.24.2 (Jan 31, 2024)
+
+BUG FIXES:
+
+- `imports/static`: Fixed an issue where `nil` values provided to the built-in
+static import were being treated as `undefined` in policy.
+
+## 0.24.1 (Jan 19, 2024)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where the Sentinel cache was unstable due to concurrent
+tests.
+
+## 0.24.0 (Dec 7, 2023)
+
+ENHANCEMENTS:
+
+- `cmd/apply`, `cmd/test`: Custom import plugins can now be fetched from remote
+sources.
+- `cmd/apply`, `cmd/test`: Static imports can now be fetched from remote
+sources.
+- `features/terraform`: Added support for `resource_drift` in the `tfplan/v2` import.
+
+## 0.23.1 (Oct 19, 2023)
+
+BUG FIXES:
+
+- `cmd/apply`: Warnings and errors are included in the JSON output if the json flag is enabled.
+
+## 0.23.0 (Sept 5, 2023)
+
+FEATURES:
+
+- `features/apply_all`: Adds the `apply-all` feature, allowing for all policies to evaluate regardless of result, instead of exiting on first failure.
+
+BUG FIXES:
+
+- `features/terraform`: The `tfplan/v1` import correctly handles nested attribute schemas.
+
+## 0.22.1 (June 22, 2023)
+
+BUG FIXES:
+
+- `sentinel/eval`: Under certain conditions, per-policy parameters would cause
+ Sentinel to panic. This has been resolved.
+
+## 0.22.0 (May 31, 2023)
+
+- `config`: Configuration now supports a `sentinel` block to manage the
+ Sentinel runtime.
+
+## 0.21.1 (May 8, 2023)
+
+BUG FIXES:
+
+- `config`: An issue with certain identifiers being treated as incorrectly invalid
+ has been resolved.
+
+## 0.21.0 (March 8, 2023)
+
+BREAKING CHANGES:
+
+- `lang/ast`: `is empty` and `is not empty` are now treated as `ast.UnaryExpr`
+ expressions, with `ast.IsEmptyExpr` being removed.
+- `lang/ast`: You can now assert if a value is defined or not using the `is defined`
+ and `is not defined` syntax.
+
+FEATURES:
+
+- `config`: Parameter values can now be provided for individual policies within
+ a policy block.
+
+## 0.20.0 (February 16, 2023)
+
+ENHANCEMENTS:
+
+- `cmd/testcmd`: Allows sentinel tests to run concurrently by default. Sequential style testing can
+ be enabled by running with the `-maxConcurrency=1` option.
+- `cmd/testcmd`: Allows sentinel test command to timeout after a certain duration. This can be provided
+ by the user or will default to 5 minutes.
+- `cmd/apply`: The policy enforcement level is now included in the JSON output.
+- `lang/ast`: Functions can now be declared as named statements, providing
+ a safer function declaration.
+
+BREAKING CHANGES:
+
+- `cmd/apply`: Policies provided directly to the apply command will now default their enforcement
+ level to `advisory`, aligning with the `policy` configuration block.
+- `sentinel`: JSON results will no longer return `allowed_failure` or `can_override` fields.
+- `sentinel/result`: A new package has been added which provides additional methods to return
+ supplemental data about the evaluation result.
+
+## 0.19.5 (February 9, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `SelectorExpr` will correctly rewrite `X`.
+
+## 0.19.4 (February 8, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `Compile` function will correctly set the `Original`
+ field when rewriting a `CallExpr` as an `ImportExpr`.
+
+## 0.19.3 (February 3, 2023)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.19.2 (January 17, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `Compile` function will no longer modify `ast.CallExpr`
+ arguments in place.
+
+## 0.19.1 (December 14, 2022)
+
+BUG FIXES:
+
+- `lang/ast`: The `Rewrite` function will no longer modify the provided Node
+ in place.
+
+ENHANCEMENTS:
+
+- `lang/semantic`: The Break semantic check uses the AST walker.
+
+## 0.19.0 (December 6, 2022)
+
+BREAKING CHANGES:
+
+- `config`: Attempts to override a standard import with a custom import will
+ now be met with an error.
+- `imports`: All plugin imports now return `sdk.Plugin` instead of `sdk.Import`,
+ as per the SDK update to v0.4.0.
+
+ENHANCEMENTS:
+
+- `runtime/importer`: An `Import` and `ImportInstance` interface have been
+ added to improve the import capabilities.
+- `runtime/importer`: The `Importer` interface now returns a `Import` interface
+ instead of a `sdk.Import`.
+- `lang/semantic`: The Import Assignment semantic check uses the AST walker.
+- `lang/ast`: Added an AST Walker so the AST rewriter does not need to be used for basic AST tasks.
+
+FEATURES:
+
+- `config`: Configuration syntax now allows for configuration of imports within
+ the standard library.
+- `config`: A new configuration syntax for defining modules and plugins is now
+ available.
+- Static data can now be added via `import` configuration.
+- `cmd/apply`: The `apply` command now supports multiple primary configuration
+ files by default, loading all configuration files within the working
+ directory.
+- `cmd/apply`: The `apply` command now accepts applying configuration overrides
+ to primary configuration.
+
+## 0.18.13 (October 31, 2022)
+
+BUG FIXES:
+
+- `cmd/apply`: `sentinel apply` no longer aborts on advisory policy failure.
+
+## 0.18.12 (September 15, 2022)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where nested `CallExpr` were not rewritten.
+
+## 0.18.11 (June 8, 2022)
+
+NOTES:
+
+- `config`: The `enforcement_level` key for policies is now optional, defaulting to `"advisory"` when not supplied.
+
+## 0.18.10 (May 20, 2022)
+
+NOTES:
+
+- `runtime/initwd`: Improvements to the installer, including timeouts and filesize limits, as well as disabling support
+ for symlinks within `git` or `hg` repositories.
+
+## 0.18.9 (March 23, 2022)
+
+NOTES:
+
+- This release fixes an issue with the Docker container build pipeline. There are no changes to the
+Sentinel runtime or CLI.
+
+## 0.18.8 (March 22, 2022)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue in the installer for Windows paths.
+
+## 0.18.7 (February 24, 2022)
+
+NOTES:
+
+- **Darwin arm64**. This release will be our first to include Darwin arm64 architecture.
+
+BUG FIXES:
+
+- `lang/ast`: Fixed issues where `IndexSpec` and `RuleLit` nodes where returning incorrect end positions.
+- `lang/ast`: Fixed an issue where `ParamSpec` nodes where returning incorrect end positions.
+
+## 0.18.6 (February 2, 2022)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.18.5 (January 14, 2022)
+
+IMPROVEMENTS:
+
+- `runtime/initwd`: Changed the sum algorithm used during remote installation from `md5` to `sha256` to reduce chance of collision.
+
+## 0.18.4 (July 20, 2021)
+
+FEATURES:
+
+- `imports/static`: Improved handling of struct tags when performing encoding, allowing `-` to mark as ignored.
+
+## 0.18.3 (June 1, 2021)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue for remote source installation where duplicate sub-directories resulted in incorrect installation.
+- `imports/static`: Fixed an issue where global configuration that contained an empty map could not be accessed without raising selector issues.
+
+## 0.18.2 (May 18, 2021)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where import expressions inside lists and maps were not being evaluated correctly.
+
+## 0.18.1 (May 11, 2021)
+
+BUG FIXES:
+
+- `imports/json`: Fixed an issue where empty maps where failing when passed at any level to `marshal`.
+
+## 0.18.0 (March 25, 2021)
+
+FEATURES:
+
+- `imports/http`: Added support for POST actions within the `http` import
+ through addition of the `post` function. Additionally, support for adding
+ a body to the `request` object has been added via `with_body`.
+
+## 0.17.4 (February 2, 2021)
+
+BUG FIXES:
+
+- `runtime/format`: Fixed a nesting issue with the rule printer where nesting
+ state leaks were leading to all collection values eventually being truncated,
+ regardless of their nesting level.
+
+## 0.17.3 (January 15, 2021)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue where local file installation failed on Windows
+ when using `sentinel test`.
+
+## 0.17.2 (January 13, 2021)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where duplicate log messages were being printed with
+ `sentinel test`.
+
+## 0.17.1 (January 7, 2021)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where hard-coded path separator caused `sentinel
+ test` issues in Windows.
+
+## 0.17.0 (January 5, 2021)
+
+LANGUAGE CHANGES:
+
+- **Map Expression** `map`. A new quantifier expression, `map` has been added.
+ `map` takes a collection and applies an operation to each element in the
+ collection, returning a list of results with the resulting values.
+- **Emptiness checks** `is empty` and `is not empty`. Two new expressions,
+ `is empty` and `is not empty` have been added. As they are aliases to the
+ `length` built-in, only `string`, `map`, `list` or `undefined` is
+ supported.
+- **Comparable maps** Maps (the data type) are now comparable. Maps are equal if
+ they are of equal length and both their corresponding keys and values are
+ comparable and equal.
+- **Rich Return Types** Rules can now process and return non-boolean data.
+ Scalar, list, and map types are supported. When non-boolean values are used,
+ the presence of a non-zero or non-zero-length `main` rule determines policy
+ failure. More details can be found on
+ https://docs.hashicorp.com/sentinel/language#main.
+
+FEATURES:
+
+- **CLI**: `sentinel apply` and `sentinel test` now have options for JSON
+ output. For more details, see the CLI documentation.
+- `imports/base64`: Added a new import, `base64`, to assist with encoding and
+ decoding base64 strings.
+
+## 0.16.1 (October 21, 2020)
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where `contains` to ensure any instance of `undefined` would correctly result in `undefined`.
+- `runtime/eval`: Fixed an issue in `any` and `all` expressions to ensure correct boolean logic when dealing with a collection containing `undefined`.
+- `imports`: Fixed an issue where import processes were not killed, which caused non-graceful closures for the clients.
+- `lang/scanner`: Fixed an issue where inline `#` style comments were not being consumed.
+
+## 0.16.0 (October 14, 2020)
+
+BREAKING CHANGES:
+
+- `cmd/doc`: Removal of the deprecated `sentinel doc` command.
+- `sentinel`: Replace `whitelist` and `blacklist` with `allowlist` and `denylist`, as per inclusive language changes.
+
+FEATURES:
+
+- `imports/version`: Added a new import, `version`, which supplies helpers for dealing with semantic versioning.
+- `cmd/apply`: Added an `HCL` based configuration file, which will eventually deprecate the legacy `JSON` configuration.
+- `cmd/apply`: Added support for download remote policies and modules.
+- `cmd/test`: Added support for downloading remote test modules.
+- `cmd/apply`: Added support for evaluating a policy based on it's key within the configuration.
+- `cmd/apply`: Added support for running all policies in a configuration by default.
+
+## 0.15.6 (July 7, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.15.5 (May 20, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel integrations. There are no user-facing changes.
+
+## 0.15.4 (May 13, 2020)
+
+BUG FIXES:
+
+- `imports`: Fixed an issue with the standard imports that was affecting the handling of null data within lists.
+
+## 0.15.3 (April 16, 2020)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where `IndexExpr` were not rewritten, as well as ensuring `SelectorExpr` uses any nested rewrites.
+- `lang/printer`: Fixed issue printing deeply nested structures and/or loops.
+
+IMPROVEMENTS:
+
+- `imports/decimal`: Added alias methods to improve consistency of method names within the `decimal` import. The alias methods are `is_nan` for `isnan`, as well as both `is_inf` and `is_infinite` for `isinfinite`.
+- `command/apply`: `sentinel apply` will now output the policy description when a trace is forced via `-trace`.
+- `sentinel`: Improved `String` output formatting of Policy docstring for `EvalPolicyResult`.
+
+## 0.15.2 (April 2, 2020)
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue with the `printer` that was causing a panic
+ when a `#` line comment was used with no text following it.
+- `imports/types`: Fixed an issue with the `types` import where calls to
+ `type_of` for undefined types were incorrectly returning as map types. These
+ will now be correctly be identified as undefined as expected.
+
+## 0.15.1 (March 6, 2020)
+
+BUG FIXES:
+
+- `sentinel`: Fixed how modules are managed internally in the runtime to correct
+ race conditions in concurrent scenarios and to allow for per-evaluation module
+ restrictions. This functionality is only of interest to embedded applications
+ with long-lived runtimes and is currently not implemented in any HashiCorp
+ integration.
+
+## 0.15.0 (March 5, 2020)
+
+NOTES:
+
+- **Windows Digital Signature**. Our windows releases for this version and up will be signed
+ and verified according to Microsoft's requirements.
+
+FEATURES:
+
+- `cmd/config`: Modules can now be loaded off of local disk using the Sentinel
+ CLI. For information on how to do so, see the Sentinel documentation.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: Changed modules so that they now have singleton scope when
+ loaded. Importing a module under two different aliases, or within a nested
+ module, will now share scopes - modification of state in one will alter the
+ other.
+- `lang/object`: Introduced an interface to allow the duplication of various
+ types, like collections.
+- `runtime/eval`: Changed how collection data is returned from modules. This
+ data is now duplicated to prevent accidental or deliberate circumvention of
+ the prohibition on assignment to import data, and brings behavior to parity
+ with binary imports.
+
+## 0.14.4 (February 6, 2020)
+
+IMPROVEMENTS:
+
+- `imports/time`: Changed the `add` timespace function in the `time` import to
+ allow it to take float types as durations. These values will be truncated to
+ the appropriate integer-value duration.
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where out-of-place right-hand braces were being
+ parsed as empty statements, instead of raising syntax errors.
+
+## 0.14.3 (January 22, 2020)
+
+IMPROVEMENTS:
+
+- `cmd/test`: Fail when an assertion is not found in a trace.
+- `cmd/test`: Added a message to notify when no rules were evaluated in a test.
+
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue where import aliases (the `as baz` in `import
+ "foo/bar" as baz`) were being removed when formatting with `sentinel fmt`.
+
+- `imports/http`: Fixed an issue with the http import where the client library's
+ debug logs were being printed to standard error.
+
+## 0.14.2 (January 15, 2020)
+
+NOTES:
+
+With this release, we are discontinuing support for MacOS 32-bit. 64-bit builds
+are now the only builds available for MacOS.
+
+IMPROVEMENTS:
+
+- `lang/printer`: A newline is now added to files formatted via `sentinel fmt`.
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue in printing where multi-line expressions would
+ indent infinitely. They will now only indent once at the most. This is mostly
+ seen when using `sentinel fmt`.
+- `runtime/encoding`: Fixed an issue where if a plugin returned a deeply
+ embedded undefined value, the value was instead decoded into a map.
+
+## 0.14.1 (January 6, 2020)
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where reserved words could not be used as
+ selectors when the selector expression was the last lexeme on a line.
+
+## 0.14.0 (December 18, 2019)
+
+LANGUAGE CHANGES:
+
+- **Filter Expression** `filter`. A new quantifier expression, `filter` has been added.
+ `filter` returns a subset of the provided collection based on a boolean condition that
+ is asserted for each element.
+
+NOTES:
+
+- **Apple Notarization**. Our darwin releases for this version and up will be signed
+ and notarized according to Apple's requirements.
+
+## 0.13.1 (November 25, 2019)
+
+BUG FIXES:
+
+- `runtime/eval`: Parameters are no longer allowed in mock files. Adding one to
+ a mock will result in a runtime error.
+- `runtime/eval`: Fixed an issue where import calls from within mocks were
+ failing under certain circumstances.
+- `imports/decimal`: `int` should now correctly provide the truncated integer
+ representation of a decimal number, not the rounded one.
+
+## 0.13.0 (November 15, 2019)
+
+LANGUAGE CHANGES:
+
+- **Policy Parameters**. The new [policy
+ parameters](https://docs.hashicorp.com/sentinel/language/parameters) feature
+ allows authors to use a `param` declaration at the top of a policy to supply
+ values that are expected to come from outside of a policy. See the linked
+ documentation for more details.
+
+FEATURES:
+
+- `imports/http`: The [`http`
+ import](https://docs.hashicorp.com/sentinel/imports/http) is a new addition
+ to the standard library enabling the use of HTTP-accessible data from outside
+ the runtime in policy rules.
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where loading other imports before a mocked
+ import would cause those imports to no longer be visible from within the
+ policy.
+
+## 0.12.0 (October 7, 2019)
+
+LANGUAGE CHANGES:
+
+- **New `case` statement**. This statement is a selection control mechanism to
+ conditionally execute a branch based on expression equality, allowing
+ simplification of complex conditional chains that may otherwise need to be
+ written with `else if`. See the [Case
+ Statements](https://docs.hashicorp.com/sentinel/language/spec#case-statements)
+ section of the Sentinel language specification for more details.
+
+IMPROVEMENTS:
+
+- `lang/semantic`: Added a semantic check to ensure usage of append is not using
+ a return value.
+
+BUG FIXES:
+
+- `runtime/eval`: Corrected an issue where calling a method on an import object
+ value that was the result of a method call on another import object value
+ would have erroneously tried to call an import of the name of the "parent"
+ import object value. Example: in `a = subject.new(); b = a.call(); c =
+ b.call()`, `b.call()` would attempt to call a method named `call` on the root
+ namespace for an import named `a`. This has now been corrected so that
+ `b.call()` will now correctly call the `call` method for the respective object
+ namespace residing in the import named `subject`.
+- `imports`: Some standard imports may have been returning null for some unknown
+ keys in objects when they should have been returning undefined. This was due
+ to an SDK issue which was corrected in SDK version 0.3.2, which has now been
+ corrected.
+- `cmd/test`: `sentinel test` will now correctly fail a policy if it encounters
+ an error.
+- `cmd/test`: `sentinel test` will now correctly display errors and other output
+ that were missing due to a formatting issue.
+- `runtime/eval`: The `append` builtin now correctly returns undefined for all
+ calls, as called for by the Specification. Note that in most cases, the
+ semantic check outlined above will trigger an error if the return value is
+ used.
+
+## 0.11.0 (September 5, 2019)
+
+LANGUAGE CHANGES:
+
+- **New builtin function:** `range()`. This function existed in the spec in
+ earlier versions but was removed as it lacked an implementation. This has now
+ been implemented and re-added to the spec. See the
+ [Range](https://docs.hashicorp.com/sentinel/language/spec#range) section of
+ the Sentinel Specification for more details.
+- Lists are now comparable. Lists are equal if their corresponding elements are
+ comparable and equal.
+- Method calls on values returned by imports are now supported. See the
+ [Imports](https://docs.hashicorp.com/sentinel/language/spec#imports) section
+ of the Sentinel Specification for more details.
+
+FEATURES:
+
+- `imports/decimal`: This is a new import designed to do exact precision
+ mathematical calculations.
+- `runtime/eval`: Compound call expressions that refer to imports (example:
+ foo.bar().baz() when `foo` is a loaded import) will now function as expected.
+ Previously, this was only supported up to the first call (example:
+ `foo.bar()`).
+- `imports/time`: Timespaces returned by calls such as `time.now` are now
+ callable. Example: `t = time.now; t.after(some_previous_time)` will now
+ function.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: The implementation of comparison of non-comparable types has
+ now changed. Rather than triggering a runtime error, non-comparable types will
+ now return false when attempting to compare.
+
+## 0.10.4 (August 15, 2019)
+
+BUG FIXES:
+
+- `runtime/encoding`: Fixed an issue with conversion of null values that could
+ lead to crashes in imports.
+
+## 0.10.3 (July 15, 2019)
+
+BUG FIXES:
+
+- `command/config`: Parsing a configuration file where malformed data is
+ encountered after apparently properly-formed data will now report an error. An
+ example would be a situation where misplaced braces would cause a JSON object
+ to only parse part of the configuration file.
+
+## 0.10.2 (June 25, 2019)
+
+BUG FIXES:
+
+- `lang/parser`: Corrected an issue where parsing certain compound binary
+ expressions where the negation predicate (`not`) was in use would cause the
+ negation to have no effect. Example: `foo else "bar" not in "baz"` would have
+ been parsed and evaluated as `foo else "bar" in "baz"`, effectively producing
+ the opposite result.
+
+## 0.10.1 (May 9, 2019)
+
+BUG FIXES:
+
+- `command/test`: `sentinel test` now displays policies without
+tests as an unknown result with [no test files], instead of the somewhat
+erroneous behavior of displaying it as a PASS. A full test run with a mixture
+of passing tests and no tests still results in an overall successful result.
+
+## 0.10.0 (April 18, 2019)
+
+LANGUAGE CHANGES:
+
+- Mixed-number arithmetic operations are now allowed. Addition (`+`),
+ subtraction (`-`), multiplication (`*`), division (`/`), and remainder
+ operations (`%`) are no longer restricted to number values of the same
+ type, and can mix integer and floating point. The result of these operations
+ is always a floating-point number.
+- Remainder (modulo, `%`) operations are now allowed on floating-point numbers.
+
+
+BUG FIXES:
+
+- `lang/parser`: Fixed a bug that affected the use of `not` with `contains`,
+ `matches`, or `in` in a compound expression.
+- `runtime/eval`: Fixed error messages on evaluation errors with `contains` and
+ `in` to indicate that strings are an allowed type along with lists and maps.
+
+
+## 0.9.2 (March 15, 2019)
+
+This is a dependency update related to the changes mentioned in 0.9.1. No other
+changes have been made.
+
+## 0.9.1 (March 14, 2019)
+
+This is a patch release that is required to integrate with the latest versions
+of the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). No other
+changes have been made.
+
+## 0.9.0 (January 28, 2019)
+
+FEATURES:
+
+- `imports/strings`: Added a `join` function to the `strings` import. This can
+ be used to join a list into a string with a specific separator.
+ Multi-dimensional lists and all Sentinel primitives are supported.
+
+## 0.8.1 (January 17, 2019)
+
+BUG FIXES:
+
+- `lang/eval`: Fixed a bug that prevents the effective use of `return`
+ statements in `for` loops.
+
+## 0.8.0 (January 14, 2019)
+
+FEATURES:
+
+- Mocks can now be represented by Sentinel code. This allows for the mocking of
+ functions and other complex data structures that cannot be represented in
+ JSON. For more information on using this feature via mocks, click
+ [here](https://docs.hashicorp.com/sentinel/commands/config#mocking-with-sentinel-code).
+
+
+IMPROVEMENTS:
+
+- Import validation has now been moved to the semantic checking phase. This
+ should result in better reporting of validation errors. In addition, import
+ validation will now enforce the use of an `as` identifier when an import path
+ is not a valid identifier on its own (example: `import "foo/bar"`).
+
+## 0.7.0 (December 12, 2018)
+
+BUG FIXES:
+
+- There have been changes to the runtime in how scope is handled over multiple
+ policy executions. Scope is now correctly unique per single policy execution,
+ and values set or builtins that are overridden in one policy will no longer
+ affect those values within another.
+
+## 0.6.0 (November 30, 2018)
+
+FEATURES:
+
+- `imports/runtime`: This new import allows for one to check various aspects of
+ the Sentinel runtime as it may be embedded in the simulator or a specific
+ implementation. For now, it allows the version to be checked.
+
+## 0.5.1 (November 28, 2018)
+
+IMPROVEMENTS:
+
+- `imports/time`: Added the `zone` and `zone_string` attributes to assist with
+ validation of a timespace's zone.
+- `command/fmt`: Added a new -check flag. This option does not commit changes,
+ but instead checks to see what files need formatting and outputs them on
+ stdout.
+
+BUG FIXES:
+
+- `command/test`: Ensure that passing test results are correctly output one per
+ line. Tests are also now run in a deterministic fashion based on
+ lexicographical (alphabetical) order.
+- `imports/time`: `month_name` and `weekday_name` will now show up correctly in
+ a returned timespace result.
+
+
+## 0.5.0 (November 5, 2018)
+
+IMPROVEMENTS:
+
+- `spec`: Selectors can now contain any reserved word (example: `rule`) or
+ keyword operator (example: `any`, `all`, `is`, `not`). This only works for the
+ _selector_ part of the expression (after the first period) - the first primary
+ expression (before the first period) still needs to be an identifier that does
+ not conflict with reserved words.
+
+BUG FIXES:
+
+- The simulator should now display import function call names correctly in
+ import errors.
+
+## 0.4.0 (October 1, 2018)
+
+FEATURES:
+
+- `builtin`: Added the `bool` built-in type conversion function. Booleans will
+ also now accepted as conversion into other values as well, with the full list
+ of behaviors available in the spec.
+
+## 0.3.2 (September 27, 2018)
+
+FEATURES:
+
+- `command/apply`: `sentinel apply` now prints out messages output by the
+ `print()` function when a trace is output on policy failure, or when a trace
+ is forced with `-trace`.
+- `imports/time`: Added the `month_name` and `weekday_name` keys to the
+ timespace, which return full-English names for the month and day of the week.
+
+
+BUG FIXES:
+
+- `command/fmt`: `sentinel fmt -` Will no longer print out the filter status
+ message on the output stream when `-write=false` Is not explicitly stated.
+ This brings the behavior of the command in line with the help text.
+- `runtime`: Index operations on the right-hand-side that have negative indexes
+ that go out of range (example: `length(list) * -1 - 1`) now correctly return
+ `undefined`. left-hand-side index assignments with a out-of-range negative
+ index still return runtime errors.
+
+## 0.3.1 (August 3, 2018)
+
+BUG FIXES:
+
+- `runtime`: Basic index assignment has been implemented as per the spec.
+- `runtime`: Index expressions for lists with negative indexes will no longer panic if the
+ list index is less than `length(list) * -1`.
+
+## 0.3.0 (July 20, 2018)
+
+FEATURES:
+
+- **New standard import: `types`.** This can be used to dynamicaly detect the
+ type of some value.
+
+## 0.2.0 (April 11, 2018)
+
+FEATURES:
+
+- **New standard import: `json`.** Marshal and unmarshal JSON documents and
+ access their contents as native Sentinel values.
+- **`break` and `continue`.** These are now both specified and implemented.
+ `break` allows loop exiting and `continue` allows immediate execution of the
+ next iteration.
+
+IMPROVEMENTS:
+
+- runtime: `print()` map values are now ordered alphabetically by keys.
+
+BUG FIXES:
+
+- command/test: If no `test` block exists, test behaves like it is asserting
+ `main: true`.
+- runtime: default maximum stack depth to 500
+- runtime: `print()` map values now appear like more typical maps.
+- runtime: division by zero is an error, not a crash
+- runtime: plugins that send map values with `null` values now decode properly
+ into native Sentinel values.
+
+## 0.1.0 (September 19, 2017)
+
+Initial release.
+
+
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/commands/apply.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/commands/apply.mdx
new file mode 100644
index 0000000000..74b74d41fa
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/commands/apply.mdx
@@ -0,0 +1,66 @@
+---
+page_title: 'Command: apply'
+sidebar_current: docs-commands-apply
+description: >-
+ The `sentinel apply` command is used to execute a policy locally for
+ development purposes.
+layout: docs
+---
+
+# Command: `apply`
+
+The `sentinel apply` command is used to execute a policy locally for development
+purposes.
+
+## Usage
+
+Usage: `sentinel apply [options] POLICY`
+
+This command executes the policy file at the path specified by POLICY. In
+addition to a path to a policy, POLICY can reference a label of a
+[policy](/sentinel/configuration#policies) entry in the configuration.
+
+Use the exit code of this command to determine the exact status of the policy
+evaluation. `0` is pass, `1` is fail, `2` is undefined (fail, but because the
+result was undefined), and `3` is a runtime error. Errors unrelated to the
+policy status itself are returned with an exit status of `9`.
+
+To control the behavior of the `apply` command, create a [configuration
+file](/sentinel/configuration). With this, you can define available
+[import plugins](/sentinel/concepts/imports), mock data, and global values.
+This can help you simulate a policy embedded within an application.
+
+As of the 0.16 release, POLICY is optional. When it is not supplied,
+`sentinel apply` will run **all** policies in the supplied configuration.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-config=path` - Path to a configuration file specifying available imports,
+ mock data, globals, etc. The default is `sentinel.[hcl|json]`.
+
+- `-json` Enable JSON output. Using this mode, all other output will be
+ suppressed and the full detail of the apply will be output in a
+ machine-readable format. Enabling this implies `-trace`. See the section on
+ [tracing JSON output](/sentinel/writing/tracing#json-output) for more details.
+
+- `-json-rule=RULE` Enable JSON output, outputting only the value of the
+ specified rule. When running against a policy set, the policy must be supplied
+ to enable per-rule filtering. Implies `-json`.
+
+- `-global key=value` - Set global values. This is the same as setting `global`
+ in the configuration file, and will override any of these respective values
+ set in the configuration. The value is either a string, or a JSON number,
+ array, or object. To force strings, use quotes.
+
+- `-param key=value` - Set parameters, the same as setting `param` in the
+ configuration file. Values are handled in the same way they are with the
+ `-global` flag.
+
+- `-timeout` - Allows users to specify a timeout after which the apply command will stop running.
+ There is no timeout if not provided. The timeout needs to be specified as a Duration type, eg `100ms, 5s etc`
+
+- `-trace` - Always show the execution trace. This shows intermediate
+ boolean expression values. This always shows for failed policies.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/commands/fmt.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/commands/fmt.mdx
new file mode 100644
index 0000000000..8297316d8b
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/commands/fmt.mdx
@@ -0,0 +1,33 @@
+---
+page_title: 'Command: fmt'
+sidebar_current: docs-commands-fmt
+description: The `sentinel fmt` command formats a policy source to a canonical format.
+layout: docs
+---
+
+# Command: `fmt`
+
+The `sentinel fmt` command formats a policy source to a canonical format.
+
+## Usage
+
+Usage: `sentinel fmt [options] FILE ...`
+
+This command formats all the specified policy files to a canonical format.
+
+By default, policy files are overwritten in place. This behavior can be
+changed with the `-write` flag. If a specified FILE is `-` then stdin is
+read and the output is always written to stdout.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-write=true` - Write formatted policy to the named source file. If false,
+ output will go to stdout. If multiple files are specified, the output will
+ be concatenated directly.
+
+- `-check=false` - Don't format, only check if formatting is necessary. Files
+ that require formatting are printed, and a non-zero exit code is returned if
+ changes are required.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/commands/index.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/commands/index.mdx
new file mode 100644
index 0000000000..7177093cec
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/commands/index.mdx
@@ -0,0 +1,23 @@
+---
+page_title: Commands (CLI)
+sidebar_current: docs-commands
+description: >-
+ Sentinel provides a `sentinel` command-line interface (CLI) for developing and
+ testing policies.
+layout: docs
+---
+
+# Sentinel CLI Commands
+
+The Sentinel command-line interface (CLI) allows for the developing and testing
+of policies outside of a particular Sentinel implementation. Having a standard
+workflow to develop policies is critical for our mission of [policy as
+code](/sentinel/concepts/policy-as-code).
+
+The CLI takes a subcommand to execute. The complete list of subcommands is in
+the navigation to the left.
+
+The Sentinel CLI is a well-behaved command line application. In erroneous cases,
+a non-zero exit status will be returned. It also responds to -h and --help as
+you'd expect. To view a list of the available commands at any time, just run
+`sentinel` with no arguments.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/commands/test.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/commands/test.mdx
new file mode 100644
index 0000000000..ed50aacc22
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/commands/test.mdx
@@ -0,0 +1,53 @@
+---
+page_title: 'Command: test'
+sidebar_current: docs-commands-test
+description: The `sentinel test` command runs policies against the Sentinel test framework.
+layout: docs
+---
+
+# Command: `test`
+
+The `sentinel test` command runs policies against the Sentinel test framework.
+
+To learn more about testing, see the [testing
+policies](/sentinel/writing/testing) page.
+
+## Usage
+
+Usage: `sentinel test [options] [PATH] ...`
+
+This command runs the defined test cases against a set of policies.
+
+The test cases are expected to live at the path `test//*.[hcl|json]` relative
+to the policy being tested. `` should be the name of the policy
+excluding the file extension. The files should be in the [configuration
+file structure](/sentinel/configuration). The `test` key is used for
+assertions. Test cases ignore the root level configuration file and must have
+all required configuration provided in each test case.
+
+The PATH arguments specified may be a list of zero or more files or directories.
+When no arguments are given, it defaults to the current directory. When a
+directory is given, all policies ending with the extension `.sentinel` are
+tested.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-json` - Suppress normal output and output test results as a JSON document.
+ See the [testing JSON output](/sentinel/writing/testing#test-json-output)
+ section for more details.
+
+- `-run=regexp` - Run only tests matching the regular expression pattern.
+ A `/` splits policy name and test case name.
+
+- `-verbose` - Show tracing and log output for tests that succeed in addition
+ to tests that fail. By default, traces and logs are only shown for tests that
+ fail.
+
+- `-maxConcurrency` - Allows users to specify the number of tests to be run concurrently.
+ Defaults to the number of logical CPUs if not provided. To run tests sequentially, use `-maxConcurrency=1`
+
+- `-timeout` - Allows users to specify a timeout after which the test command will stop running.
+Defaults to 5 minutes if not provided. The timeout needs to be specified as a Duration type, eg `100ms, 5s etc`
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/concepts/enforcement-levels.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/concepts/enforcement-levels.mdx
new file mode 100644
index 0000000000..fc4b8062d0
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/concepts/enforcement-levels.mdx
@@ -0,0 +1,43 @@
+---
+page_title: Enforcement Levels
+sidebar_current: docs-concepts-levels
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Enforcement Levels
+
+Enforcement levels are a first class concept in Sentinel allowing pass/fail
+behavior to be associated separately from the policy logic. This enables
+any policy to be a warning, allow overrides, or be absolutely mandatory.
+Because this level is not part of the policy body itself, different uses of
+the same policy can have different enforcement levels.
+
+Sentinel has three enforcement levels:
+
+- **Advisory:** The policy is allowed to fail. However, a warning should be
+ shown to the user or logged. Advisory is the default enforcement level.
+
+- **Soft Mandatory:** The policy must pass unless an override is specified.
+ The semantics of "override" are specific to each Sentinel-enabled application.
+ The purpose of this level is to provide a level of privilege separation
+ for a behavior. Additionally, the override provides non-repudiation since
+ at least the primary actor was explicitly overriding a failed policy.
+
+- **Hard Mandatory:** The policy must pass no matter what. The only way to
+ override a hard mandatory policy is to explicitly remove the policy.
+ It should be used in situations where an override is not possible.
+
+## Configuring Enforcement Levels
+
+Enforcement levels are configured when a policy is deployed to a
+Sentinel-enabled application. The exact mechanism that the level is specified
+is determined by each application. Please reference the documentation for
+your Sentinel-enabled application for more information.
+
+Enforcement levels are not configured and are not known by the policy body
+itself. All policies should be written to describe exactly the behavior
+they're attempting to control. For example, a policy that restricts deploys
+to business hours should be written exactly like so. When that policy is
+configured on an application, the operator may specify that it is advisory,
+soft, or hard mandatory.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/concepts/imports.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/concepts/imports.mdx
new file mode 100644
index 0000000000..1306e1512e
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/concepts/imports.mdx
@@ -0,0 +1,32 @@
+---
+page_title: Imports
+sidebar_current: docs-concepts-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom imports](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only
+local context for making policy decisions.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that the
+import calendar allows access to engineer calendars. This import only exists
+for the purpose of this example and at the time of writing is not a real
+available import.
+
+```json sentinel
+{
+ "policy": "import \"calendar\"\n// Get the calendar for Bob for today\nbob_calendar = calendar.of(\"bob\").today\n\n// Allow this policy to pass if Bob is not on vacation.\nmain = rule { not bob_calendar.has_event(\"vacation\") }",
+ "mocks": {
+ "calendar": "has_event = func(event) {\n\treturn true\n}\nof = func(name) {\n\treturn { \"today\": { \"has_event\": has_event } }\n}"
+ }
+}
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/concepts/index.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/concepts/index.mdx
new file mode 100644
index 0000000000..ce7ac51454
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/concepts/index.mdx
@@ -0,0 +1,15 @@
+---
+page_title: Basic Concepts
+sidebar_current: docs-concepts
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Basic Concepts
+
+This section covers some high level basic concepts that are important
+to understand for day to day Sentinel usage. Every page in
+this section is recommended reading for anyone consuming
+or extending Sentinel.
+
+Please use the navigation to the left to learn more about a topic.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/concepts/language.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/concepts/language.mdx
new file mode 100644
index 0000000000..ce91f3eb8f
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/concepts/language.mdx
@@ -0,0 +1,91 @@
+---
+page_title: Policy Language
+sidebar_current: docs-concepts-language
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Policy Language
+
+Sentinel defines and uses its own [policy language](/sentinel/language).
+
+The language was designed to be approachable by non-programmers, since
+there are many use cases where the individual defining policy may not
+be a developer. However, the language includes constructs that
+are familiar to developers to enable powerful policies.
+
+To learn more about the language, please see the
+[writing policy section](/sentinel/writing)
+or the [language reference](/sentinel/language).
+
+An example of the policy language is shown below:
+
+```sentinel playground
+import "time"
+
+# Validate time is between 8 AM and 4 PM
+valid_time = rule { time.now.hour >= 8 and time.now.hour < 16 }
+
+# Validate day is M - Th
+valid_day = rule {
+ time.now.weekday_name in ["Monday", "Tuesday", "Wednesday", "Thursday"]
+}
+
+main = rule { valid_time and valid_day }
+```
+
+## Why?
+
+To explain why Sentinel defines and uses its own language, the question
+can be more easily split into roughly two types of languages: _configuration_ languages
+and _programming_ languages.
+
+### Configuration Languages
+
+Configuration languages are formats such as JSON, YAML, XML, etc. Many applications
+use configuration languages as their ACL format. Configuration languages
+are good for static, declarative information. ACL systems are a good fit
+for this. ACL systems are focused, providing the limiting options necessary
+to restrict access to a system.
+
+Configuration languages are not good for dynamic or logical rules. They
+typically only contain limited conditions, loops, or functions. Further
+configuration is often declarative, which can introduce complexities to
+logical statements that depend on ordering.
+
+The use cases that Sentinel was built for require the ability to perform
+complex behavior that didn't model well into a configuration format or a
+declarative form.
+
+### Programming Languages
+
+The Sentinel language was designed with the following goals:
+
+- **Non-programmer friendly.** Sentinel was built to be used by non-programmers.
+ For the use cases we discovered, non-programmers needed the ability to
+ enforce certain rules within a system. For example, a person responsible for
+ compliance may need to insert rules into a system.
+
+- **Programmer friendly.** At the same time, the language needed to support
+ programmer-friendly constructs such as conditionals, loops, and functions
+ for complex policies that a programmer may be writing.
+
+- **Embeddable.** Sentinel is embedded in existing software. The language
+ itself needs to be easily embeddedable. Further, Sentinel was designed
+ to be embedded in [HashiCorp](https://www.hashicorp.com) software
+ written in Go, so it needed to be easily embeddable in Go.
+
+- **Safe.** The language is used in highly security-sensitive environments.
+ It must not be able to crash its host system or access system resources
+ without explicit approval.
+
+Prior to building our own language, Sentinel evaluated other languages.
+Some languages worked quite well. As we continued to develop Sentinel, we
+found that the flexibility and control over designing our own language
+outweighed the costs.
+
+Because the language itself doesn't need to be general purpose, building
+a language focused on policy proved to be the best route for Sentinel.
+It allows us to build powerful tracing capabilities for debugging, make
+interesting performance choices, and extend the language as needed in the
+future.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/concepts/policy-as-code.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/concepts/policy-as-code.mdx
new file mode 100644
index 0000000000..6dac593e18
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/concepts/policy-as-code.mdx
@@ -0,0 +1,72 @@
+---
+page_title: Policy as Code
+sidebar_current: docs-concepts-pac
+description: >-
+ Policy as code is the idea of writing code in a high-level language to manage
+ and automate policies. By representing policies as code in text files, proven
+ software development best practices can be adopted such as version control,
+ automated testing, and automated deployment.
+layout: docs
+---
+
+# Policy as Code
+
+Policy as code is the idea of writing code in a high-level language to
+manage and automate policies. By representing policies as code in text files,
+proven software development best practices can be adopted such as version
+control, automated testing, and automated deployment.
+
+Many existing policy or ACL systems do not practice policy as code. Many
+policies are set by clicking in a GUI, which isn't easily repeatable nor
+versionable. They usually don't provide any system for testing policies
+other than testing an action that would violate the policy. This makes it
+difficult for automated testing. And the policy language itself varies by
+product.
+
+Sentinel is built around the idea and provides all the benefits of policy as code.
+
+## Benefits
+
+Policy as code provides a number of benefits:
+
+- **Sandboxing.** Policies provide the guardrails for other automated systems.
+ As the number of automated systems grow, there is also a growing need to
+ protect those automated systems from performing dangerous actions. Manual
+ verification is too slow; policies need to be represented as code to
+ keep up with other automated systems.
+
+- **Codification.** By representing policy logic as code, the information
+ and logic about a policy is directly represented in code and can be augmented
+ with comments rather than relying on oral tradition to learn about the
+ reason for policies.
+
+- **Version Control.** Policies are encouraged to be stored as simple text
+ files managed by a version control system. This lets you gain all the
+ benefits of a modern VCS such as history, diffs, pull requests, and more.
+
+- **Testing.** Policies are just code. Their syntax and behavior can be
+ [easily validated with Sentinel](/sentinel/commands/test). This also encourages
+ automated testing such as through a CI. Paired with a VCS system, this
+ allows a pull request workflow to verify that a policy keeps the system
+ behavior as expected before merging.
+
+- **Automation.** With all policies as code in simple text files, various
+ automation tools can be used. For example, it is trivial to create tools
+ to automatically deploy the policies into a system.
+
+## Sentinel and Policy as Code
+
+Sentinel fully embraces policy as code in a number of ways:
+
+- **Language.** All Sentinel policies are written using the
+ [Sentinel language](/sentinel/concepts/language). This language is
+ made to be inputted directly to text files. As an additional benefit,
+ all Sentinel-enabled applications share the same policy language.
+
+- **Development.** Sentinel provides a [CLI](/sentinel/commands)
+ for development and testing. This local CLI can be used to verify policies
+ before deploying them to a system.
+
+- **Testing.** Sentinel provides a [test framework](/sentinel/commands/test)
+ designed specifically for automation. This allows developers and CI systems
+ to further verify policies.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/configuration/index.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/configuration/index.mdx
new file mode 100644
index 0000000000..c7d4a8e3bd
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/configuration/index.mdx
@@ -0,0 +1,422 @@
+---
+page_title: Sentinel CLI Configuration File Syntax
+sidebar_current: docs-configuration
+description: >-
+ The Sentinel CLI's configuration file can be used to control the
+ behavior of the simulator during apply and test operations.
+layout: docs
+---
+
+# Sentinel CLI Configuration File Syntax
+
+The Sentinel CLI's configuration file can be used to control the behavior
+of the simulator during [`apply`](/sentinel/commands/apply) and
+[`test`](/sentinel/commands/test) operations.
+
+## Usage
+
+The configuration file is used in different ways depending on what operation you
+are trying to execute.
+
+### Apply
+
+When using `sentinel apply`, configuration files that have the `.hcl`
+extension are loaded into a single configuration. This allows for configuration
+to be split across multiple files, which is useful for large policy sets. To
+supply a single configuration file, the `-config=FILE` flag can be used, where
+`FILE` is the path to the configuration file. This argument also allows for a
+`.json` configuration file to be supplied. The default is `sentinel.[hcl|json]`.
+
+See the [apply command](/sentinel/commands/apply) reference for more details.
+
+### Test
+
+When using `sentinel test`, each file matching `test//*.[hcl|json]` is a
+configuration file representing a single test case, where `` is the name
+of the policy being tested. Configure assertions for each [test
+case](#test-cases) using the test section.
+
+See the [test command](/sentinel/commands/test) reference for more details.
+
+#### Remote sources
+
+When declaring a `policy` or `module` block within the configuration, a
+`source` attribute must be supplied. This `source` should declare the location
+of the resource, which can be either a local file or a URL pointing to a remote
+file. Examples of local `source` attributes are detailed both in the
+[Policies](#policies) and [Modules](#modules) sections of this page. Below
+are a few examples of remote `source` attributes.
+
+Example:
+
+```hcl
+policy "foo" {
+ source = "git::https://github.com/hashicorp/sentinel-example.git//main.sentinel"
+ enforcement_level = "hard-mandatory"
+}
+```
+
+The above example will fetch the policy from the provided URL and place it into
+the cache ready for use. Be sure to read the
+[Remote Sources](/sentinel/configuration/remote-sources) page for an
+in-depth overview.
+
+## Configuration File Reference
+
+The format of the configuration file is either JSON or HCL. The available
+blocks are:
+
+- [`mock`](#mock-imports) - A mock import specification.
+- [`policy`](#policies) - Configuration for a [policy](/sentinel/writing).
+- [`import`](#imports) - Configuration for a [module](/sentinel/extending/modules), [standard import](/sentinel/imports), [plugin](/sentinel/extending/plugins) or
+ [static import](/sentinel/extending/static-imports).
+- [`global`](#globals) - Data that is inserted into the global scope.
+- [`param`](#parameters) - Values for [parameters](/sentinel/language/parameters) defined in the policy.
+- [`test`](#test-cases) - Test cases for the [test command](/sentinel/commands/test).
+- [`sentinel`](#sentinel) - Configuration to manage the Sentinel runtime
+
+### Mock Imports
+
+Mock imports allow running a policy with an
+[import](/sentinel/concepts/imports) that you may not have access to or is
+too difficult to run. For example, it can be easier to mock data for [Terraform
+Enterprise](https://www.terraform.io/docs/enterprise/sentinel/index.html)
+locally instead of having to run a workspace through a plan/policy check cycle.
+
+Mock imports are specified using the `mock` block, labelled by the import
+name that you want to mock. For example, if you wanted to mock the `time`
+import, you could create an entry with the `time` label pointing to the data
+you want to mock.
+
+The data can take one of two forms:
+
+#### Mocking static data
+
+Static data can be mocked directly via HCl using the `data` attribute on the
+`mock` block.
+
+Example:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ hour = 9
+ minute = 42
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"time\"\n\nmain = rule { time.now.hour is 9 }",
+ "mocks": {
+ "time": "now = { \"hour\": 9, \"minute\": 42 }"
+ }
+}
+```
+
+#### Mocking with Sentinel code
+
+There are some Sentinel types that raw data cannot mock, such as functions,
+and maps that don't have string keys. To mock this data, you can use
+a Sentinel file itself. In this case, you can set the `module` attribute to
+a file with the Sentinel code in it, relative to the current working directory in
+the event of `apply`, or relative to the configuration file location in the
+event of `test`.
+
+Note that `main` is not required in a mock data file.
+
+Example:
+
+```hcl
+mock "foo" {
+ module {
+ source = "mock-foo.sentinel"
+ }
+}
+```
+
+`mock-foo.sentinel` would contain:
+
+```sentinel
+bar = func() {
+ return "baz"
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"foo\"\n\nmain = rule { foo.bar() is \"baz\" }",
+ "mocks": {
+ "foo": "bar = func() { return \"baz\" }"
+ }
+}
+```
+
+### Policies
+
+The `policy` block allows for the the configuration of policies to assist
+with integrating into other commands.
+
+Each `policy` block has a label to identify the policy, with the block
+providing configuration of the policy. The available configuration options for
+policy are `source`, `enforcement_level` and an optional `params` attribute. The
+`source` key provides the location of the policy, while `enforcement_level` is
+currently used by integrations such as [HCP Terraform](/terraform/cloud-docs/policy-enforcement/manage-policy-sets#enforcement-levels).
+For more information on the `source` value, see [Policy and Module Sources](#policy-and-module-sources).
+
+The optional `params` attribute is used to provide values to [parameters](/sentinel/language/parameters)
+defined within the policy file.
+
+```hcl
+policy "foo" {
+ source = "foo.sentinel"
+ enforcement_level = "hard-mandatory"
+ params = {
+ "name" = "Sample"
+ }
+}
+```
+
+If `enforcement_level` is not supplied, it will fallback to the `advisory` level.
+
+### Imports
+
+Import configuration allows you to configure [modules](/sentinel/extending/modules),
+[standard imports](/sentinel/imports) and [import plugins](/sentinel/extending/plugins).
+
+The `import` is a multi-label block, with the first label determining the kind of import
+being configured. Currently the supported values for this label are:
+
+* [`"module"`](#import-modules) for configuring import modules
+* [`"plugin"`](#import-plugins) for configuring standard imports and import plugins
+* [`"static"`](#static-imports) for configuring static data files as imports
+
+#### Import Modules
+
+The `import "module"` block allows you to map a Sentinel import to a
+[module](/sentinel/extending/modules). The Sentinel CLI will parse the module
+source and make it available for evaluation with the policy.
+
+Each block has a label aligning to the name of the import, with
+the block set to the configuration object for the module. Currently, the only
+value within the configuration object is `source`, which points to the
+location of the module. For more information on the `source` value, see
+[Policy and Module Sources](#policy-and-module-sources).
+
+```hcl
+import "module" "foo" {
+ source = "modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "modules/bar.sentinel"
+}
+```
+
+Note when using [`sentinel test`](/sentinel/commands/test), module paths must be
+specified relative to the location of the configuration file.
+
+```hcl
+import "module" "foo" {
+ source = "../../modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "../../modules/bar/sentinel"
+}
+```
+
+See [Writing and Using a
+Module](/sentinel/extending/modules#writing-and-using-a-module) for more details
+on using a module with this configuration.
+
+### Import Plugins
+
+Import `"plugin"` configuration allows you to configure both [standard imports](/sentinel/imports)
+as well as [import plugins](/sentinel/extending/plugins) that can be used within
+a policy. If defining a custom import plugin, the Sentinel CLI will launch the
+plugin, connect to it, configure it, and execute it as needed by the policy.
+
+The available configuration attributes for an `import "plugin"` are:
+
+- `source` (string, required) - Path to the import plugin executable.
+- `args` (list of string, optional) - A list of arguments to pass to the
+ executable when starting it.
+- `env` (map of string to string, optional) - A set of environmental variables
+ to set when launching the plugin.
+- `config` (map, optional) - Configuration for the plugin itself. This is
+ specific to each individual plugin. Please reference the documentation for
+ the plugin for more information.
+
+Standard import example:
+
+```hcl
+import "plugin" "time" {
+ config = { "timezone": "Australia/Brisbane" }
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"time\"\n\nmain = time.now.zone_string is \"+10:00\"",
+ "mocks": {
+ "time": "now = { \"zone_string\": \"+10:00\" }"
+ }
+}
+```
+
+Custom plugin example:
+
+```hcl
+# flipper receives a boolean and returns the opposite
+import "plugin" "flipper" {
+ source = "/path/to/flipper"
+}
+```
+
+```json sentinel
+{
+ "policy": "import \"flipper\"\n\nmain = flipper.flip(false)",
+ "mocks": {
+ "flipper": "flip = func(input) { return not input }"
+ }
+}
+```
+
+#### Static Imports
+
+Static import configuration allows you to supply a data file and make
+it available to a policy via an import statement.
+
+The available configuration attributes for an `import "static"` are:
+
+- `source` (string, required) - Path to the static data file.
+- `format` (string, required) - The format of the static data. Currently, only
+ the "json" value is supported.
+
+Static import example:
+
+```hcl
+import "static" "people" {
+ source = "./data/people.json"
+ format = "json"
+}
+```
+
+```json sentinel
+{
+ "policy": "import \"people\"\n\nmain = length(people.names) > 0",
+ "mocks": {
+ "people": "names = [\"John Smith\", \"Jane Smith\"]"
+ }
+}
+```
+
+### Globals
+
+Global data is injected directly into the global scope of the policy.
+This can be used to simulate global data that may be injected by a
+Sentinel-enabled application.
+
+Global data is specified by the `global` key. The value is a map of
+variables to inject directly into the running policy.
+
+Example:
+
+```hcl
+global "time" {
+ value = {
+ now = {
+ day = 31
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass. Notice
+that we don't have to do any `import`. The values of `global` are
+injected directly into the global scope.
+
+```json sentinel
+{
+ "policy": "main = time.now.day == 31",
+ "globals": {
+ "time": {
+ "now": {
+ "day": 31
+ }
+ }
+ }
+}
+```
+
+### Parameters
+
+The `param` section allows you to supply values for the
+[parameters](/sentinel/language/parameters) found in a policy. Values
+entered here will satisfy required parameters in a policy, in addition to
+override any defaults. Note that any of the manual parameter value methods
+supported by `sentinel apply` will override the values set here.
+
+```hcl
+param "foo" {
+ value = "bar"
+}
+```
+
+### Test Cases
+
+Test cases specify the test cases for the [test command](/sentinel/commands/test).
+
+Tests are specified by the `test` key. The value of this is a map of
+string to boolean. The key is the name of a rule and the value is the
+expected value of that rule. If a rule is not specified, than any value is
+allowed.
+
+Example:
+
+```hcl
+test {
+ rules = {
+ main = true
+ days = []
+ }
+}
+```
+
+For the policy:
+
+```json sentinel
+{
+ "policy": "valid_days = rule {\n\tfilter days as d {\n\td is \"monday\"\n\t}\n}\n\nmain = rule { valid_days is empty }",
+ "globals": {
+ "days": []
+ }
+}
+```
+
+For more information, read about the [test command](/sentinel/commands/test).
+
+### Sentinel
+
+The `sentinel` block provides configuration specific to the Sentinel runtime.
+
+An example of how the `sentinel` block can be used to enable the [terraform](/sentinel/features/terraform)
+feature is as follows:
+
+```hcl
+sentinel {
+ features = {
+ terraform = true
+ }
+}
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/configuration/overrides.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/configuration/overrides.mdx
new file mode 100644
index 0000000000..ca1a6842b1
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/configuration/overrides.mdx
@@ -0,0 +1,80 @@
+---
+page_title: Override Files
+sidebar_current: docs-configuration-overrides
+description: >-
+ The Sentinel CLI's configuration can be overriden using override files.
+ Override files merge additional settings into existing configuration.
+layout: docs
+---
+
+# Override Files
+
+As outlined in the configuration [overview](/sentinel/configuration#Apply),
+the normal behavior of Sentinel is to load all `.hcl` files into a single
+configuration object.
+
+In addition to appending multiple configuration files, Sentinel allows for the
+rare case of overriding configuration through override files. Override files
+are any files that match either `_override.{hcl,json}` or `override.{hcl,json}`.
+
+Sentinel will parse override files after it has loaded the primary
+configuration, and then process each override file in turn (in lexicographical
+order). All top level blocks other than [test](/sentinel/configuration#test-cases)
+require the block to already be defined in the primary configuration. It will
+then attempt to merge the override block into the existing configuration.
+
+## Example
+
+If you had a Sentinel configuration `sentinel.hcl` that contained the following:
+
+```hcl
+policy "main" {
+ source = "./main.sentinel"
+ enforcement_level = "advisory"
+}
+```
+
+And you created a file `override.hcl` that contained the following:
+
+```hcl
+policy "main" {
+ enforcement_level = "soft-mandatory"
+}
+```
+
+Sentinel will merge the contents of the override into the former, providing the
+following configuration:
+
+```hcl
+policy "main" {
+ source = "./main.sentinel"
+ enforcement_level = "soft-mandatory"
+}
+```
+
+## Merging Behavior
+
+The general rule, which applies in most cases, is:
+
+- A top-level block in an override file merges with a block in a normal
+ configuration file that has the same block header. The block header is the
+ block type and any quoted labels that follow it.
+- Within a top-level block, an attribute argument within an override block
+ replaces any argument of the same name in the original block.
+- Within a top-level block, any nested blocks within an override block replace
+ all blocks of the same type in the original block. Any block types that do not
+ appear in the override block remain from the original block.
+- The contents of nested configuration blocks are not merged.
+- The resulting merged block must still comply with any validation rules that
+ apply to the given block type.
+
+If more than one override file defines the same top-level block, the overriding
+effect is compounded, with later blocks taking precedence over earlier blocks.
+Overrides are processed in order first by filename (in lexicographical order)
+and then by position in each file.
+
+## Required Attributes
+
+It is important to note that all attributes inside an override file are
+considered to be optional, meaning that an override file can itself fail
+validation rules for block types.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/configuration/remote-sources.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/configuration/remote-sources.mdx
new file mode 100644
index 0000000000..b64e4d3abc
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/configuration/remote-sources.mdx
@@ -0,0 +1,165 @@
+---
+page_title: Remote Sources
+sidebar_current: docs-configuration-remote-sources
+description: >-
+ There are a number of options for formatting the URL provided to the
+ source attribute on policy and module blocks.
+layout: docs
+---
+
+# Remote Sources
+
+There are a number of options for formatting the URL provided to the
+`source` attribute on `policy` and `import "module"` blocks. This flexibility
+should solve most use cases and allow for reusable policies and modules.
+
+### Supported Protocols
+
+-> **NOTE:** All protocols are supported on the CLI, however each production
+Sentinel integration may not. Be sure to read the appropriate integration
+documentation to check the supported protocols.
+
+- Local filesystem
+- Git
+- Mercurial
+- HTTP
+- Amazon S3
+- Google GCP
+
+### Forced Protocols
+
+Due to the ambiguous nature of URLs, it is possible to force a particular
+protocol to be used during the fetch. To achieve this, simply prefix the url
+with the appropriate protocol as listed below:
+
+- `file` - Local filesystem
+- `git` - Git
+- `hg` - Mercurial
+- `http` or `https` - HTTP
+- `s3` - Amazon S3
+- `gcp` - Google GCP
+
+Example:
+
+```
+git::http://github.com/hashicorp/sentinel.git
+```
+
+The above would download the provided HTTP URL using the Git protocol.
+
+### Protocol Options
+
+In addition to some global options, there are options available to specific
+protocols to perform features such as authentication.
+
+#### Subdirectories / File Pathing
+
+When supplying URLs that point to a directory (eg. `git`), a subdirectory and
+file can be provided by suffixing the URL with `//` and the path. For example,
+if we have a git repository that has a policy, `main.sentinel` in the root, we
+could directly fetch the file by using the following:
+
+```
+git::https://github.com/hashicorp/sentinel-docs-example.git//main.sentinel
+```
+
+Or, if the file was nested in the `policies` folder:
+
+```
+git::https://github.com/hashicorp/sentinel-docs-example.git//policies/main.sentinel
+```
+
+#### Checksumming
+
+For downloads of any protocol, you can automatically verify a checksum. To
+checksum a file, append a `checksum` query parameter to the URL. The parameter
+value can be in the format of type:value or just value, where type is "md5",
+"sha1", "sha256", "sha512" or "file" . The "value" should be the actual
+checksum value or download URL for "file". When type part is omitted, type will
+be guessed based on the length of the checksum string.
+
+```
+./foo.sentinel?checksum=md5:b7d96c89d09d9e204f5fedc4d5d55b21
+
+./foo.sentinel?checksum=b7d96c89d09d9e204f5fedc4d5d55b21
+
+./foo.sentinel?checksum=file:./foo.txt.sha256sum
+```
+
+#### Unarchiving
+
+An `archive` query parameter can be supplied to explicitly state what format
+to attempt to extract, if required. The supported formats are:
+
+- `tar.gz` and `tgz`
+- `tar.bz2` and `tbz2`
+- `tar.xz` and `txz`
+- `zip`
+- `gz`
+- `bz2`
+- `xz`
+
+If a URL has an extension that matches one of the supported formats, it will
+use that format to unarchive.
+
+```
+./file.zip
+```
+
+The above would use the `zip` format to unarchive.
+
+```
+./path/to/file?archive=zip
+```
+
+Would force the `zip` format. If for some reason you required archiving to be
+disabled, this can also be achieved by using the `false` value.
+
+```
+./path/to/file?archive=false
+```
+
+#### Git (`git`)
+
+- `ref` - The Git ref to checkout. This is a ref, so it can point to a commit
+ SHA, a branch name, etc. If it is a named ref such as a branch name, it will
+ be updated to the latest on each get.
+- `sshkey` - An SSH private key to use during clones. The provided key must be
+ a base64-encoded string. For example, to generate a suitable sshkey from a
+ private key file on disk, you would run base64 -w0 \.
+ **Note:** Git 2.3+ is required to use this feature.
+- `depth` - The Git clone depth. The provided number specifies the last `n`
+ revisions to clone from the repository.
+
+The git getter accepts both URL-style SSH addresses like
+`git::ssh://git@example.com/foo/bar`, and "scp-style" addresses like
+`git::git@example.com/foo/bar`. In the latter case, omitting the `git::` forced
+prefix is allowed if the username prefix is exactly git@.
+
+The "scp-style" addresses cannot be used in conjunction with the `ssh://`
+scheme prefix, because in that case the colon is used to mark an optional port
+number to connect on, rather than to delimit the path from the host.
+
+#### Mercurial (`hg`)
+
+- `rev` - The Mercurial revision to checkout.
+
+#### HTTP (`http` or `https`)
+
+Basic Authentication
+
+To use HTTP basic authentication, simply prepend `username:password@` to the
+hostname in the URL such as `https://Aladdin:OpenSesame@www.example.com/index.html`.
+All special characters, including the username and password, must be URL
+encoded.
+
+#### S3 (`s3`)
+
+The following query parameters are available and will take priority when
+authenticating against an S3 bucket.
+
+- `aws_access_key_id` - AWS access key.
+- `aws_access_key_secret` - AWS access key secret.
+- `aws_access_token` - AWS access token if this is being used.
+- `aws_profile` - Use this profile from local ~/.aws/ config. Takes priority
+ over the other three.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/consul.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/consul.mdx
new file mode 100644
index 0000000000..7a3aaaa697
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/consul.mdx
@@ -0,0 +1,47 @@
+---
+page_title: Consul and Sentinel
+sidebar_current: docs-app-consul
+description: >-
+ Consul Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies are applied during writes to
+ the KV Store.
+layout: docs
+---
+
+# Consul
+
+[Consul Enterprise](https://www.hashicorp.com/products/consul/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies are applied during writes to the KV Store.
+
+Sentinel policies have access to the key/value being written. They can be used to
+allow or deny the modification. The information that Sentinel policies have access
+to will expand over time.
+
+The Consul integration with Sentinel is documented in depth in the
+[Consul Enterprise documentation](https://www.consul.io/docs/guides/sentinel.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Input validation depending on the name of the key.**
+
+```sentinel
+main = rule { valid_key() }
+
+required = [
+ ["port", "\\d+"], # ports must be integers
+ ["name", "\\w+"], # name must be a word
+]
+
+valid_key = func() {
+ for required as v {
+ if key is v[0] {
+ return value matches v[1]
+ }
+ }
+
+ return false
+}
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/extending/index.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/extending/index.mdx
new file mode 100644
index 0000000000..a39cb0384d
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/extending/index.mdx
@@ -0,0 +1,52 @@
+---
+page_title: Extending Sentinel
+sidebar_current: docs-extending
+description: Learn how to create your own Sentinel imports to extend Sentinel's functionality.
+layout: docs
+---
+
+# Extending Sentinel
+
+-> **NOTE:** Sentinel's extensibility is currently undergoing large
+improvements - watch this space for future updates!
+
+Sentinel can be extended by writing additional
+[imports](/sentinel/language/imports). These imports can bring new functionality
+to Sentinel by allowing access to new data or by the addition of new functions.
+This section is designed to show you how to accomplish this extensibility and
+describe how it works.
+
+Currently, the only usable way to add additional imports is via
+[modules](#modules). While [plugins](#plugins) currently work under the Sentinel
+CLI, no Sentinel integration currently supports their use.
+
+-> You may also be interested in advanced details on [import
+internals](/sentinel/extending/internals).
+
+## Modules
+
+Modules allow you to re-use Sentinel code as an import. Modules are loaded
+external of policies and mapped to imports. This is usually configured via a
+file such as the [CLI configuration file](/sentinel/configuration).
+
+See the [modules](/sentinel/extending/modules) section for more details.
+
+## Plugins
+
+-> **NOTE:** Plugins are currently only supported on the CLI and not in any
+production Sentinel integration, with the exception of existing configuration
+in [Nomad](https://www.nomadproject.io/docs/configuration/sentinel).
+
+Imports can also be implemented as plugins when Sentinel code itself is too
+restrictive to represent the import, or if you need access to features not
+normally available to the Sentinel runtime.
+
+See the [plugins](/sentinel/extending/plugins) section for more details.
+
+## Static Imports
+
+Static imports provide support for loading data files into the Sentinel runtime,
+making them available as an import.
+
+See the [static imports](/sentinel/extending/static-imports) section for more
+details.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/extending/internals.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/extending/internals.mdx
new file mode 100644
index 0000000000..7133e6df1b
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/extending/internals.mdx
@@ -0,0 +1,252 @@
+---
+page_title: >-
+ Extending Sentinel: Internals
+sidebar_current: docs-extending-internals
+description: This Section covers some details about Sentinel's import internals.
+layout: docs
+---
+
+# Extending Sentinel: Internals
+
+-> **Advanced Topic!** This page covers low-level technical details of Sentinel.
+You don't need to understand these details to effectively use Sentinel. The
+details are documented here for those who wish to learn about them.
+
+This section covers some of the internal details of Sentinel's import system.
+The goal of this section is to help remove notion of "magic" from Sentinel, to
+allow you to trust and understand what Sentinel is doing with imports, and also
+to help practitioners and developers alike understand the differences between
+the ways that imports can be loaded into Sentinel.
+
+## Language and Runtime
+
+Understanding the import system in Sentinel generally requires understanding of
+two key concepts within Sentinel: the _language_, and the _runtime
+implementation_ of the language.
+
+The Sentinel language and the rules governing it are detailed in the [Sentinel
+Language Specification](/sentinel/language/spec). A runtime implementation must
+conform to the rules laid out in the specification at a minimum. However, to
+meet the design goals of a particular implementation, the runtime may implement
+features on top of the language. The subtle implementation details within the
+runtime may also vary while still being compliant with the specification.
+
+The core runtime included within the [Sentinel CLI](/sentinel/commands) is
+sometimes referred to as the _reference implementation_ of the Sentinel
+language. This runtime is also the main runtime embedded within each
+Sentinel-enabled HashiCorp product such as HCP Terraform, Terraform Enterprise, Vault
+Enterprise, Nomad Enterprise, and Consul Enterprise. The runtime includes an API
+to allow these integrations implement Sentinel in as robust or as restrictive of
+a way as possible, so depending on the implementation, your experience may vary.
+
+## Sentinel's Import Model
+
+The concept of Imports within Sentinel is a _language_ feature. You can see the
+exact rules governing imports within the
+[Imports](/sentinel/language/spec#imports) section of the specification.
+
+Within the runtime, there are currently two major classifications of imports:
+
+- **Modules:** The mapping of Sentinel code to an import, essentially mapping a
+ scope of values to a particular package.
+- **Binary Imports:** A grouping of embedded and external plugins written using
+ the Sentinel SDK. These are comprised of internal or embedded imports (usually
+ the [standard imports](/sentinel/imports) and imports included by an
+ integration) and import plugins.
+
+Below is a diagram explaining in brief the relationship between the langauge
+and runtime features.
+
+
+
+This kind of topology ultimately minimizes the visibility of implementation
+internals to the actual policy language. This allows us to keep the Sentinel
+language itself simple and keep concerns such as module or plugin management,
+validation, and configuration out of the language. These kinds of things would
+impact the readability of a policy, possibly present security challenges, and
+would ultimately be of no use to more minimal embedded applications.
+
+### Implementation Differences Between Import Types
+
+As one would imagine, both modules and binary imports are loaded in much
+different ways, and the methods that they expose data to Sentinel differ as
+well. The effect is generally the same however across both, and we take care to
+ensure that both modules and binary imports behave as close as possible to each
+other, however there are some subtle differences:
+
+- Modules currently support the exporting of rules, but do not support function
+ calls with object receiver data (as seen in some standard imports such as
+ [`decimal`](/sentinel/imports/decimal), [`http`](/sentinel/imports/http), and
+ [`time`](/sentinel/imports/time)). This will be coming in a later release.
+- Binary imports cannot export rules. However, they do support object receiver
+ data (see [`framework.New`](/sentinel/extending/plugins#framework-new) in
+ [Extending Sentinel: Plugins](/sentinel/extending/plugins)).
+
+## Modules
+
+_Modules_ are essentially Sentinel code that is intended to be re-used in multiple
+policies as an import.
+
+The mechanism of module loading is fairly straightforward: during initialization
+of the Sentinel runtime, modules are parsed along with policies. The parsed
+_abstract syntax tree_ (AST) for each module is loaded into the runtime under
+the specified _import path_. These are then passed to the lower-level
+interpreter during the evaluation of each policy.
+
+As with policy code, during evaluation, a module's AST is walked to execute the
+code within that specific module. The module data is then stored within an
+specific scope mapped to the import name. The module data can then be accessed
+through the import via selector expressions and function calls (e.g.,
+`foo.some_map` or `foo.some_func()` for a module loaded via `import "foo"`).
+
+The AST for a module is only walked if a policy imports it, and it is _only
+walked once_. That means if a policy and a module import the same module, or a
+policy imports the same module twice (using [import
+aliases](/sentinel/language/imports#aliases)), those instances will share state.
+If you call a function that alters a singleton variable within the module, that
+singleton will be modified for all instances of the import.
+
+-> **Fun fact!** When you [mock an import with Sentinel
+code](/sentinel/configuration#mocking-with-sentinel-code), you are actually
+using a module. The module is loaded with a flag that allows it to override an
+existing import, which would normally give a runtime error.
+
+### Map and List Cloning for Module Calls
+
+It's also worthwhile noting that map and list values are cloned for modules.
+
+Consider the case where you have a module with a map singleton.
+
+```sentinel
+// modules/foo.sentinel
+a_map = {"a": "b"}
+```
+
+Normally, assignment to the singleton, even when using an index expression, is
+blocked, so `foo.a_map["c"] = "d"` would not pass semantic checking.
+
+However, even when you try to do assignment via an intermediary, you will still
+be only modifying the copy, and the original will not be affected:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+a_map_copy = foo.a_map
+a_map_copy["c"] = "d" // Only modifies copy, does not modify module singleton
+print(foo.a_map) // Still only prints {"a": "b"}
+```
+
+This is to ensure that modules are consistent with binary imports in how return
+data is handled, and the expectation that most non-object data within an import
+is read-only, with the exception of modification of singleton data via
+functions. As return of complex object and collection data in a binary import is
+always via copy, it's not possible to modify any value elements within the
+import in a similar fashion.
+
+There are also some other implications of this cloning that are of note:
+
+- Rules are not cloned, either within a list or map, or by themselves. This is
+ to ensure that [memoization](/sentinel/language/rules#lazy-and-memoized)
+ functions correctly. As only a module can export rules at this time, there is no
+ parity for this in binary imports.
+- Functions are _not_ cloned. This mainly has implications for scope - for
+ example, if you assign a function to another value, or assign an object to a
+ value that has a method that utilizes a global module variable, those calls will
+ reference and/or modify those values.
+
+Functions, while represented differently in binary imports, generally follow the
+same logic here - as they would be invoked in the same package within the binary
+import, any singleton values they accessed there would be affected in a similar
+fashion. As rules are pseudo-functions, this generally makes sense here too.
+
+## Binary Imports
+
+_Binary imports_ is a grouping referring to all imports that are designed with
+the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). These can be
+either internally embedded, or executed via a plugin.
+
+All imports found in the [standard library](/sentinel/imports) are binary
+imports, embedded internally.
+
+The main difference between internally embedded imports and external plugins is
+whether or not the runtime needs to execute a plugin binary as part of
+[lifecycle management](#lifecycle), and whether or not it needs to
+[communicate](#communication) over RPC. Internal binary imports do not require
+these steps, so their execution model is somewhat simpler; however, technically,
+they still are the same as external plugins in terms of design model and value
+conversion. Most standard imports are even tested using the [SDK test
+suite](/sentinel/extending/plugins#testing), which builds the import as an
+external plugin.
+
+The remainder of this section discusses topics mostly relevant to external
+plugins. A large amount of technical detail regarding binary import development
+(also applicable to embedded binary imports) can be seen on the
+[Plugins](/sentinel/extending/plugins) page.
+
+-> **NOTE:** At this time, plugins can only be written for the Sentinel CLI, and
+cannot be used with any of Sentinel's integrations.
+
+### Plugin Basics
+
+Sentinel plugins are built on top of the HashiCorp [go-plugin
+system](https://github.com/hashicorp/go-plugin). This is the same plugin system
+powering all pluggable HashiCorp tools such as
+[Terraform](https://www.terraform.io), [Vault](https://www.vaultproject.io), and
+more. go-plugin is a system that has been used in production for millions of
+users for over 5 years.
+
+Plugins are executable binaries. Sentinel is responsible for launching and
+managing the lifecycle of plugins. Once a plugin is running, Sentinel
+communicates with the plugin via [gRPC](https://grpc.io/). The [protocol is open
+source](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/import.proto)
+to allow anyone to write a Sentinel plugin.
+
+### Lifecycle
+
+Sentinel launches plugins and is responsible for plugin lifecycle.
+
+The runtime optimizes for latency by launching the plugin as soon as it is
+configured, rather than when a policy requires it. This ensures that the plugin
+is ready to be used immediately. A plugin is only closed when Sentinel is
+reconfigured to no longer allow that plugin or if the Sentinel-enabled
+application is closing.
+
+When a plugin is removed from the configuration, Sentinel may wait to shut down
+the plugin until all currently executing policies that are using that plugin
+complete. New policy executions will not be allowed to use the old plugins.
+
+Plugins are automatically restarted if they shut down unexpectedly. Policies
+that were executing while this happens may fail. The Sentinel system is
+improving to more gracefully understand locations where it is safe to retry an
+execution.
+
+### Communication
+
+An [initial
+handshake](https://github.com/hashicorp/go-plugin/blob/master/docs/internals.md)
+is done via stdout to determine the main communication location. Following the
+handshake, communication will either occur on a local Unix domain socket or via
+a local-only TCP socket. This communication is done mostly over the low-level
+[`Get`](https://github.com/hashicorp/sentinel-sdk/blob/2b4db07dd12b55142f0c016f301af80aa4422520/proto/import.proto#L12)
+RPC call.
+
+#### Value Conversion
+
+As can generally be seen in [Developing an Import
+Plugin](/sentinel/extending/plugins#developing-an-import-plugin), the Sentinel
+type system is not exposed to binary imports. While explicit values for null and
+undefined exist, values are mostly converted over the wire from their Go values
+to their Sentinel equivalents.
+
+This has a few implications:
+
+- As discussed in [Map and List Cloning for Module
+ Calls](#map-and-list-cloning-for-module-calls), Map and list data returned from
+ binary import value lookups or function calls is always cloned. These can be
+ freely modified without affecting anything within the binary import.
+- It's currently impossible to represent certain Sentinel types such as
+ [rules](/sentinel/language/rules) within a binary import. This is not a major
+ issue at this point in time as practitioners generally do not look to binary
+ imports for rules; we may examine this as a later time if this situation
+ changes.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/extending/modules.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/extending/modules.mdx
new file mode 100644
index 0000000000..9846d263e5
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/extending/modules.mdx
@@ -0,0 +1,135 @@
+---
+page_title: >-
+ Extending Sentinel: Modules
+sidebar_current: docs-extending-modules
+description: >-
+ Modules allow you to re-use Sentinel code as an import.
+layout: docs
+---
+
+# Extending Sentinel: Modules
+
+Modules allow you to re-use Sentinel code as an import. This allows for the
+export of any general construct that Sentinel supports, including values,
+functions, and even rules. As such, it's a great way to package code that is
+useful across multiple policies, reducing boilerplate and making final policy
+code simpler.
+
+You may want to use modules for:
+
+- **Writing a simple re-usable helper library.** If you mostly know Sentinel or
+ have helpers that you have written in Sentinel, moving these helpers to a module
+ will offer a low-friction way of facilitating their re-use.
+- **Writing re-usable rules.** If you have a set of
+ [rules](/sentinel/language/rules) you know you want to use across multiple
+ policies, bundling them into a module is a great way of abstracting the logic
+ away.
+- **Abstracting existing Sentinel imports.** Using a module is a great way to
+ extend existing Sentinel imports by abstracting the common functionality that
+ you use within an import to your own workflow.
+
+This page describes how you can use modules within the context of the Sentinel
+CLI. For instructions for a particular integration, see the documentation for
+that product.
+
+-> **Not all Sentinel-enabled applications support modules.** Please refer
+to the documentation of the specific Sentinel-enabled application. Some
+applications disable modules for security reasons.
+
+## Configuring a Module
+
+A module is configured within the Sentinel CLI by adding an entry for it in
+within the `imports` section of the [CLI configuration
+file](/sentinel/configuration). The key for each entry specifies the path that
+the module is imported as.
+
+```hcl
+import "module" "foo" {
+ source = "modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "modules/bar.sentinel"
+}
+```
+
+In this example, we have two modules:
+
+- Import path `foo`, being loaded from the code within `modules/foo.sentinel`.
+- Import path `bar`, being loaded from the code within `modules/bar.sentinel`.
+
+See the [Import Modules](/sentinel/configuration#import-modules) section within
+the CLI configuration file syntax page for more details.
+
+### Remote Modules
+
+In addition to loading from a local source, modules can be loaded from a remote
+location. This can increase reusability and allow for sharing modules across
+multiple configurations.
+
+### Testing Using Modules
+
+As with [mocks](/sentinel/configuration#mocking-with-sentinel-code), modules
+are loaded relative to the configuration file location when using `sentinel test`. As such, you need to account for this in your test configuration files:
+
+```hcl
+import "module" "foo" {
+ source = "../../modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "../../modules/bar.sentinel"
+}
+```
+
+This could be a test file for a policy (example: `policy.sentinel`), residing
+under the correct test file directory for this policy (example:
+`test/policy/pass.json`).
+
+## Writing and Using a Module
+
+Writing a module is nearly the same as writing a Sentinel policy, except for the
+following two exceptions:
+
+- Modules do not require [`main`](/sentinel/writing/basic#the-simplest-policy)
+ to be present. It can be, but it will not carry any extra significance as it
+ does within a policy.
+- [`param`](/sentinel/language/parameters) declarations cannot be present in a module. If they are encountered,
+ a runtime error is given.
+
+Once you have a complete module ready for use and configured, using the module
+is as simple as importing it.
+
+Building on the above [configuration example](#configuring-a-module), we can
+export a simple test function in the module `foo` by adding this code to
+`modules/foo.sentinel`:
+
+```sentinel
+// modules/foo.sentinel
+hello = func() {
+ print("hello world!")
+ return undefined
+}
+```
+
+We can then import it within our policy and use it:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+// prints "hello world" in the trace
+foo.hello()
+
+main = true
+```
+
+Note that modules are considered [imports](/sentinel/language/spec#imports) by
+the Sentinel runtime and as such conform to the specification. This means that
+you cannot directly assign values to anything behind a module scope. You can,
+however, use functions to change state.
+
+-> **NOTE:** At this time, modules do not support object receiver data in the
+way that some imports do, like [`time`](/sentinel/imports/time),
+[`decimal`](/sentinel/imports/decimal), and [`http`](/sentinel/imports/http).
+Support for this will be coming in later versions of Sentinel.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/extending/plugins.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/extending/plugins.mdx
new file mode 100644
index 0000000000..e80de5d4b1
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/extending/plugins.mdx
@@ -0,0 +1,521 @@
+---
+page_title: >-
+ Extending Sentinel: Plugins
+sidebar_current: docs-extending-plugins
+description: >-
+ Plugins allow you to write imports as standalone executables, allowing you to
+ extend Sentinel in ways not normally possible with policy code alone.
+layout: docs
+---
+
+# Extending Sentinel: Plugins
+
+-> **NOTE:** Plugins are currently only supported on the CLI and not in any
+production Sentinel integration, with the exception of existing configuration
+in [Nomad](https://www.nomadproject.io/docs/configuration/sentinel).
+
+Plugins allow you to write imports as standalone executables, allowing you to
+extend Sentinel in ways not normally possible with policy code alone.
+
+You might want to write or use a plugin when you need to:
+
+- **Use a provider or API integration for Sentinel.** In this case, you will
+ probably be working with a provider SDK, or other libraries that will be making
+ complex network requests to external services. Sentinel only provides limited
+ capabilities for these kinds of operations in the standard library, so writing
+ this kind of import as a module is not optimal.
+- **Introduce novel functionality not currently supported by Sentinel.**
+ Sentinel's policy language is limited by design to encourage simple policies,
+ reduce its runtime footprint, and to keep it efficient and safe. This will
+ mean that some functionality may be difficult or impossible to express as
+ Sentinel code, and would require a plugin to write.
+- **Extend a complex module.** Additionally, modules are restricted to a single
+ file of Sentinel code, so these will naturally become more and more unwieldy as
+ you continue to add to them. Eventually, there might be a time where a plugin is
+ better suited for the functionality, especially if it's a general-purpose
+ library.
+
+This section details how import plugins are currently configured in the Sentinel
+CLI, and some detail on how they are written. As we continue to build on the
+ability to use import plugins, we will extend this section with more details.
+
+-> **Not all Sentinel-enabled applications will support plugins.** Some
+applications will disable plugins for security reasons. For those that do enable
+plugins, the method to configure plugins will vary.
+
+## Installing Plugins
+
+Sentinel plugins are standalone executables. Sentinel-enabled applications such
+as the CLI launch these plugins and communicate with them via
+[RPC](https://en.wikipedia.org/wiki/Remote_procedure_call). To install a plugin,
+the first step is to download the plugin executable.
+
+After downloading the plugin, you must add it in the CLI configuration file,
+setting its name, path, and any arguments, environment variables, or
+configuration. An example is below:
+
+```hcl
+import "plugin" "custom_time" {
+ source = "/path/to/sentinel-import-time"
+ config = { "fixed_time": 1504155600 }
+}
+```
+
+-> **NOTE:** We are using "custom_time" as standard import paths cannot be
+overriden. See [Configuration File Syntax](/sentinel/configuration#imports) for
+more details.
+
+After configuring the plugin, it will be available on the next `sentinel apply`
+or `sentinel test`.
+
+For more details on configuration, see the
+[plugins](/sentinel/configuration#plugins) section of the [CLI configuration
+file syntax](/sentinel/configuration).
+
+## Debugging Plugins
+
+The Sentinel CLI logs when it launches, configures, and closes a plugin. The
+plugin may also log additional information when it is running.
+
+To debug issues with a plugin, you can usually refer to these logs. Depending on
+the plugin configuration, the logs you see may vary - refer to the plugin
+documentation on how to enable logs for a particular plugin.
+
+## Developing an Import Plugin
+
+Anyone can develop a Sentinel import plugin using the [Sentinel
+SDK](https://github.com/hashicorp/sentinel-sdk). The SDK contains a high-level
+framework for writing plugins in [Go](https://golang.org) including a test
+framework. Additionally, the SDK contains the low-level [gRPC protocol buffers
+definition](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/plugin.proto)
+for writing plugins in other languages.
+
+The rest of this page will document how to write a new Sentinel plugin in Go
+using the Sentinel SDK and the high-level framework provided. You are expected
+to already know the basics of Go and have a properly configured Go installation.
+
+Throughout this page, we'll be developing a simple plugin to access time values.
+
+-> **NOTE:** The example here is not reflective of the actual implementation of
+the [`time`](/sentinel/imports/time) standard import, so make sure to not
+get the two confused.
+
+### Plugin Framework
+
+The Sentinel SDK provides a high-level
+[framework](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework)
+for writing plugins. We recommend using this framework.
+
+#### Basic Concepts
+
+This framework works by implementing the correct Go interfaces.
+
+As a general overview:
+[`framework.Plugin`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Plugin)
+implements the
+[`sdk.Plugin`](https://godoc.org/github.com/hashicorp/sentinel-sdk#Plugin)
+interface and therefore is a valid import plugin. You must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface to configure the plugin. The root may then delegate nested access to
+various
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+implementations. Other interfaces are implemented to provide functionality past
+what is provided by the basic `framework.Namespace` implementation - these are
+documented below.
+
+This all may sound like a lot of interfaces, but each interface typically only
+requires a single function implementation and you're only required to implement
+a single namespace (`framework.Namespace`).
+
+As we move on, we'll use real examples to make this clear.
+
+#### Implementing framework.Root
+
+To begin, you must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface. This is the interface representing the root of your plugin. The root
+itself must be implement
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace),
+which allows values to be accessed. Note that the root can optionally implement
+other interfaces, but they're more advanced and omitted here for simplicity.
+
+For our custom time example, our root may look like this:
+
+```sentinel
+type root struct {
+ time time.Time
+}
+
+// framework.Root impl.
+func (m *root) Configure(raw map[string]interface{}) error {
+ if _, ok := raw["timestamp"]; !ok {
+ raw["timestamp"] = time.Now().Unix()
+ }
+
+ v := raw["timestamp"]
+ timestamp, ok := v.(int)
+ if !ok {
+ return fmt.Errorf("invalid timestamp type %T", v)
+ }
+
+ m.time = time.Unix(timestamp, 0).UTC()
+ return nil
+}
+
+// framework.Namespace impl.
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "minute":
+ return m.time.Minute(), nil
+ }
+
+ return nil, nil
+}
+```
+
+This example implements `framework.Root` and `framework.Namespace` and would
+enable the following within a Sentinel policy once the completed plugin is
+installed.
+
+```sentinel
+import "custom_time"
+
+print(custom_time.minute)
+main = true
+```
+
+#### framework.Namespace
+
+The
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+interface implements a namespace of values. You saw above that
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+must itself be a namespace. However, a namespace `Get` implementation may
+further return namespaces to access nested values.
+
+This enables behavior such as `custom_time.month.string` vs. `custom_time.month.index`.
+Notice each of these examples accesses a nested value within `month`. This can
+be modeled as namespaces within the framework.
+
+In the example below, we return a new namespace for `month` to do just this:
+
+```sentinel
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "month":
+ return &namespaceMonth{Month: m.time.Month()}, nil
+ }
+
+ // ...
+}
+
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil nil
+}
+```
+
+#### Primitive Types and Structs
+
+A namespace may also return any primitive Go type as well as structs. The
+framework automatically exposes primitive values as you would expect. For
+structs, exported fields are lowercased and exposed to the policies. The
+`sentinel` struct tag can be used to control how Sentinel can access the field.
+
+In the example below, we expose a struct directly from the root namespace:
+
+```sentinel
+type Location struct {
+ Name string
+ TimeZone string `sentinel:"time_zone"`
+}
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "location":
+ return &Location{Name: "somewhere", TimeZone: "some zone"}, nil
+ }
+
+ // ...
+}
+```
+
+This makes the following values work within a Sentinel policy:
+`time.location.name`, `time.location.time_zone`.
+
+If a field has an empty `sentinel` struct tag (example: `sentinel:""`),
+then that field will not be accessible from a policy.
+
+#### Optional Interfaces
+
+The following are optional interfaces that can be implemented on top of
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace).
+All of these interfaces can be implemented at any level, except for
+[`framework.New`](#framework-new), which only works at the root level.
+
+##### `framework.Call`
+
+The
+[`framework.Call`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Call)
+interface can be implemented to support function calls.
+
+Implementing this interface is very similar to attribute access, except instead
+of returning the attribute value, you return a function. The framework uses Go
+reflection to determine the argument and result types and calls it. If the
+argument types do not match or the signature is otherwise invalid, an error is
+returned.
+
+For example, let's implement a function to add months to the current month:
+
+```sentinel
+func (m *root) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *root) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.time.AddDate(0, n, 0).Month()}
+}
+```
+
+You can now call the function. If today was in the month of September,
+the policy below would pass:
+
+```sentinel
+import "custom_time"
+
+main = custom_time.add_month(4).string == "January"
+```
+
+##### `framework.Map`
+
+The optional
+[`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+interface allows an alternative method to to present a namespace back to a
+Sentinel policy as a map.
+
+`framework.Map` is best paired with
+[`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys),
+which allows you to quickly fetch the necessary data via `Get` calls on the
+namespace:
+
+```
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil, nil
+}
+
+func (m *namespaceMonth) Map() (map[string]interface{}, error) {
+ return framework.MapFromKeys(m, []string{"string", "index"})
+}
+```
+
+##### `framework.New`
+
+The optional
+[`framework.New`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#New)
+interface can be implemented on a root namespace to add _methods_ to your
+namespaces.
+
+Data returned from a namespace is memoized and returned as a map, and is
+normally not callable - to call a function to operate on the data, you would
+need to create a new namespace from the top-level of the plugin, and then make a
+function call on the result within the same expression.
+
+Let's consider the [root example](#implementing-framework-root). Let's say,
+instead of hard-coding the time value at configuration, we wanted to load it
+on-demand using a `time.now` key, and allow `add_month` to be callable on that
+value. Our root and de-coupled time namespace would now look like:
+
+```
+type root struct{}
+
+func (m *root) Configure(raw map[string]interface{}) error { return nil }
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "now":
+ return &namespaceTime{Time: time.Now()}
+ }
+
+ return nil
+}
+
+func (m *root) New(data map[string]interface{}) (framework.Namespace, error) {
+ if v, ok := data["unix"]; ok {
+ if t, ok := v.(int64); !ok {
+ return &namespaceTime{Time: time.Unix(t, 0)}
+ }
+
+ return nil, fmt.Errorf("expected timestamp to be int64, got %T", v)
+ }
+
+ return nil, nil
+}
+
+type namespaceTime struct {
+ Time time.Time
+}
+
+func (m *namespaceTime) Get(key string) interface{} {
+ switch key {
+ case "unix":
+ return m.Time.Unix()
+
+ // case ...
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) Get(key string) (interface{}, error) {
+ return framework.MapFromKeys(m, []string{"unix", "..."})
+}
+
+func (m *namespaceTime) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.Time.AddDate(0, n, 0).Month()}
+}
+```
+
+Via this example, we can now create and assign a `namespaceTime` using `t = custom_time.now`, and then call `t.add_month(months_to_add)` to return a
+`namespaceMonth` for the corresponding month.
+
+Methods on a namespace can also _modfiy_ the receiver data. So you can, for
+example, add a method named `increment_month` that takes no arguments, but
+increments the time stored in `namespaceTime.Time` by a month. Subsequent
+statements using the receiver would see the new value.
+
+Note that there are some restrictions and guidelines that you should take into
+account when using `framework.New`:
+
+- Only data that makes it back to a policy can be used as receiver data to
+ instantiate new namespaces. As such it's recommended to make use of
+ [`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+ and
+ [`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys)
+ to return all of the attribute data you need to construct new objects.
+- `framework.New` is only supported on the root namespace and has no effect on
+ namespaces below the root. Check all possible cases of receiver data within
+ the top-level `New` function and return the appropriate namespace based on the
+ input data.
+- Do not return general errors from your `New` method. When an unknown lookup is
+ made off of memoized data, it will hit your `New` method for possible
+ instantiation and key calls. This will ensure `undefined` is correctly
+ returned for these cases.
+- `framework.New` is designed to add utility to plugins where calling methods on
+ a value is more intuitive than just making a function call, or just returning
+ all of a data set as a memoized map. Use it sparingly and avoid using it on
+ recursively complex data sets.
+
+### Testing
+
+The SDK exposes a testing framework to verify your plugin works as
+expected. This test framework integrates directly into `go test` so it
+is part of a familiar workflow.
+
+The test framework works by dynamically building your plugin and
+running it against the `sentinel` CLI to verify it behaves as expected.
+This ensures that your plugin builds, your plugin communicates via RPC
+correctly, and that the behavior within a policy is also correct.
+
+The example below shows a complete ready table-driven test for our time plugin.
+You can run this with a normal `go test`.
+
+```sentinel
+package main
+
+import (
+ "os"
+ "testing"
+
+ "github.com/hashicorp/sentinel-sdk"
+ plugintesting "github.com/hashicorp/sentinel-sdk/testing"
+)
+
+func TestMain(m *testing.M) {
+ exitCode := m.Run()
+ plugintesting.Clean()
+ os.Exit(exitCode)
+}
+
+func TestPlugin(t *testing.T) {
+ cases := []struct {
+ Name string
+ Data map[string]interface{}
+ Source string
+ }{
+ {
+ "month",
+ map[string]interface{}{"timestamp": 1495483674},
+ `main = subject.month.string == "September"`,
+ },
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.Name, func(t *testing.T) {
+ plugintesting.TestPlugin(t, plugintesting.TestPluginCase{
+ Config: tc.Data,
+ Source: tc.Source,
+ })
+ })
+ }
+}
+```
+
+### Building Your Plugin
+
+To build your plugin, you must first implement the `main` function.
+The main function should just use the `rpc.Serve` method to serve the
+plugin over the Sentinel RPC layer:
+
+```sentinel
+package main
+
+import (
+ "github.com/hashicorp/sentinel-sdk"
+ "github.com/hashicorp/sentinel-sdk/rpc"
+)
+
+func main() {
+ rpc.Serve(&rpc.ServeOpts{
+ PluginFunc: func() sdk.Plugin {
+ return &framework.Plugin{Root: &root{}}
+ },
+ })
+}
+```
+
+You can then build this using `go build`. The output can be named
+anything. Once your plugin is built, [install it](#installing-plugins)
+and try it out!
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/extending/static-imports.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/extending/static-imports.mdx
new file mode 100644
index 0000000000..cb51beb5d8
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/extending/static-imports.mdx
@@ -0,0 +1,48 @@
+---
+page_title: >-
+ Extending Sentinel: Static Imports
+sidebar_current: docs-extending-static-imports
+description: >-
+ Static imports allow you to write imports that use static data files.
+layout: docs
+---
+
+# Extending Sentinel: Static Imports
+
+Static imports allow you to write imports that use static data files. This can
+provide methods of consuming data in ways that are not available via
+[modules](./modules) or [plugins](./plugins).
+
+### Configuring a Static Import
+
+The [configuration page](/sentinel/configuration#static-imports) details how to
+configure static imports.
+
+### Usage
+
+Using a static import is not that different to using a plugin or module. The
+import must first be introduced to the policy via an import statement.
+
+```sentinel
+import "people"
+```
+
+From here, the static data can be used throughout the policy as you would any
+normal Sentinel value.
+
+```sentinel
+admins = filter people as person {
+ person.role is "Admin"
+}
+```
+
+One differentiator for static imports is that you can directly reference the
+import without the use of selectors. For example, to print the value;
+
+```sentinel
+print(people)
+```
+
+This is due to the static data being directly loaded and converted into a
+Sentinel value, ready for use. Static imports, like modules and plugins, cannot
+be assigned a new value.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/features/index.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/features/index.mdx
new file mode 100644
index 0000000000..e035be978c
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/features/index.mdx
@@ -0,0 +1,28 @@
+---
+page_title: Features
+sidebar_current: docs-features
+description: Learn how features can enhance the Sentinel runtime by enabling further capabilities.
+layout: docs
+---
+
+# Features
+
+Features are a set of capabilities that enhance the runtime experience. They
+are enabled through the [`sentinel`](/sentinel/configuration#sentinel) block of the
+configuration, using the features attribute. An example of how to enable features
+is as follows:
+
+```hcl
+sentinel {
+ features = {
+ apply-all = true
+ terraform = true
+ }
+}
+```
+
+The current set of features are:
+
+- [terraform](/sentinel/features/terraform) - Allowing the Sentinel runtime
+ to interact with Terraform data.
+- apply-all - Force Sentinel to evaluate all policies within a policy set without prematurely terminating on failures.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/features/terraform/index.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/features/terraform/index.mdx
new file mode 100644
index 0000000000..a810dbe478
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/features/terraform/index.mdx
@@ -0,0 +1,24 @@
+---
+page_title: terraform
+sidebar_current: docs-features-terraform
+description: Learn about the terraform feature and its capabilities.
+layout: docs
+---
+
+# terraform feature
+
+The `terraform` feature enhances the Sentinel runtime to work with Terraform
+data. It will add the following imports to the standard library:
+
+- [tfplan/v1](/sentinel/features/terraform/tfplan-v1)
+- [tfplan/v2](/sentinel/features/terraform/tfplan-v2)
+- [tfconfig/v1](/sentinel/features/terraform/tfconfig-v1)
+- [tfconfig/v2](/sentinel/features/terraform/tfconfig-v2)
+- [tfstate/v1](/sentinel/features/terraform/tfstate-v1)
+- [tfstate/v2](/sentinel/features/terraform/tfstate-v2)
+
+-> **NOTE:** The above imports will only work with Terraform 0.12 and above,
+as they rely on the output from the `terraform show -json` command.
+
+It is recommended that the `/v2` suffixed imports are used, as they provide
+the best experience when interacting with the underlying data structures.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/features/terraform/tfconfig-v1.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/features/terraform/tfconfig-v1.mdx
new file mode 100644
index 0000000000..95fb5840ab
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/features/terraform/tfconfig-v1.mdx
@@ -0,0 +1,949 @@
+---
+page_title: tfconfig/v1 - terraform - Features
+sidebar_current: docs-features-terraform-config-v1
+description: The tfconfig/v1 import provides access to a Terraform configuration.
+layout: docs
+---
+
+# Import: tfconfig/v1
+
+~> **Warning:** The `tfconfig/v1` import is deprecated and will be permanently removed in August 2025.
+Use the [tfconfig/v2](/sentinel/docs/features/terraform/tfconfig-v2) import as soon as possible to avoid disruptions.
+The `tfconfig/v2` import offers improved functionality and is designed to better support your policy enforcement needs.
+
+The `tfconfig/v1` import provides access to a Terraform configuration.
+
+The Terraform configuration is the set of `*.tf` files that are used to
+describe the desired infrastructure state. Policies using the `tfconfig/v1`
+import can access all aspects of the configuration: providers, resources,
+data sources, modules, and variables.
+
+Some use cases for `tfconfig/v1` include:
+
+* **Organizational naming conventions**: requiring that configuration elements
+ are named in a way that conforms to some organization-wide standard.
+* **Required inputs and outputs**: organizations may require a particular set
+ of input variable names across all workspaces or may require a particular
+ set of outputs for asset management purposes.
+* **Enforcing particular modules**: organizations may provide a number of
+ "building block" modules and require that each workspace be built only from
+ combinations of these modules.
+* **Enforcing particular providers or resources**: an organization may wish to
+ require or prevent the use of providers and/or resources so that configuration
+ authors cannot use alternative approaches to work around policy
+ restrictions.
+
+Note with these use cases that this import is concerned with object _names_
+in the configuration. Since this is the configuration and not an invocation
+of Terraform, you can't see values for variables, the state, or the diff for
+a pending plan. If you want to write policy around expressions used
+within configuration blocks, you likely want to use the
+[`tfplan/v1`](/sentinel/features/terraform/tfplan-v1) import.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfconfig/v1" {
+ config = {
+ "plan": "./path/to/plan.json"
+ }
+}
+```
+
+## Namespace Overview
+
+The following is a tree view of the import namespace. For more detail on a
+particular part of the namespace, see below.
+
+-> **Note:** The root-level alias keys shown here (`data`, `modules`,
+`providers`, `resources`, and `variables`) are shortcuts to a [module
+namespace](#namespace-module) scoped to the root module. For more details, see
+the section on [root namespace aliases](#root-namespace-aliases).
+
+```
+tfconfig/v1
+├── module() (function)
+│ └── (module namespace)
+│ ├── data
+│ │ └── TYPE.NAME
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── provisioners
+│ │ └── NUMBER
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── type (string)
+│ ├── modules
+│ │ └── NAME
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ ├── source (string)
+│ │ └── version (string)
+│ ├──outputs
+│ │ └── NAME
+│ │ ├── depends_on (list of strings)
+│ │ ├── description (string)
+│ │ ├── sensitive (boolean)
+│ │ ├── references (list of strings)
+│ │ └── value (value)
+│ ├── providers
+│ │ └── TYPE
+│ │ ├── alias
+│ │ │ └── ALIAS
+│ │ │ ├── config (map of keys)
+│ │ | ├── references (map of keys)
+│ │ │ └── version (string)
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── version (string)
+│ ├── resources
+│ │ └── TYPE.NAME
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── provisioners
+│ │ └── NUMBER
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── type (string)
+│ └── variables
+│ └── NAME
+│ ├── default (value)
+│ └── description (string)
+├── module_paths ([][]string)
+│
+├── data (root module alias)
+├── modules (root module alias)
+├── outputs (root module alias)
+├── providers (root module alias)
+├── resources (root module alias)
+└── variables (root module alias)
+```
+
+### `references` Overview
+
+As an example, consider the following resource block:
+
+```hcl
+resource "local_file" "accounts" {
+ content = "some text"
+ filename = "${var.subdomain}.${var.domain}/accounts.txt"
+}
+```
+
+In this example, one might want to ensure `domain` and `subdomain` input
+variables are used within `filename` in this configuration.
+
+-> Any non-static values (such as interpolated strings) are not present within the
+configuration value and `references` should be used instead:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+# filename_references is a list of string values containing the references used in the expression
+filename_references = tfconfig.resources.local_file.accounts.references.filename
+
+main = rule {
+ filename_references contains "var.domain" and
+ filename_references contains "var.subdomain"
+}
+```
+
+The `references` value is present in any namespace where non-constant
+configuration values can be expressed. This is essentially every namespace
+which has a `config` value as well as the `outputs` namespace.
+
+-> **Note:** Remember, this import enforces policy around the literal Terraform
+configuration and not the final values as a result of invoking Terraform. If
+you want to write policy around the _result_ of expressions used within
+configuration blocks (for example, if you wanted to ensure the final value of
+`filename` above includes `accounts.txt`), you likely want to use the
+[`tfplan/v1`](/sentinel/features/terraform/tfplan-v1) import.
+
+## Namespace: Root
+
+The root-level namespace consists of the values and functions documented below.
+
+In addition to this, the root-level `data`, `modules`, `providers`, `resources`,
+and `variables` keys all alias to their corresponding namespaces within the
+[module namespace](#namespace-module).
+
+
+
+### Function: `module()`
+
+```
+module = func(ADDR)
+```
+
+* **Return Type:** A [module namespace](#namespace-module).
+
+The `module()` function in the [root namespace](#namespace-root) returns the
+[module namespace](#namespace-module) for a particular module address.
+
+The address must be a list and is the module address, split on the period (`.`),
+excluding the root module.
+
+Hence, a module with an address of simply `foo` (or `root.foo`) would be
+`["foo"]`, and a module within that (so address `foo.bar`) would be read as
+`["foo", "bar"]`.
+
+[`null`][ref-null] is returned if a module address is invalid, or if the module
+is not present in the configuration.
+
+[ref-null]: /sentinel/language/spec#null
+
+As an example, given the following module block:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+If the module contained the following content:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { subject.module(["foo"]).resources.null_resource.foo.config.triggers[0].foo is "bar" }
+```
+
+
+
+### Value: `module_paths`
+
+* **Value Type:** List of a list of strings.
+
+The `module_paths` value within the [root namespace](#namespace-root) is a list
+of all of the modules within the Terraform configuration.
+
+Modules not present in the configuration will not be present here, even if they
+are present in the diff or state.
+
+This data is represented as a list of a list of strings, with the inner list
+being the module address, split on the period (`.`).
+
+The root module is included in this list, represented as an empty inner list.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The value of `module_paths` would be:
+
+```
+[
+ [],
+ ["foo"],
+]
+```
+
+And the following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.module_paths contains ["foo"] }
+```
+
+#### Iterating Through Modules
+
+Iterating through all modules to find particular resources can be useful. This
+[example][iterate-over-modules] shows how to use `module_paths` with the
+[`module()` function](#function-module-) to find all resources of a
+particular type from all modules using the `tfplan` import. By changing `tfplan`
+in this function to `tfconfig`, you could make a similar function find all
+resources of a specific type in the Terraform configuration.
+
+[iterate-over-modules]: https://developer.hashicorp.com/terraform/cloud-docs/policy-enforcement/sentinel#sentinel-imports
+
+## Namespace: Module
+
+The **module namespace** can be loaded by calling [`module()`](#root-function-module)
+for a particular module.
+
+It can be used to load the following child namespaces:
+
+* `data` - Loads the [resource namespace](#namespace-resources-data-sources),
+ filtered against data sources.
+* `modules` - Loads the [module configuration
+ namespace](#namespace-module-configuration).
+* `outputs` - Loads the [output namespace](#namespace-outputs).
+* `providers` - Loads the [provider namespace](#namespace-providers).
+* `resources` - Loads the [resource
+ namespace](#namespace-resources-data-sources), filtered against resources.
+* `variables` - Loads the [variable namespace](#namespace-variables).
+
+### Root Namespace Aliases
+
+The root-level `data`, `modules`, `providers`, `resources`, and `variables` keys
+all alias to their corresponding namespaces within the module namespace, loaded
+for the root module. They are the equivalent of running `module([]).KEY`.
+
+
+
+## Namespace: Resources/Data Sources
+
+The **resource namespace** is a namespace _type_ that applies to both resources
+(accessed by using the `resources` namespace key) and data sources (accessed
+using the `data` namespace key).
+
+Accessing an individual resource or data source within each respective namespace
+can be accomplished by specifying the type and name, in the syntax
+`[resources|data].TYPE.NAME`.
+
+In addition, each of these namespace levels is a map, allowing you to filter
+based on type and name. Some examples of multi-level access are below:
+
+* To fetch all `aws_instance` resources within the root module, you can specify
+ `tfconfig.resources.aws_instance`. This would give you a map of resource
+ namespaces indexed from the names of each resource (`foo`, `bar`, and so
+ on).
+* To fetch all resources within the root module, irrespective of type, use
+ `tfconfig.resources`. This is indexed by type, as shown above with
+ `tfconfig.resources.aws_instance`, with names being the next level down.
+
+As an example, perhaps you wish to deny use of the `local_file` resource
+in your configuration. Consider the following resource block:
+
+```hcl
+resource "local_file" "foo" {
+ content = "foo!"
+ filename = "${path.module}/foo.bar"
+}
+```
+
+The following policy would fail:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.resources not contains "local_file" }
+```
+
+Further explanation of the namespace will be in the context of resources. As
+mentioned, when operating on data sources, use the same syntax, except with
+`data` in place of `resources`.
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+The `config` value within the [resource
+namespace](#namespace-resources-data-sources) is a map of key-value pairs that
+directly map to Terraform config keys and values.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#resources-value-references) should be used instead.
+
+As an example, consider the following resource block:
+
+```hcl
+resource "local_file" "accounts" {
+ content = "some text"
+ filename = "accounts.txt"
+}
+```
+
+In this example, one might want to access `filename` to validate that the correct
+file name is used. Given the above example, the following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule {
+ tfconfig.resources.local_file.accounts.config.filename is "accounts.txt"
+}
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [resource namespace](#namespace-resources-data-sources)
+contains the identifiers within non-constant expressions found in [`config`](#resources-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+
+
+### Value: `provisioners`
+
+* **Value Type:** List of [provisioner namespaces](#namespace-provisioners).
+
+The `provisioners` value within the [resource namespace](#namespace-resources)
+represents the [provisioners][ref-tf-provisioners] within a specific resource.
+
+Provisioners are listed in the order they were provided in the configuration
+file.
+
+While the `provisioners` value will be present within data sources, it will
+always be an empty map `null` (in Terraform 0.12) since data sources cannot
+actually have provisioners.
+
+The data within a provisioner can be inspected via the returned [provisioner
+namespace](#namespace-provisioners).
+
+[ref-tf-provisioners]: https://developer.hashicorp.com/terraform/language/resources/provisioners/syntax
+
+## Namespace: Provisioners
+
+The **provisioner namespace** represents the configuration for a particular
+[provisioner][ref-tf-provisioners] within a specific resource.
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+The `config` value within the [provisioner namespace](#namespace-provisioners)
+represents the values of the keys within the provisioner.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#provisioners-value-references) should be used instead.
+
+As an example, given the following resource block:
+
+```hcl
+resource "null_resource" "foo" {
+ # ...
+
+ provisioner "local-exec" {
+ command = "echo ${self.private_ip} > file.txt"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule {
+ tfconfig.resources.null_resource.foo.provisioners[0].config.command is "echo ${self.private_ip} > file.txt"
+}
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [provisioner namespace](#namespace-provisioners)
+contains the identifiers within non-constant expressions found in [`config`](#provisioners-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+
+
+### Value: `type`
+
+* **Value Type:** String.
+
+The `type` value within the [provisioner namespace](#namespace-provisioners)
+represents the type of the specific provisioner.
+
+As an example, in the following resource block:
+
+```hcl
+resource "null_resource" "foo" {
+ # ...
+
+ provisioner "local-exec" {
+ command = "echo ${self.private_ip} > file.txt"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.resources.null_resource.foo.provisioners[0].type is "local-exec" }
+```
+
+## Namespace: Module Configuration
+
+The **module configuration** namespace displays data on _module configuration_
+as it is given within a `module` block. This means that the namespace concerns
+itself with the contents of the declaration block (example: the `source`
+parameter and variable assignment keys), not the data within the module
+(example: any contained resources or data sources). For the latter, the module
+instance would need to be looked up with the [`module()`
+function](#root-function-module).
+
+
+
+### Value: `source`
+
+* **Value Type:** String.
+
+The `source` value within the [module configuration
+namespace](#namespace-module-configuration) represents the module source path as
+supplied to the module configuration.
+
+As an example, given the module declaration block:
+
+```hcl
+module "foo" {
+ source = "./foo"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.modules.foo.source is "./foo" }
+```
+
+
+
+### Value: `version`
+
+* **Value Type:** String.
+
+The `version` value within the [module configuration
+namespace](#namespace-module-configuration) represents the [version
+constraint][module-version-constraint] for modules that support it, such as
+modules within the [Terraform Module Registry][terraform-module-registry] or the
+[HCP Terraform private module registry][tfe-private-registry].
+
+[module-version-constraint]: https://developer.hashicorp.com/terraform/language/modules#module-versions
+
+[terraform-module-registry]: https://registry.terraform.io/
+
+[tfe-private-registry]: https://developer.hashicorp.com/terraform/cloud-docs/registry
+
+As an example, given the module declaration block:
+
+```hcl
+module "foo" {
+ source = "foo/bar"
+ version = "~> 1.2"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.modules.foo.version is "~> 1.2" }
+```
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#modules-value-references) should be used instead.
+
+The `config` value within the [module configuration
+namespace](#namespace-module-configuration) represents the values of the keys
+within the module configuration. This is every key within a module declaration
+block except [`source`](#modules-value-source) and [`version`](#modules-value-version), which
+have their own values.
+
+As an example, given the module declaration block:
+
+```hcl
+module "foo" {
+ source = "./foo"
+
+ bar = "baz"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.modules.foo.config.bar is "baz" }
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [module configuration namespace](#namespace-module-configuration)
+contains the identifiers within non-constant expressions found in [`config`](#modules-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+## Namespace: Outputs
+
+The **output namespace** represents _declared_ output data within a
+configuration. As such, configuration for the [`value`](#outputs-value-value) attribute
+will be in its raw form, and not yet interpolated. For fully interpolated output
+values, see the [`tfstate` import][ref-tfe-sentinel-tfstate].
+
+[ref-tfe-sentinel-tfstate]: /sentinel/features/terraform/tfstate-v1
+
+This namespace is indexed by output name.
+
+
+
+### Value: `depends_on`
+
+* **Value Type:** A list of strings.
+
+The `depends_on` value within the [output namespace](#namespace-outputs)
+represents any _explicit_ dependencies for this output. For more information,
+see the [depends_on output setting][ref-depends_on] within the general Terraform
+documentation.
+
+[ref-depends_on]: https://developer.hashicorp.com/terraform/language/values/outputs#depends_on
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ depends_on = ["null_resource.bar"]
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.depends_on[0] is "null_resource.bar" }
+```
+
+
+
+### Value: `description`
+
+* **Value Type:** String.
+
+The `description` value within the [output namespace](#namespace-outputs)
+represents the defined description for this output.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ description = "foobar"
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.description is "foobar" }
+```
+
+
+
+### Value: `sensitive`
+
+* **Value Type:** Boolean.
+
+The `sensitive` value within the [output namespace](#namespace-outputs)
+represents if this value has been marked as sensitive or not.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ sensitive = true
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { subject.outputs.id.sensitive }
+```
+
+
+
+### Value: `value`
+
+* **Value Type:** Any primitive type, list or map.
+
+The `value` value within the [output namespace](#namespace-outputs) represents
+the defined value for the output as declared in the configuration. Primitives
+will bear the implicit type of their declaration (string, int, float, or bool),
+and maps and lists will be represented as such.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#outputs-value-references) should be used instead.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ value = "foo"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.value is "foo" }
+```
+
+
+
+### Value: `references`
+
+* **Value Type:**. List of strings.
+
+The `references` value within the [output namespace](#namespace-outputs)
+contains the names of any referenced identifiers when [`value`](#outputs-value-value)
+is a non-constant expression.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.references contains "null_resource.foo.id" }
+```
+
+## Namespace: Providers
+
+The **provider namespace** represents data on the declared providers within a
+namespace.
+
+This namespace is indexed by provider type and _only_ contains data about
+providers when actually declared. If you are using a completely implicit
+provider configuration, this namespace will be empty.
+
+This namespace is populated based on the following criteria:
+
+* The top-level namespace [`config`](#providers-value-config) and
+ [`version`](#providers-value-version) values are populated with the configuration and
+ version information from the default provider (the provider declaration that
+ lacks an alias).
+* Any aliased providers are added as namespaces within the
+ [`alias`](#providers-value-alias) value.
+* If a module lacks a default provider configuration, the top-level `config` and
+ `version` values will be empty.
+
+
+
+### Value: `alias`
+
+* **Value Type:** A map of [provider namespaces](#namespace-providers), indexed
+ by alias.
+
+The `alias` value within the [provider namespace](#namespace-providers)
+represents all declared [non-default provider
+instances][ref-tf-provider-instances] for a specific provider type, indexed by
+their specific alias.
+
+[ref-tf-provider-instances]: https://developer.hashicorp.com/terraform/language/providers/configuration#alias-multiple-provider-configurations
+
+The return type is a provider namespace with the data for the instance in
+question loaded. The `alias` key will not be available within this namespace.
+
+As an example, given the following provider declaration block:
+
+```hcl
+provider "aws" {
+ alias = "east"
+ region = "us-east-1"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.providers.aws.alias.east.config.region is "us-east-1" }
+```
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#providers-value-references) should be used instead.
+
+The `config` value within the [provider namespace](#namespace-providers)
+represents the values of the keys within the provider's configuration, with the
+exception of the provider version, which is represented by the
+[`version`](#providers-value-version) value. [`alias`](#providers-value-alias) is also not included
+when the provider is aliased.
+
+As an example, given the following provider declaration block:
+
+```hcl
+provider "aws" {
+ region = "us-east-1"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.providers.aws.config.region is "us-east-1" }
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [provider namespace](#namespace-providers)
+contains the identifiers within non-constant expressions found in [`config`](#providers-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+
+
+### Value: `version`
+
+* **Value Type:** String.
+
+The `version` value within the [provider namespace](#namespace-providers)
+represents the explicit expected version of the supplied provider. This includes
+the pessimistic operator.
+
+As an example, given the following provider declaration block:
+
+```hcl
+provider "aws" {
+ version = "~> 1.34"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.providers.aws.version is "~> 1.34" }
+```
+
+## Namespace: Variables
+
+The **variable namespace** represents _declared_ variable data within a
+configuration. As such, static data can be extracted, such as defaults, but not
+dynamic data, such as the current value of a variable within a plan (although
+this can be extracted within the [`tfplan` import][ref-tfe-sentinel-tfplan]).
+
+[ref-tfe-sentinel-tfplan]: /sentinel/features/terraform/tfplan-v1
+
+This namespace is indexed by variable name.
+
+
+
+### Value: `default`
+
+* **Value Type:** Any primitive type, list, map, or `null`.
+
+The `default` value within the [variable namespace](#namespace-variables)
+represents the default for the variable as declared in the configuration.
+
+The actual value will be as configured. Primitives will bear the implicit type
+of their declaration (string, int, float, or bool), and maps and lists will be
+represented as such.
+
+If no default is present, the value will be [`null`][ref-sentinel-null] (not to
+be confused with [`undefined`][ref-sentinel-undefined]).
+
+[ref-sentinel-null]: /sentinel/language/spec#null
+
+[ref-sentinel-undefined]: /sentinel/language/undefined
+
+As an example, given the following variable blocks:
+
+```hcl
+variable "foo" {
+ default = "bar"
+}
+
+variable "number" {
+ default = 42
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+default_foo = rule { tfconfig.variables.foo.default is "bar" }
+default_number = rule { tfconfig.variables.number.default is 42 }
+
+main = rule { default_foo and default_number }
+```
+
+
+
+### Value: `description`
+
+* **Value Type:** String.
+
+The `description` value within the [variable namespace](#namespace-variables)
+represents the description of the variable, as provided in configuration.
+
+As an example, given the following variable block:
+
+```hcl
+variable "foo" {
+ description = "foobar"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.variables.foo.description is "foobar" }
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/features/terraform/tfconfig-v2.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/features/terraform/tfconfig-v2.mdx
new file mode 100644
index 0000000000..1fe4fff092
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/features/terraform/tfconfig-v2.mdx
@@ -0,0 +1,441 @@
+---
+page_title: tfconfig/v2 - terraform - Features
+sidebar_current: docs-features-terraform-tfconfig-v2
+description: The tfconfig/v2 import provides access to a Terraform configuration.
+layout: docs
+---
+
+# Import: tfconfig/v2
+
+The `tfconfig/v2` import provides access to a Terraform configuration.
+
+The Terraform configuration is the set of `*.tf` files that are used to
+describe the desired infrastructure state. Policies using the `tfconfig`
+import can access all aspects of the configuration: providers, resources,
+data sources, modules, and variables.
+
+Some use cases for `tfconfig` include:
+
+* **Organizational naming conventions**: requiring that configuration elements
+ are named in a way that conforms to some organization-wide standard.
+* **Required inputs and outputs**: organizations may require a particular set
+ of input variable names across all workspaces or may require a particular
+ set of outputs for asset management purposes.
+* **Enforcing particular modules**: organizations may provide a number of
+ "building block" modules and require that each workspace be built only from
+ combinations of these modules.
+* **Enforcing particular providers or resources**: an organization may wish to
+ require or prevent the use of providers and/or resources so that configuration
+ authors cannot use alternative approaches to work around policy
+ restrictions.
+
+The data in the `tfconfig/v2` import is sourced from the JSON configuration file
+that is generated by the [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) command. For more information on
+the file format, see the [JSON Output Format](https://developer.hashicorp.com/terraform/internals/json-format)
+page.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfconfig/v2" {
+ config = {
+ "plan": "./path/to/plan.json"
+ }
+}
+```
+
+## Import Overview
+
+The `tfconfig/v2` import is structured as a series of _collections_, keyed as a
+specific format, such as resource address, module address, or a
+specifically-formatted provider key.
+
+```
+tfconfig/v2
+├── strip_index() (function)
+├── providers
+│ └── (indexed by [module_address:]provider[.alias])
+│ ├── provider_config_key (string)
+│ ├── name (string)
+│ ├── full_name (string)
+│ ├── alias (string)
+│ ├── module_address (string)
+│ ├── config (block expression representation)
+│ └── version_constraint (string)
+├── resources
+│ └── (indexed by address)
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── provider_config_key (string)
+│ ├── provisioners (list)
+│ │ └── (ordered provisioners for this resource only)
+│ ├── config (block expression representation)
+│ ├── count (expression representation)
+│ ├── for_each (expression representation)
+│ └── depends_on (list of strings)
+├── provisioners
+│ └── (indexed by resource_address:index)
+│ ├── resource_address (string)
+│ ├── type (string)
+│ ├── index (string)
+│ └── config (block expression representation)
+├── variables
+│ └── (indexed by module_address:name)
+│ ├── module_address (string)
+│ ├── name (string)
+│ ├── default (value)
+│ └── description (string)
+├── outputs
+│ └── (indexed by module_address:name)
+│ ├── module_address (string)
+│ ├── name (string)
+│ ├── sensitive (boolean)
+│ ├── value (expression representation)
+│ ├── description (string)
+│ └── depends_on (list of strings)
+└── module_calls
+ └── (indexed by module_address:name)
+ ├── module_address (string)
+ ├── name (string)
+ ├── source (string)
+ ├── config (block expression representation)
+ ├── count (expression representation)
+ ├── depends_on (expression representation)
+ ├── for_each (expression representation)
+ └── version_constraint (string)
+```
+
+The collections are:
+
+* [`providers`](#the-providers-collection) - The configuration for all provider
+ instances across all modules in the configuration.
+* [`resources`](#the-resources-collection) - The configuration of all resources
+ across all modules in the configuration.
+* [`variables`](#the-variables-collection) - The configuration of all variable
+ definitions across all modules in the configuration.
+* [`outputs`](#the-outputs-collection) - The configuration of all output
+ definitions across all modules in the configuration.
+* [`module_calls`](#the-module_calls-collection) - The configuration of all module
+ calls (individual [`module`](/terraform/language/modules) blocks) across
+ all modules in the configuration.
+
+These collections are specifically designed to be used with the
+[`filter`](/sentinel/language/collection-operations#filter-expression)
+quantifier expression in Sentinel, so that one can collect a list of resources
+to perform policy checks on without having to write complex module or
+configuration traversal. As an example, the following code will return all
+`aws_instance` resource types within the configuration, regardless of what
+module they are in:
+
+```
+all_aws_instances = filter tfconfig.resources as _, r {
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+You can add specific attributes to the filter to narrow the search, such as the
+module address. The following code would return resources in a module named
+`foo` only:
+
+```
+all_aws_instances = filter tfconfig.resources as _, r {
+ r.module_address is "module.foo" and
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+### Address Differences Between `tfconfig`, `tfplan`, and `tfstate`
+
+This import deals with configuration before it is expanded into a
+resource graph by Terraform. As such, it is not possible to compute an index as
+the import is building its collections and computing addresses for resources and
+modules.
+
+As such, addresses found here may not always match the expanded addresses found
+in the [`tfplan/v2`](/sentinel/features/terraform/tfplan-v2) and [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2)
+imports, specifically when
+[`count`](https://developer.hashicorp.com/terraform/language/resources#count-multiple-resource-instances-by-count)
+and
+[`for_each`](https://developer.hashicorp.com/terraform/language/resources#for_each-multiple-resource-instances-defined-by-a-map-or-set-of-strings),
+are used.
+
+As an example, consider a resource named `null_resource.foo` with a count of `2`
+located in a module named `bar`. While there will possibly be entries in the
+other imports for `module.bar.null_resource.foo[0]` and
+`module.bar.null_resource.foo[1]`, in `tfconfig/v2`, there will only be a
+`module.bar.null_resource.foo`. As mentioned in the start of this section, this
+is because configuration actually _defines_ this scaling, whereas _expansion_
+actually happens when the resource graph is built, which happens as a natural
+part of the refresh and planning process.
+
+The `strip_index` helper function, found in this import, can assist in
+removing the indexes from addresses found in the `tfplan/v2` and `tfstate/v2`
+imports so that data from those imports can be used to reference data in this
+one.
+
+## The `strip_index` Function
+
+The `strip_index` helper function can be used to remove indexes from addresses
+found in [`tfplan/v2`](/sentinel/features/terraform/tfplan-v2) and [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2),
+by removing the indexes from each resource.
+
+This can be used to help facilitate cross-import lookups for data between plan,
+state, and config.
+
+```
+import "tfconfig/v2" as tfconfig
+import "tfplan/v2" as tfplan
+
+main = rule {
+ all filter tfplan.resource_changes as _, rc {
+ rc.mode is "managed" and
+ rc.type is "aws_instance"
+ } as _, rc {
+ tfconfig.resources[tfconfig.strip_index(rc.address)].config.ami.constant_value is "ami-abcdefgh012345"
+ }
+}
+```
+
+## Expression Representations
+
+Most collections in this import will have one of two kinds of _expression
+representations_. This is a verbose format for expressing a (parsed)
+configuration value independent of the configuration source code, which is not
+100% available to a policy check in HCP Terraform.
+
+```
+(expression representation)
+├── constant_value (value)
+└── references (list of strings)
+```
+
+There are two major parts to an expression representation:
+
+* Any _strictly constant value_ is expressed as an expression with a
+ `constant_value` field.
+* Any expression that requires some degree of evaluation to generate the final
+ value - even if that value is known at plan time - is not expressed in
+ configuration. Instead, any particular references that are made are added to
+ the `references` field. More details on this field can be found in the
+ [expression
+ representation](https://developer.hashicorp.com/terraform/internals/json-format#expression-representation)
+ section of the JSON output format documentation.
+
+For example, to determine if an output is based on a particular
+resource value, one could do:
+
+```
+import "tfconfig/v2" as tfconfig
+
+main = rule {
+ tfconfig.outputs["instance_id"].value.references is ["aws_instance.foo"]
+}
+```
+
+-> **Note:** The representation does not account for
+complex interpolations or other expressions that combine constants with other
+expression data. For example, the partially constant data in `"foo${var.bar}"` would be lost.
+
+### Block Expression Representation
+
+Expanding on the above, a multi-value expression representation (such as the
+kind found in a [`resources`](#the-resources-collection) collection element) is
+similar, but the root value is a keyed map of expression representations. This
+is repeated until a "scalar" expression value is encountered, ie: a field that
+is not a block in the resource's schema.
+
+```
+(block expression representation)
+└── (attribute key)
+ ├── (child block expression representation)
+ │ └── (...)
+ ├── constant_value (value)
+ └── references (list of strings)
+```
+
+As an example, one can validate expressions in an
+[`aws_instance`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance) resource using the
+following:
+
+```
+import "tfconfig/v2" as tfconfig
+
+main = rule {
+ tfconfig.resources["aws_instance.foo"].config.ami.constant_value is "ami-abcdefgh012345"
+}
+```
+
+Note that _nested blocks_, sometimes known as _sub-resources_, will be nested in
+configuration as as list of blocks (reflecting their ultimate nature as a list
+of objects). An example would be the `aws_instance` resource's
+[`ebs_block_device`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#ebs-ephemeral-and-root-block-devices) block:
+
+```
+import "tfconfig/v2" as tfconfig
+
+main = rule {
+ tfconfig.resources["aws_instance.foo"].config.ebs_block_device[0].volume_size < 10
+}
+```
+
+## The `providers` Collection
+
+The `providers` collection is a collection representing the configurations of
+all provider instances across all modules in the configuration.
+
+This collection is indexed by an opaque key. This is currently
+`module_address:provider.alias`, the same value as found in the
+`provider_config_key` field. `module_address` and the colon delimiter are
+omitted for the root module.
+
+The `provider_config_key` field is also found in the `resources` collection and
+can be used to locate a provider that belongs to a configured resource.
+
+The fields in this collection are as follows:
+
+* `provider_config_key` - The opaque configuration key, used as the index key.
+* `name` - The name of the provider, ie: `aws`.
+* `full_name` - The fully-qualified name of the provider, e.g. `registry.terraform.io/hashicorp/aws`.
+* `alias` - The alias of the provider, ie: `east`. Empty for a default provider.
+* `module_address` - The address of the module this provider appears in.
+* `config` - A [block expression
+ representation](#block-expression-representation) with provider configuration
+ values.
+* `version_constraint` - The defined version constraint for this provider.
+
+## The `resources` Collection
+
+The `resources` collection is a collection representing all of the resources
+found in all modules in the configuration.
+
+This collection is indexed by the resource address.
+
+The fields in this collection are as follows:
+
+* `address` - The resource address. This is the index of the collection.
+* `module_address` - The module address that this resource was found in.
+* `mode` - The resource mode, either `managed` (resources) or `data` (data
+ sources).
+* `type` - The type of resource, ie: `null_resource` in `null_resource.foo`.
+* `name` - The name of the resource, ie: `foo` in `null_resource.foo`.
+* `provider_config_key` - The opaque configuration key that serves as the index
+ of the [`providers`](#the-providers-collection) collection.
+* `provisioners` - The ordered list of provisioners for this resource. The
+ syntax of the provisioners matches those found in the
+ [`provisioners`](#the-provisioners-collection) collection, but is a list
+ indexed by the order the provisioners show up in the resource.
+* `config` - The [block expression
+ representation](#block-expression-representation) of the configuration values
+ found in the resource.
+* `count` - The [expression data](#expression-representations) for the `count`
+ value in the resource.
+* `for_each` - The [expression data](#expression-representations) for the
+ `for_each` value in the resource.
+* `depends_on` - The contents of the `depends_on` config directive, which
+ declares explicit dependencies for this resource.
+
+## The `provisioners` Collection
+
+The `provisioners` collection is a collection of all of the provisioners found
+across all resources in the configuration.
+
+While normally bound to a resource in an ordered fashion, this collection allows
+for the filtering of provisioners within a single expression.
+
+This collection is indexed with a key following the format
+`resource_address:index`, with each field matching their respective field in the
+particular element below:
+
+* `resource_address`: The address of the resource that the provisioner was found
+ in. This can be found in the [`resources`](#the-resources-collection)
+ collection.
+* `type`: The provisioner type, ie: `local_exec`.
+* `index`: The provisioner index as it shows up in the resource provisioner
+ order.
+* `config`: The [block expression
+ representation](#block-expression-representation) of the configuration values
+ in the provisioner.
+
+## The `variables` Collection
+
+The `variables` collection is a collection of all variables across all modules
+in the configuration.
+
+Note that this tracks variable definitions, not values. See the [`tfplan/v2`
+`variables` collection](/sentinel/features/terraform/tfplan-v2#the-variables-collection) for variable
+values set within a plan.
+
+This collection is indexed by the key format `module_address:name`, with each
+field matching their respective name below. `module_address` and the colon
+delimiter are omitted for the root module.
+
+* `module_address` - The address of the module the variable was found in.
+* `name` - The name of the variable.
+* `default` - The defined default value of the variable.
+* `description` - The description of the variable.
+
+## The `outputs` Collection
+
+The `outputs` collection is a collection of all outputs across all modules in
+the configuration.
+
+Note that this tracks variable definitions, not values. See the [`tfstate/v2`
+`outputs` collection](/sentinel/features/terraform/tfstate-v2#the-outputs-collection) for the final
+values of outputs set within a state. The [`tfplan/v2` `output_changes`
+collection](/sentinel/features/terraform/tfplan-v2#the-output_changes-collection) also contains a more
+complex collection of planned output changes.
+
+This collection is indexed by the key format `module_address:name`, with each
+field matching their respective name below. `module_address` and the colon
+delimiter are omitted for the root module.
+
+* `module_address` - The address of the module the output was found in.
+* `name` - The name of the output.
+* `sensitive` - Indicates whether or not the output was marked as
+ [`sensitive`](https://developer.hashicorp.com/terraform/language/values/outputs#sensitive-suppressing-values-in-cli-output).
+* `value` - An [expression representation](#expression-representations) for the output.
+* `description` - The description of the output.
+* `depends_on` - A list of resource names that the output depends on. These are
+ the hard-defined output dependencies as defined in the
+ [`depends_on`](https://developer.hashicorp.com/terraform/language/values/outputs#depends_on-explicit-output-dependencies)
+ field in an output declaration, not the dependencies that get derived from
+ natural evaluation of the output expression (these can be found in the
+ `references` field of the expression representation).
+
+## The `module_calls` Collection
+
+The `module_calls` collection is a collection of all module declarations at all
+levels within the configuration.
+
+Note that this is the
+[`module`](https://developer.hashicorp.com/terraform/language/modules#calling-a-child-module) stanza in
+any particular configuration, and not the module itself. Hence, a declaration
+for `module.foo` would actually be declared in the root module, which would be
+represented by a blank field in `module_address`.
+
+This collection is indexed by the key format `module_address:name`, with each
+field matching their respective name below. `module_address` and the colon
+delimiter are omitted for the root module.
+
+* `module_address` - The address of the module the declaration was found in.
+* `name` - The name of the module.
+* `source` - The contents of the `source` field.
+* `config` - A [block expression
+ representation](#block-expression-representation) for all parameter values
+ sent to the module.
+* `count` - An [expression representation](#expression-representations) for the
+ `count` field.
+* `depends_on`: An [expression representation](#expression-representations) for the
+ `depends_on` field.
+* `for_each` - An [expression representation](#expression-representations) for
+ the `for_each` field.
+* `version_constraint` - The string value found in the `version` field of the
+ module declaration.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/features/terraform/tfplan-v1.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/features/terraform/tfplan-v1.mdx
new file mode 100644
index 0000000000..1e9f9f1f9b
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/features/terraform/tfplan-v1.mdx
@@ -0,0 +1,614 @@
+---
+page_title: tfplan/v1 - terraform - Features
+sidebar_current: docs-features-terraform-tfplan-v1
+description: >-
+ The tfplan/v1 import provides access to a Terraform plan. A Terraform plan is the
+ file created as a result of `terraform plan` and is the input to `terraform
+ apply`. The plan represents the changes that Terraform needs to make to
+ infrastructure to reach the desired state represented by the configuration.
+layout: docs
+---
+
+# Import: tfplan/v1
+
+~> **Warning:** The `tfplan/v1` import is deprecated and will be permanently removed in August 2025.
+Use the updated [tfplan/v2](/sentinel/docs/features/terraform/tfplan-v2) import as soon as possible to avoid disruptions.
+The `tfplan/v2` import offers improved functionality and is designed to better support your policy enforcement needs.
+
+The `tfplan/v1` import provides access to a Terraform plan. A Terraform plan is the
+file created as a result of `terraform plan` and is the input to `terraform
+apply`. The plan represents the changes that Terraform needs to make to
+infrastructure to reach the desired state represented by the configuration.
+
+In addition to the diff data available in the plan, there is an
+[`applied`](#value-applied) state available that merges the plan with the state
+to create the planned state after apply.
+
+Finally, this import also allows you to access the configuration files and the
+Terraform state at the time the plan was run. See the section on [accessing a
+plan's state and configuration
+data](#accessing-a-plan-39-s-state-and-configuration-data) for more information.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan and schemas file.
+
+```hcl
+import "plugin" "tfplan/v1" {
+ config = {
+ "plan_path": "./path/to/plan.json",
+ "schemas_path": "./path/to/schemas.json"
+ }
+}
+```
+
+## Namespace Overview
+
+The following is a tree view of the import namespace. For more detail on a
+particular part of the namespace, see below.
+
+-> Note that the root-level alias keys shown here (`data`, `path`, and
+`resources`) are shortcuts to a [module namespace](#namespace-module) scoped to
+the root module. For more details, see the section on [root namespace
+aliases](#root-namespace-aliases).
+
+```
+tfplan/v1
+├── module() (function)
+│ └── (module namespace)
+│ ├── path ([]string)
+│ ├── data
+│ │ └── TYPE.NAME[NUMBER]
+│ │ ├── applied (map of keys)
+│ │ └── diff
+│ │ └── KEY
+│ │ ├── computed (bool)
+│ │ ├── new (string)
+│ │ └── old (string)
+│ └── resources
+│ └── TYPE.NAME[NUMBER]
+│ ├── applied (map of keys)
+│ ├── destroy (bool)
+│ ├── requires_new (bool)
+│ └── diff
+│ └── KEY
+│ ├── computed (bool)
+│ ├── new (string)
+│ └── old (string)
+├── module_paths ([][]string)
+├── terraform_version (string)
+├── variables (map of keys)
+│
+├── data (root module alias)
+├── path (root module alias)
+├── resources (root module alias)
+│
+├── config (tfconfig namespace alias)
+└── state (tfstate import alias)
+```
+
+## Namespace: Root
+
+The root-level namespace consists of the values and functions documented below.
+
+In addition to this, the root-level `data`, `path`, and `resources` keys alias
+to their corresponding namespaces or values within the [module
+namespace](#namespace-module).
+
+### Accessing a Plan's State and Configuration Data
+
+The `config` and `state` keys alias to the [`tfconfig`](/sentinel/features/terraform/tfconfig-v1) and
+[`tfstate`](/sentinel/features/terraform/tfstate-v1) namespaces, respectively, with the data sourced from
+the Terraform _plan_ (as opposed to actual configuration and state).
+
+-> Note that these aliases are not represented as maps. While they will appear
+empty when viewed as maps, the specific import namespace keys will still be
+accessible.
+
+### Function: `module()`
+
+```
+module = func(ADDR)
+```
+
+* **Return Type:** A [module namespace](#namespace-module).
+
+The `module()` function in the [root namespace](#namespace-root) returns the
+[module namespace](#namespace-module) for a particular module address.
+
+The address must be a list and is the module address, split on the period (`.`),
+excluding the root module.
+
+Hence, a module with an address of simply `foo` (or `root.foo`) would be
+`["foo"]`, and a module within that (so address `foo.bar`) would be read as
+`["foo", "bar"]`.
+
+[`null`](/sentinel/language/spec#null) is returned if a module address is
+invalid, or if the module is not present in the diff.
+
+As an example, given the following module block:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+If the module contained the following content:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.module(["foo"]).resources.null_resource.foo[0].applied.triggers.foo is "bar" }
+```
+
+### Value: `module_paths`
+
+* **Value Type:** List of a list of strings.
+
+The `module_paths` value within the [root namespace](#namespace-root) is a list
+of all of the modules within the Terraform diff for the current plan.
+
+Modules not present in the diff will not be present here, even if they are
+present in the configuration or state.
+
+This data is represented as a list of a list of strings, with the inner list
+being the module address, split on the period (`.`).
+
+The root module is included in this list, represented as an empty inner list, as
+long as there are changes.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The value of `module_paths` would be:
+
+```
+[
+ [],
+ ["foo"],
+]
+```
+
+And the following policy would evaluate to `true`:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.module_paths contains ["foo"] }
+```
+
+-> Note the above example only applies if the module is present in the diff.
+
+#### Iterating Through Modules
+
+Iterating through all modules to find particular resources can be useful. This
+[example][iterate-over-modules] shows how to use `module_paths` with the
+[`module()` function](#function-module-) to find all resources of a
+particular type from all modules that have pending changes using the `tfplan/v1`
+import.
+
+[iterate-over-modules]: https://developer.hashicorp.com/terraform/cloud-docs/policy-enforcement/sentinel#iterate-over-modules-and-find-resources
+
+### Value: `terraform_version`
+
+* **Value Type:** String.
+
+The `terraform_version` value within the [root namespace](#namespace-root)
+represents the version of Terraform used to create the plan. This can be used to
+enforce a specific version of Terraform in a policy check.
+
+As an example, the following policy would evaluate to `true`, as long as the
+plan was made with a version of Terraform in the 0.11.x series, excluding any
+pre-release versions (example: `-beta1` or `-rc1`):
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.terraform_version matches "^0\\.11\\.\\d+$" }
+```
+
+### Value: `variables`
+
+* **Value Type:** A string-keyed map of values.
+
+The `variables` value within the [root namespace](#namespace-root) represents
+all of the variables that were set when creating the plan. This will only
+contain variables set for the root module.
+
+Note that unlike the [`default`][import-tfconfig-variables-default] value in the
+[`tfconfig` variables namespace][import-tfconfig-variables], primitive values
+here are stringified, and type conversion will need to be performed to perform
+comparison for int, float, or boolean values. This only applies to variables
+that are primitives themselves and not primitives within maps and lists, which
+will be their original types.
+
+[import-tfconfig-variables-default]: /sentinel/features/terraform/tfconfig-v1#value-default
+
+[import-tfconfig-variables]: /sentinel/features/terraform/tfconfig-v1#namespace-variables
+
+If a default was accepted for the particular variable, the default value will be
+populated here.
+
+As an example, given the following variable blocks:
+
+```hcl
+variable "foo" {
+ default = "bar"
+}
+
+variable "number" {
+ default = 42
+}
+
+variable "map" {
+ default = {
+ foo = "bar"
+ number = 42
+ }
+}
+```
+
+The following policy would evaluate to `true`, if no values were entered to
+change these variables:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+default_foo = rule { tfplan.variables.foo is "bar" }
+default_number = rule { tfplan.variables.number is "42" }
+default_map_string = rule { tfplan.variables.map["foo"] is "bar" }
+default_map_int = rule { tfplan.variables.map["number"] is 42 }
+
+main = rule { default_foo and default_number and default_map_string and default_map_int }
+```
+
+## Namespace: Module
+
+The **module namespace** can be loaded by calling
+[`module()`](#function-module-) for a particular module.
+
+It can be used to load the following child namespaces, in addition to the values
+documented below:
+
+* `data` - Loads the [resource namespace](#namespace-resources-data-sources),
+ filtered against data sources.
+* `resources` - Loads the [resource
+ namespace](#namespace-resources-data-sources), filtered against resources.
+
+### Root Namespace Aliases
+
+The root-level `data` and `resources` keys both alias to their corresponding
+namespaces within the module namespace, loaded for the root module. They are the
+equivalent of running `module([]).KEY`.
+
+### Value: `path`
+
+* **Value Type:** List of strings.
+
+The `path` value within the [module namespace](#namespace-module) contains the
+path of the module that the namespace represents. This is represented as a list
+of strings.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The following policy would evaluate to `true` _only_ if the diff had changes for
+that module:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.module(["foo"]).path contains "foo" }
+```
+
+## Namespace: Resources/Data Sources
+
+The **resource namespace** is a namespace _type_ that applies to both resources
+(accessed by using the `resources` namespace key) and data sources (accessed
+using the `data` namespace key).
+
+Accessing an individual resource or data source within each respective namespace
+can be accomplished by specifying the type, name, and resource number (as if the
+resource or data source had a `count` value in it) in the syntax
+`[resources|data].TYPE.NAME[NUMBER]`. Note that NUMBER is always needed, even if
+you did not use `count` in the resource.
+
+In addition, each of these namespace levels is a map, allowing you to filter
+based on type and name.
+
+-> The (somewhat strange) notation here of `TYPE.NAME[NUMBER]` may imply that
+the inner resource index map is actually a list, but it's not - using the square
+bracket notation over the dotted notation (`TYPE.NAME.NUMBER`) is required here
+as an identifier cannot start with a number.
+
+Some examples of multi-level access are below:
+
+* To fetch all `aws_instance.foo` resource instances within the root module, you
+ can specify `tfplan.resources.aws_instance.foo`. This would then be indexed by
+ resource count index (`0`, `1`, `2`, and so on). Note that as mentioned above,
+ these elements must be accessed using square-bracket map notation (so `[0]`,
+ `[1]`, `[2]`, and so on) instead of dotted notation.
+* To fetch all `aws_instance` resources within the root module, you can specify
+ `tfplan.resources.aws_instance`. This would be indexed from the names of
+ each resource (`foo`, `bar`, and so on), with each of those maps containing
+ instances indexed by resource count index as per above.
+* To fetch all resources within the root module, irrespective of type, use
+ `tfplan.resources`. This is indexed by type, as shown above with
+ `tfplan.resources.aws_instance`, with names being the next level down, and so
+ on.
+
+~> When [resource targeting](https://developer.hashicorp.com/terraform/cli/commands/plan#resource-targeting) is
+ in effect, `tfplan.resources` will only include the resources specified as
+ targets for the run. This may lead to unexpected outcomes if a policy expects
+ a resource to be present in the plan.
+
+Further explanation of the namespace will be in the context of resources. As
+mentioned, when operating on data sources, use the same syntax, except with
+`data` in place of `resources`.
+
+### Value: `applied`
+
+* **Value Type:** A string-keyed map of values.
+
+The `applied` value within the [resource
+namespace](#namespace-resources-data-sources) contains a "predicted"
+representation of the resource's state post-apply. It's created by merging the
+pending resource's diff on top of the existing data from the resource's state
+(if any). The map is a complex representation of these values with data going
+as far down as needed to represent any state values such as maps, lists, and
+sets.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if the resource was in the diff:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].applied.triggers.foo is "bar" }
+```
+
+-> Note that some values will not be available in the `applied` state because
+they cannot be known until the plan is actually applied. In Terraform 0.11 or
+earlier, these values are represented by a placeholder (the UUID value
+`74D93920-ED26-11E3-AC10-0800200C9A66`) and in Terraform 0.12 or later they
+are `undefined`. **In either case**, you should instead use the
+[`computed`](#value-computed) key within the [diff
+namespace](#namespace-resource-diff) to determine that a computed value will
+exist.
+
+-> If a resource is being destroyed, its `applied` value is omitted from the
+namespace and trying to fetch it will return undefined.
+
+### Value: `diff`
+
+* **Value Type:** A map of [diff namespaces](#namespace-resource-diff).
+
+The `diff` value within the [resource
+namespace](#namespace-resources-data-sources) contains the diff for a particular
+resource. Each key within the map links to a [diff
+namespace](#namespace-resource-diff) for that particular key.
+
+Note that unlike the [`applied`](#value-applied) value, this map is not complex;
+the map is only 1 level deep with each key possibly representing a diff for a
+particular complex value within the resource.
+
+See the below section for more details on the diff namespace, in addition to
+usage examples.
+
+### Value: `destroy`
+
+* **Value Type:** Boolean.
+
+The `destroy` value within the [resource
+namespace](#namespace-resources-data-sources) is `true` if a resource is being
+destroyed for _any_ reason, including cases where it's being deleted as part of
+a resource re-creation, in which case [`requires_new`](#value-requires_new) will
+also be set.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` when `null_resource.foo` is being
+destroyed:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].destroy }
+```
+
+### Value: `requires_new`
+
+* **Value Type:** Boolean.
+
+The `requires_new` value within the [resource
+namespace](#namespace-resources-data-sources) is `true` if the resource is still
+present in the configuration, but must be replaced to satisfy its current diff.
+Whenever `requires_new` is `true`, [`destroy`](#value-destroy) is also `true`.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if one of the `triggers` in
+`null_resource.foo` was being changed:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].requires_new }
+```
+
+## Namespace: Resource Diff
+
+The **diff namespace** is a namespace that represents the diff for a specific
+attribute within a resource. For details on reading a particular attribute,
+see the [`diff`](#value-diff) value in the [resource
+namespace](#namespace-resources-data-sources).
+
+### Value: `computed`
+
+* **Value Type:** Boolean.
+
+The `computed` value within the [diff namespace](#namespace-resource-diff) is
+`true` if the resource key in question depends on another value that isn't yet
+known. Typically, that means the value it depends on belongs to a resource that
+either doesn't exist yet, or is changing state in such a way as to affect the
+dependent value so that it can't be known until the apply is complete.
+
+-> Keep in mind that when using `computed` with complex structures such as maps,
+lists, and sets, it's sometimes necessary to test the count attribute for the
+structure, versus a key within it, depending on whether or not the diff has
+marked the whole structure as computed. This is demonstrated in the example
+below. Count keys are `%` for maps, and `#` for lists and sets. If you are
+having trouble determining the type of specific field within a resource, contact
+the support team.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+
+resource "null_resource" "bar" {
+ triggers = {
+ foo_id = "${null_resource.foo.id}"
+ }
+}
+```
+
+The following policy would evaluate to `true`, if the `id` of
+`null_resource.foo` was currently not known, such as when the resource is
+pending creation, or is being deleted and re-created:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.bar[0].diff["triggers.%"].computed }
+```
+
+### Value: `new`
+
+* **Value Type:** String.
+
+The `new` value within the [diff namespace](#namespace-resource-diff) contains
+the new value of a changing attribute, _if_ the value is known at plan time.
+
+-> `new` will be an empty string if the attribute's value is currently unknown.
+For more details on detecting unknown values, see [`computed`](#value-computed).
+
+Note that this value is _always_ a string, regardless of the actual type of the
+value changing. [Type conversion][ref-sentinel-type-conversion] within policy
+may be necessary to achieve the comparison needed.
+
+[ref-sentinel-type-conversion]: https://docs.hashicorp.com/sentinel/language/values#type-conversion
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`, if the resource was in the diff
+and each of the concerned keys were changing to new values:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].diff["triggers.foo"].new is "bar" }
+```
+
+### Value: `old`
+
+* **Value Type:** String.
+
+The `old` value within the [diff namespace](#namespace-resource-diff) contains
+the old value of a changing attribute.
+
+Note that this value is _always_ a string, regardless of the actual type of the
+value changing. [Type conversion][ref-sentinel-type-conversion] within policy
+may be necessary to achieve the comparison needed.
+
+If the value did not exist in the previous state, `old` will always be an empty
+string.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "baz"
+ }
+}
+```
+
+If that resource was previously in config as:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].diff["triggers.foo"].old is "bar" }
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/features/terraform/tfplan-v2.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/features/terraform/tfplan-v2.mdx
new file mode 100644
index 0000000000..61fb7b2bf2
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/features/terraform/tfplan-v2.mdx
@@ -0,0 +1,402 @@
+---
+page_title: tfplan/v2 - terraform - Features
+sidebar_current: docs-features-terraform-tfplan-v2
+description: >-
+ The tfplan import provides access to a Terraform plan. A Terraform plan is the
+ file created as a result of `terraform plan` and is the input to `terraform
+ apply`. The plan represents the changes that Terraform needs to make to
+ infrastructure to reach the desired state represented by the configuration.
+layout: docs
+---
+
+# Import: tfplan/v2
+
+The `tfplan/v2` import provides access to a Terraform plan.
+
+A Terraform plan is the file created as a result of `terraform plan` and is the
+input to `terraform apply`. The plan represents the changes that Terraform needs
+to make to infrastructure to reach the desired state represented by the
+configuration.
+
+In addition to the diff data available in the plan, there is a "planned state"
+that is available through this import, via the
+[`planned_values`](#the-planned_values-collection) collection. This collection
+presents the Terraform state as how it might look after the plan data is
+applied, but is not guaranteed to be the final state.
+
+The data in the `tfplan/v2` import is sourced from the JSON configuration file
+that is generated by the [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) command. For more information on
+the file format, see the [JSON Output Format](https://developer.hashicorp.com/terraform/internals/json-format)
+page.
+
+The entirety of the JSON output file is exposed as a Sentinel map via the
+[`raw`](#the-raw-collection) collection. This allows direct, low-level access to
+the JSON data, but should only be used in complex situations where the
+higher-level collections do not serve the purpose.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfplan/v2" {
+ config = {
+ "plan_path": "./path/to/plan.json"
+ }
+}
+```
+
+## Import Overview
+
+The `tfplan/v2` import is structured as a series of _collections_, keyed as a
+specific format depending on the collection.
+
+```
+tfplan/v2
+├── terraform_version (string)
+├── variables
+│ └── (indexed by name)
+│ ├── name (string)
+│ └── value (value)
+├── planned_values
+│ ├── outputs (tfstate/v2 outputs representation)
+│ └── resources (tfstate/v2 resources representation)
+├── resource_changes
+│ └── (indexed by address[:deposed])
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── index (float (number) or string)
+│ ├── provider_name (string)
+│ ├── deposed (string)
+│ └── change (change representation)
+├── resource_drift
+│ └── (indexed by address[:deposed])
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── index (float (number) or string)
+│ ├── provider_name (string)
+│ ├── deposed (string)
+│ └── change (change representation)
+├── output_changes
+│ └── (indexed by name)
+│ ├── name (string)
+│ └── change (change representation)
+└── raw (map)
+```
+
+The collections are:
+
+* [`variables`](#the-variables-collection) - The values of variables that have
+ been set in the plan itself. This collection only contains variables set in
+ the root module.
+* [`planned_values`](#the-planned_values-collection) - The state representation
+ of _planned values_, or an estimation of what the state will look like after
+ the plan is applied.
+* [`resource_changes`](#the-resource_changes-and-resource_drift-collections) - The set of change
+ operations for resources and data sources within this plan.
+* [`resource_drift`](#the-resource_changes-and-resource_drift-collections) - A description of the
+ changes Terraform detected when it compared the most recent state to the prior saved state.
+* [`output_changes`](#the-output_changes-collection) - The changes to outputs
+ within this plan. This collection only contains outputs set in the root
+ module.
+* [`raw`](#the-raw-collection) - Access to the raw plan data.
+
+These collections are specifically designed to be used with the
+[`filter`](/sentinel/language/collection-operations#filter-expression)
+quantifier expression in Sentinel, so that one can collect a list of resources
+to perform policy checks on without having to write complex discovery code. As
+an example, the following code will return all `aws_instance` resource changes,
+across all modules in the plan:
+
+```
+all_aws_instances = filter tfplan.resource_changes as _, rc {
+ rc.mode is "managed" and
+ rc.type is "aws_instance"
+}
+```
+
+You can add specific attributes to the filter to narrow the search, such as the
+module address, or the operation being performed. The following code would
+return resources in a module named `foo` only, and further narrow the search
+down to only resources that were being created:
+
+```
+all_aws_instances = filter tfplan.resource_changes as _, rc {
+ rc.module_address is "module.foo" and
+ rc.mode is "managed" and
+ rc.type is "aws_instance" and
+ rc.change.actions is ["create"]
+}
+```
+
+### Change Representation
+
+Certain collections in this import contain a _change representation_, an object
+with details about changes to a particular entity, such as a resource (within
+the [`resource_changes`](#the-resource_changes-collection) collection), or
+output (within the [`output_changes`](#the-output_changes-collection)
+collection).
+
+```
+(change representation)
+├── actions (list)
+├── before (value, or map)
+├── after (value, or map)
+└── after_unknown (boolean, or map of booleans)
+```
+
+This change representation contains the following fields:
+
+* `actions` - A list of actions being carried out for this change. The order is
+ important, for example a regular replace operation is denoted by `["delete",
+ "create"]`, but a
+ [`create_before_destroy`](https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle#create_before_destroy)
+ resource will have an operation order of `["create", "delete"]`.
+* `before` - The representation of the resource data object value before the
+ action. For create-only actions, this is unset. For no-op actions, this value
+ will be identical with `after`.
+* `after` - The representation of the resource data object value after the
+ action. For delete-only actions, this is unset. For no-op actions, this value
+ will be identical with `before`. Note that unknown values will not show up in
+ this field.
+* `after_unknown` - A deep object of booleans that denotes any values that are
+ unknown in a resource. These values were previously referred to as "computed"
+ values. If the value cannot be found in this map, then its value should be
+ available within `after`, so long as the operation supports it.
+
+#### Actions
+
+As mentioned above, actions show up within the `actions` field of a change
+representation and indicate the type of actions being performed as part of the
+change, and the order that they are being performed in.
+
+The current list of actions are as follows:
+
+* `create` - The action will create the associated entity. Depending on the
+ order this appears in, the entity may be created alongside a copy of the
+ entity before replacing it.
+* `read` - The action will read the associated entity. In practice, seeing this
+ change type should be rare, as reads generally happen before a plan is
+ executed (usually during a refresh).
+* `update` - The action will update the associated entity in a way that alters its state
+ in some way.
+* `delete` - The action will remove the associated entity, deleting any
+ applicable state and associated real resources or infrastructure.
+* `no-op` - No action will be performed on the associated entity.
+
+The `actions` field is a list, as some real-world actions are actually a
+composite of more than one primitive action. At this point in time, this
+is generally only applicable to resource replacement, in which the following
+action orders apply:
+
+* **Normal replacement:** `["delete", "create"]` - Applies to default lifecycle
+ configurations.
+* **Create-before-destroy:** `["create", "delete"]` - Applies when
+ [`create_before_destroy`](https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle#create_before_destroy)
+ is used in a lifecycle configuration.
+
+Note that, in most situations, the plan will list all "changes", including no-op
+changes. This makes filtering on change type crucial to the accurate selection
+of data if you are concerned with the state change of a particular resource.
+
+To filter on a change type, use exact list comparison. For example, the
+following example from the [Import Overview](#import-overview) filters on
+exactly the resources being created _only_:
+
+```
+all_aws_instances = filter tfplan.resource_changes as _, rc {
+ rc.module_address is "module.foo" and
+ rc.mode is "managed" and
+ rc.type is "aws_instance" and
+ rc.change.actions is ["create"]
+}
+```
+
+#### `before`, `after`, and `after_unknown`
+
+The exact attribute changes for a particular operation are outlined in the
+`before` and `after` attributes. Depending on the entity being operated on, this
+will either be a map (as with
+[`resource_changes`](#the-resource_changes-collection)) or a singular value (as
+with [`output_changes`](#the-output_changes-collection)).
+
+What you can expect in these fields varies depending on the operation:
+
+* For fresh create operations, `before` will generally be `null`, and `after`
+ will contain the data you can expect to see after the change.
+* For full delete operations, this will be reversed - `before` will contain
+ data, and `after` will be `null`.
+* Update or replace operations will have data in both fields relevant to their
+ states before and after the operation.
+* No-op operations should have identical data in `before` and `after`.
+
+For resources, if a field cannot be found in `after`, it generally means one of
+two things:
+
+* The attribute does not exist in the resource schema. Generally, known
+ attributes that do not have a value will show up as `null` or otherwise empty
+ in `after`.
+* The attribute is _unknown_, that is, it was unable to be determined at plan
+ time and will only be available after apply-time values have been able to be
+ calculated.
+
+In the latter case, there should be a value for the particular attribute in
+`after_unknown`, which can be checked to assert that the value is indeed
+unknown, versus invalid:
+
+```
+import "tfplan/v2" as tfplan
+
+no_unknown_amis = rule {
+ all filter tfplan.resource_changes as _, rc {
+ rc.module_address is "module.foo" and
+ rc.mode is "managed" and
+ rc.type is "aws_instance" and
+ rc.change.actions is ["create"]
+ } as _, rc {
+ rc.change.after_unknown.ami else false is false
+ }
+}
+```
+
+For output changes, `after_unknown` will simply be `true` if the value won't be
+known until the plan is applied.
+
+## The `terraform_version` Value
+
+The top-level `terraform_version` value in this import gives the Terraform
+version that made the plan. This can be used to do version validation.
+
+```
+import "tfplan/v2" as tfplan
+import "strings"
+
+v = strings.split(tfplan.terraform_version, ".")
+version_major = int(v[1])
+version_minor = int(v[2])
+
+main = rule {
+ version_major is 12 and version_minor >= 19
+}
+```
+
+## The `variables` Collection
+
+The `variables` collection is a collection of the variables set in the root
+module when creating the plan.
+
+This collection is indexed on the name of the variable.
+
+The valid values are:
+
+* `name` - The name of the variable, also used as the collection key.
+* `value` - The value of the variable assigned during the plan.
+
+## The `planned_values` Collection
+
+The `planned_values` collection is a special collection in that it contains two
+fields that alias to state collections with the _planned_ state set. This is the
+best prediction of what the state will look like after the plan is executed.
+
+The two fields are:
+
+* `outputs` - The prediction of what output values will look like after the
+ state is applied. For more details on the structure of this collection, see
+ the [`outputs`](/sentinel/features/terraform/tfstate-v2#the-outputs-collection) collection in the
+ [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2) documentation.
+* `resources` - The prediction of what resource values will look like after the
+ state is applied. For more details on the structure of this collection, see
+ the [`resources`](/sentinel/features/terraform/tfstate-v2#the-resources-collection) collection in the
+ [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2) documentation.
+
+-> **NOTE:** Unknown values are omitted from the `planned_values` state
+representations, regardless of whether or not they existed before. Use
+[`resource_changes`](#the-resource_changes-collection) if awareness of unknown
+data is important.
+
+## The `resource_changes` and `resource_drift` Collections
+
+The `resource_changes` and `resource_drift` collections are a set of change operations for resources
+and data sources within this plan.
+
+The `resource_drift` collection provides a description of the changes Terraform detected
+when it compared the most recent state to the prior saved state.
+
+The `resource_changes` collection includes all resources that have been found in the configuration and state,
+regardless of whether or not they are changing.
+
+~> When [resource targeting](/terraform/cli/commands/plan#resource-targeting) is in effect, the `resource_changes` collection will only include the resources specified as targets for the run. This may lead to unexpected outcomes if a policy expects a resource to be present in the plan. To prohibit targeted runs altogether, ensure [`tfrun.target_addrs`](/terraform/cloud-docs/policy-enforcement/sentinel/import/tfrun#value-target_addrs) is undefined or empty.
+
+This collection is indexed on the complete resource address as the key. If
+`deposed` is non-empty, it is appended to the end, and may look something like
+`aws_instance.foo:deposed-abc123`.
+
+An element contains the following fields:
+
+* `address` - The absolute resource address - also the key for the collection's
+ index, if `deposed` is empty.
+
+* `module_address` - The module portion of the absolute resource address.
+
+* `mode` - The resource mode, either `managed` (resources) or `data` (data
+ sources).
+
+* `type` - The resource type, example: `aws_instance` for `aws_instance.foo`.
+
+* `name` - The resource name, example: `foo` for `aws_instance.foo`.
+
+* `index` - The resource index. Can be either a number or a string.
+
+* `provider_name` - The name of the provider this resource belongs to. This
+ allows the provider to be interpreted unambiguously in the unusual situation
+ where a provider offers a resource type whose name does not start with its own
+ name, such as the `googlebeta` provider offering `google_compute_instance`.
+
+ -> **Note:** Starting with Terraform 0.13, the `provider_name` field contains the
+ _full_ source address to the provider in the Terraform Registry. Example:
+ `registry.terraform.io/hashicorp/null` for the null provider.
+
+* `deposed` - An identifier used during replacement operations, and can be used
+ to identify the exact resource being replaced in state.
+
+* `change` - The data describing the change that will be made to this resource.
+ For more details, see [Change Representation](#change-representation).
+
+## The `output_changes` Collection
+
+The `output_changes` collection is a collection of the change operations for
+outputs within this plan.
+
+Only outputs for the root module are included.
+
+This collection is indexed by the name of the output. The fields in a collection
+value are below:
+
+* `name` - The name of the output, also the index key.
+* `change` - The data describing the change that will be made to this output.
+ For more details, see [Change Representation](#change-representation).
+
+## The `raw` Collection
+
+The `raw` collection exposes the raw, unprocessed plan data.
+
+This is the same data that is produced by [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) on the plan file for the run this
+policy check is attached to.
+
+Use of this data is only recommended in expert situations where the data the
+collections present may not exactly serve the needs of the policy. For more
+information on the file format, see the [JSON Output
+Format](https://developer.hashicorp.com/terraform/internals/json-format) page.
+
+-> **NOTE:** Although designed to be relatively stable, the actual makeup for
+the JSON output format is a Terraform CLI concern and as such not managed by
+Sentinel. Use at your own risk, follow the [Terraform CLI
+project](https://github.com/hashicorp/terraform), and watch the file format
+documentation for any changes.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/features/terraform/tfstate-v1.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/features/terraform/tfstate-v1.mdx
new file mode 100644
index 0000000000..7ff2690947
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/features/terraform/tfstate-v1.mdx
@@ -0,0 +1,556 @@
+---
+page_title: tfstate/v1 - terraform - Features
+sidebar_current: docs-features-terraform-tfstate-v1
+description: The tfstate/v1 import provides access to a Terraform state.
+layout: docs
+---
+
+# Import: tfstate/v1
+
+~> **Warning:** The `tfstate/v1` import is deprecated and will be permanently removed in August 2025.
+Use the updated [tfstate/v2](/sentinel/docs/features/terraform/tfstate-v2) import as soon as possible to avoid disruptions.
+The `tfstate/v2` import offers improved functionality and is designed to better support your policy enforcement needs.
+
+The `tfstate/v1` import provides access to the Terraform state.
+
+The _state_ is the data that Terraform has recorded about a workspace at a
+particular point in its lifecycle, usually after an apply. You can read more
+general information about how Terraform uses state [here][ref-tf-state].
+
+[ref-tf-state]: https://developer.hashicorp.com/terraform/language/state
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfstate/v1" {
+ config = {
+ "path": "./path/to/plan.json"
+ }
+}
+```
+
+## Namespace Overview
+
+The following is a tree view of the import namespace. For more detail on a
+particular part of the namespace, see below.
+
+-> Note that the root-level alias keys shown here (`data`, `outputs`, `path`,
+and `resources`) are shortcuts to a [module namespace](#namespace-module) scoped
+to the root module. For more details, see the section on [root namespace
+aliases](#root-namespace-aliases).
+
+```
+tfstate/v1
+├── module() (function)
+│ └── (module namespace)
+│ ├── path ([]string)
+│ ├── data
+│ │ └── TYPE.NAME[NUMBER]
+│ │ ├── attr (map of keys)
+│ │ ├── depends_on ([]string)
+│ │ ├── id (string)
+│ │ └── tainted (boolean)
+│ ├── outputs (root module only in TF 0.12 or later)
+│ │ └── NAME
+│ │ ├── sensitive (bool)
+│ │ ├── type (string)
+│ │ └── value (value)
+│ └── resources
+│ └── TYPE.NAME[NUMBER]
+│ ├── attr (map of keys)
+│ ├── depends_on ([]string)
+│ ├── id (string)
+│ └── tainted (boolean)
+│
+├── module_paths ([][]string)
+├── terraform_version (string)
+│
+├── data (root module alias)
+├── outputs (root module alias)
+├── path (root module alias)
+└── resources (root module alias)
+```
+
+## Namespace: Root
+
+The root-level namespace consists of the values and functions documented below.
+
+In addition to this, the root-level `data`, `outputs`, `path`, and `resources`
+keys alias to their corresponding namespaces or values within the [module
+namespace](#namespace-module).
+
+### Function: `module()`
+
+```
+module = func(ADDR)
+```
+
+* **Return Type:** A [module namespace](#namespace-module).
+
+The `module()` function in the [root namespace](#namespace-root) returns the
+[module namespace](#namespace-module) for a particular module address.
+
+The address must be a list and is the module address, split on the period (`.`),
+excluding the root module.
+
+Hence, a module with an address of simply `foo` (or `root.foo`) would be
+`["foo"]`, and a module within that (so address `foo.bar`) would be read as
+`["foo", "bar"]`.
+
+[`null`][ref-null] is returned if a module address is invalid, or if the module
+is not present in the state.
+
+[ref-null]: /sentinel/language/spec#null
+
+As an example, given the following module block:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+If the module contained the following content:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if the resource was present in
+the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.module(["foo"]).resources.null_resource.foo[0].attr.triggers.foo is "bar" }
+```
+
+### Value: `module_paths`
+
+* **Value Type:** List of a list of strings.
+
+The `module_paths` value within the [root namespace](#namespace-root) is a list
+of all of the modules within the Terraform state at plan-time.
+
+Modules not present in the state will not be present here, even if they are
+present in the configuration or the diff.
+
+This data is represented as a list of a list of strings, with the inner list
+being the module address, split on the period (`.`).
+
+The root module is included in this list, represented as an empty inner list, as
+long as it is present in state.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The value of `module_paths` would be:
+
+```
+[
+ [],
+ ["foo"],
+]
+```
+
+And the following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.module_paths contains ["foo"] }
+```
+
+-> Note the above example only applies if the module is present in the state.
+
+#### Iterating Through Modules
+
+Iterating through all modules to find particular resources can be useful. This
+[example][iterate-over-modules] shows how to use `module_paths` with the
+[`module()` function](#function-module-) to find all resources of a
+particular type from all modules using the `tfplan` import. By changing `tfplan`
+in this function to `tfstate`, you could make a similar function find all
+resources of a specific type in the current state.
+
+[iterate-over-modules]: https://developer.hashicorp.com/terraform/cloud-docs/policy-enforcement/sentinel#sentinel-imports
+
+### Value: `terraform_version`
+
+* **Value Type:** String.
+
+The `terraform_version` value within the [root namespace](#namespace-root)
+represents the version of Terraform in use when the state was saved. This can be
+used to enforce a specific version of Terraform in a policy check.
+
+As an example, the following policy would evaluate to `true` as long as the
+state was made with a version of Terraform in the 0.11.x series, excluding any
+pre-release versions (example: `-beta1` or `-rc1`):
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.terraform_version matches "^0\\.11\\.\\d+$" }
+```
+
+-> **NOTE:** This value is also available via the [`tfplan`](/sentinel/features/terraform/tfplan-v1)
+import, which will be more current when a policy check is run against a plan.
+It's recommended you use the value in `tfplan` until HCP Terraform
+supports policy checks in other stages of the workspace lifecycle. See the
+[`terraform_version`][import-tfplan-terraform-version] reference within the
+`tfplan` import for more details.
+
+[import-tfplan-terraform-version]: /sentinel/features/terraform/tfplan-v1#value-terraform_version
+
+## Namespace: Module
+
+The **module namespace** can be loaded by calling
+[`module()`](#function-module-) for a particular module.
+
+It can be used to load the following child namespaces, in addition to the values
+documented below:
+
+* `data` - Loads the [resource namespace](#namespace-resources-data-sources),
+ filtered against data sources.
+* `outputs` - Loads the [output namespace](#namespace-outputs), which supply the
+ outputs present in this module's state. Note that with Terraform 0.12 or
+ later, this value is only available for the root namespace.
+* `resources` - Loads the [resource
+ namespace](#namespace-resources-data-sources), filtered against resources.
+
+### Root Namespace Aliases
+
+The root-level `data`, `outputs`, and `resources` keys both alias to their
+corresponding namespaces within the module namespace, loaded for the root
+module. They are the equivalent of running `module([]).KEY`.
+
+### Value: `path`
+
+* **Value Type:** List of strings.
+
+The `path` value within the [module namespace](#namespace-module) contains the
+path of the module that the namespace represents. This is represented as a list
+of strings.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The following policy would evaluate to `true`, _only_ if the module was present
+in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.module(["foo"]).path contains "foo" }
+```
+
+## Namespace: Resources/Data Sources
+
+The **resource namespace** is a namespace _type_ that applies to both resources
+(accessed by using the `resources` namespace key) and data sources (accessed
+using the `data` namespace key).
+
+Accessing an individual resource or data source within each respective namespace
+can be accomplished by specifying the type, name, and resource number (as if the
+resource or data source had a `count` value in it) in the syntax
+`[resources|data].TYPE.NAME[NUMBER]`. Note that NUMBER is always needed, even if
+you did not use `count` in the resource.
+
+In addition, each of these namespace levels is a map, allowing you to filter
+based on type and name.
+
+-> The (somewhat strange) notation here of `TYPE.NAME[NUMBER]` may imply that
+the inner resource index map is actually a list, but it's not - using the square
+bracket notation over the dotted notation (`TYPE.NAME.NUMBER`) is required here
+as an identifier cannot start with number.
+
+Some examples of multi-level access are below:
+
+* To fetch all `aws_instance.foo` resource instances within the root module, you
+ can specify `tfstate.resources.aws_instance.foo`. This would then be indexed
+ by resource count index (`0`, `1`, `2`, and so on). Note that as mentioned
+ above, these elements must be accessed using square-bracket map notation (so
+ `[0]`, `[1]`, `[2]`, and so on) instead of dotted notation.
+* To fetch all `aws_instance` resources within the root module, you can specify
+ `tfstate.resources.aws_instance`. This would be indexed from the names of
+ each resource (`foo`, `bar`, and so on), with each of those maps containing
+ instances indexed by resource count index as per above.
+* To fetch all resources within the root module, irrespective of type, use
+ `tfstate.resources`. This is indexed by type, as shown above with
+ `tfstate.resources.aws_instance`, with names being the next level down, and so
+ on.
+
+Further explanation of the namespace will be in the context of resources. As
+mentioned, when operating on data sources, use the same syntax, except with
+`data` in place of `resources`.
+
+### Value: `attr`
+
+* **Value Type:** A string-keyed map of values.
+
+The `attr` value within the [resource
+namespace](#namespace-resources-data-sources) is a direct mapping to the state
+of the resource.
+
+The map is a complex representation of these values with data going as far down
+as needed to represent any state values such as maps, lists, and sets.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if the resource was in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.resources.null_resource.foo[0].attr.triggers.foo is "bar" }
+```
+
+### Value: `depends_on`
+
+* **Value Type:** A list of strings.
+
+The `depends_on` value within the [resource
+namespace](#namespace-resources-data-sources) contains the dependencies for the
+resource.
+
+This is a list of full resource addresses, relative to the module (example:
+`null_resource.foo`).
+
+As an example, given the following resources:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+
+resource "null_resource" "bar" {
+ # ...
+
+ depends_on = [
+ "null_resource.foo",
+ ]
+}
+```
+
+The following policy would evaluate to `true` if the resource was in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.resources.null_resource.bar[0].depends_on contains "null_resource.foo" }
+```
+
+### Value: `id`
+
+* **Value Type:** String.
+
+The `id` value within the [resource
+namespace](#namespace-resources-data-sources) contains the id of the resource.
+
+-> **NOTE:** The example below uses a _data source_ here because the
+[`null_data_source`][ref-tf-null-data-source] data source gives a static ID,
+which makes documenting the example easier. As previously mentioned, data
+sources share the same namespace as resources, but need to be loaded with the
+`data` key. For more information, see the
+[synopsis](#namespace-resources-data-sources) for the namespace itself.
+
+[ref-tf-null-data-source]: https://registry.terraform.io/providers/hashicorp/null/latest/docs/data-sources/data_source
+
+As an example, given the following data source:
+
+```hcl
+data "null_data_source" "foo" {
+ # ...
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.data.null_data_source.foo[0].id is "static" }
+```
+
+### Value: `tainted`
+
+* **Value Type:** Boolean.
+
+The `tainted` value within the [resource
+namespace](#namespace-resources-data-sources) is `true` if the resource is
+marked as tainted in Terraform state.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`, if the resource was marked as
+tainted in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.resources.null_resource.foo[0].tainted }
+```
+
+## Namespace: Outputs
+
+The **output namespace** represents all of the outputs present within a
+[module](#namespace-module). Outputs are present in a state if they were saved
+during a previous apply, or if they were updated with known values during the
+pre-plan refresh.
+
+**With Terraform 0.11 or earlier** this can be used to fetch both the outputs
+of the root module, and the outputs of any module in the state below the root.
+This makes it possible to see outputs that have not been threaded to the root
+module.
+
+**With Terraform 0.12 or later** outputs are available in the top-level (root
+module) namespace only and not accessible within submodules.
+
+This namespace is indexed by output name.
+
+### Value: `sensitive`
+
+* **Value Type:** Boolean.
+
+The `sensitive` value within the [output namespace](#namespace-outputs) is
+`true` when the output has been [marked as sensitive][ref-tf-sensitive-outputs].
+
+[ref-tf-sensitive-outputs]: https://developer.hashicorp.com/terraform/language/values/outputs#sensitive-suppressing-values-in-cli-output
+
+As an example, given the following output:
+
+```hcl
+output "foo" {
+ sensitive = true
+ value = "bar"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.outputs.foo.sensitive }
+```
+
+### Value: `type`
+
+* **Value Type:** String.
+
+The `type` value within the [output namespace](#namespace-outputs) gives the
+output's type. This will be one of `string`, `list`, or `map`. These are
+currently the only types available for outputs in Terraform.
+
+As an example, given the following output:
+
+```hcl
+output "string" {
+ value = "foo"
+}
+
+output "list" {
+ value = [
+ "foo",
+ "bar",
+ ]
+}
+
+output "map" {
+ value = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+type_string = rule { tfstate.outputs.string.type is "string" }
+type_list = rule { tfstate.outputs.list.type is "list" }
+type_map = rule { tfstate.outputs.map.type is "map" }
+
+main = rule { type_string and type_list and type_map }
+```
+
+### Value: `value`
+
+* **Value Type:** String, list, or map.
+
+The `value` value within the [output namespace](#namespace-outputs) is the value
+of the output in question.
+
+Note that the only valid primitive output type in Terraform is currently a
+string, which means that any int, float, or boolean value will need to be
+converted before it can be used in comparison. This does not apply to primitives
+within maps and lists, which will be their original types.
+
+As an example, given the following output blocks:
+
+```hcl
+output "foo" {
+ value = "bar"
+}
+
+output "number" {
+ value = "42"
+}
+
+output "map" {
+ value = {
+ foo = "bar"
+ number = 42
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+value_foo = rule { tfstate.outputs.foo.value is "bar" }
+value_number = rule { int(tfstate.outputs.number.value) is 42 }
+value_map_string = rule { tfstate.outputs.map.value["foo"] is "bar" }
+value_map_int = rule { tfstate.outputs.map.value["number"] is 42 }
+
+main = rule { value_foo and value_number and value_map_string and value_map_int }
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/features/terraform/tfstate-v2.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/features/terraform/tfstate-v2.mdx
new file mode 100644
index 0000000000..9b29aa2c51
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/features/terraform/tfstate-v2.mdx
@@ -0,0 +1,176 @@
+---
+page_title: tfstate/v2 - terraform - Features
+sidebar_current: docs-features-terraform-tfstate-v2
+description: The tfstate/v2 import provides access to a Terraform state.
+layout: docs
+---
+
+# Import: tfstate/v2
+
+The `tfstate/v2` import provides access to a Terraform state.
+
+The _state_ is the data that Terraform has recorded about a workspace at a
+particular point in its lifecycle, usually after an apply. You can read more
+general information about how Terraform uses state
+[here](https://developer.hashicorp.com/terraform/language/state).
+
+The data in the `tfstate/v2` import is sourced from the JSON configuration file
+that is generated by the [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) command. For more information on
+the file format, see the [JSON Output Format](https://developer.hashicorp.com/terraform/internals/json-format)
+page.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfstate/v2" {
+ config = {
+ "path": "./path/to/plan.json"
+ }
+}
+```
+
+## Import Overview
+
+The `tfstate/v2` import is structured as currently two _collections_, keyed in
+resource address and output name, respectively.
+
+```
+(tfstate/v2)
+├── terraform_version (string)
+├── resources
+│ └── (indexed by address)
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── index (float (number) or string)
+│ ├── provider_name (string)
+│ ├── values (map)
+│ ├── depends_on (list of strings)
+│ ├── tainted (boolean)
+│ └── deposed_key (string)
+└── outputs
+ └── (indexed by name)
+ ├── name (string)
+ ├── sensitive (boolean)
+ └── value (value)
+```
+
+The collections are:
+
+* [`resources`](#the-resources-collection) - The state of all resources across
+ all modules in the state.
+* [`outputs`](#the-outputs-collection) - The state of all outputs from the root module in the state.
+
+These collections are specifically designed to be used with the
+[`filter`](/sentinel/language/collection-operations#filter-expression)
+quantifier expression in Sentinel, so that one can collect a list of resources
+to perform policy checks on without having to write complex module traversal. As
+an example, the following code will return all `aws_instance` resource types
+within the state, regardless of what module they are in:
+
+```
+all_aws_instances = filter tfstate.resources as _, r {
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+You can add specific attributes to the filter to narrow the search, such as the
+module address. The following code would return resources in a module named
+`foo` only:
+
+```
+all_aws_instances = filter tfstate.resources as _, r {
+ r.module_address is "module.foo" and
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+## The `terraform_version` Value
+
+The top-level `terraform_version` value in this import gives the Terraform
+version that recorded the state. This can be used to do version validation.
+
+```
+import "tfstate/v2" as tfstate
+import "strings"
+
+v = strings.split(tfstate.terraform_version, ".")
+version_major = int(v[1])
+version_minor = int(v[2])
+
+main = rule {
+ version_major is 12 and version_minor >= 19
+}
+```
+
+## The `resources` Collection
+
+The `resources` collection is a collection representing all of the resources in
+the state, across all modules.
+
+This collection is indexed on the complete resource address as the key.
+
+An element in the collection has the following values:
+
+* `address` - The absolute resource address - also the key for the collection's
+ index.
+
+* `module_address` - The address portion of the absolute resource address.
+
+* `mode` - The resource mode, either `managed` (resources) or `data` (data
+ sources).
+
+* `type` - The resource type, example: `aws_instance` for `aws_instance.foo`.
+
+* `name` - The resource name, example: `foo` for `aws_instance.foo`.
+
+* `index` - The resource index. Can be either a number or a string.
+
+* `provider_name` - The name of the provider this resource belongs to. This
+ allows the provider to be interpreted unambiguously in the unusual situation
+ where a provider offers a resource type whose name does not start with its own
+ name, such as the `googlebeta` provider offering `google_compute_instance`.
+
+ -> **Note:** Starting with Terraform 0.13, the `provider_name` field contains the
+ _full_ source address to the provider in the Terraform Registry. Example:
+ `registry.terraform.io/hashicorp/null` for the null provider.
+
+* `values` - An object (map) representation of the attribute values of the
+ resource, whose structure depends on the resource type schema. When accessing
+ proposed state through the [`planned_values`](/sentinel/features/terraform/tfplan-v2#the-planned_values-collection)
+ collection of the tfplan/v2 import, unknown values will be omitted.
+
+* `depends_on` - The addresses of the resources that this resource depends on.
+
+* `tainted` - `true` if the resource has been explicitly marked as
+ [tainted](https://developer.hashicorp.com/terraform/cli/commands/taint) in the state.
+
+* `deposed_key` - Set if the resource has been marked deposed and will be
+ destroyed on the next apply. This matches the deposed field in the
+ [`resource_changes`](/sentinel/features/terraform/tfplan-v2#the-resource_changes-collection)
+ collection in the [`tfplan/v2`](/sentinel/features/terraform/tfplan-v2) import.
+
+## The `outputs` Collection
+
+The `outputs` collection is a collection of outputs from the root module of the
+state.
+
+Note that no child modules are included in this output set, and there is no way
+to fetch child module output values. This is to encourage the correct flow of
+outputs to the recommended root consumption level.
+
+The collection is indexed on the output name, with the following fields:
+
+* `name`: The name of the output, also the collection key.
+* `sensitive`: Whether or not the value was marked as
+ [sensitive](https://developer.hashicorp.com/terraform/language/values/outputs#sensitive-suppressing-values-in-cli-output)
+ in
+ configuration.
+* `value`: The value of the output.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/functions/append.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/functions/append.mdx
new file mode 100644
index 0000000000..81395154f6
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/functions/append.mdx
@@ -0,0 +1,46 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Append'
+sidebar_current: docs-funcs-append
+description: The built-in function `append` appends a value to the end of a list.
+layout: docs
+---
+
+# Builtin Function: append
+
+The built-in function `append` appends a value to the end of a list. The list
+is modified in-place. The return value will always be [undefined](/sentinel/language/undefined).
+
+`append` called on any value other than a list or undefined will result
+in an immediate error.
+
+Examples:
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+### Why does this function return undefined?
+
+This function returns `undefined` to avoid unintended side effects of the function in the context
+of a [rule](/sentinel/language/rules).
+
+For example, if `append` returned the list value as a result, the following policy would depend
+on the order in which rules are referenced:
+
+```sentinel
+list = []
+a = rule { append(list, 1) contains 1 }
+b = rule { append(list, 2) contains 2 }
+
+// Scenario 1: list becomes [1, 2]
+main = rule { a and b }
+
+// Scenario 2: list becomes [2, 1]
+main = rule { b and a }
+```
+
+This sort of side effect behavior introduces complex and difficult to track logical errors in programs.
+As a result, functions like this return `undefined` and modify values in-place.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/functions/compare.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/functions/compare.mdx
new file mode 100644
index 0000000000..ed55775454
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/functions/compare.mdx
@@ -0,0 +1,42 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Compare'
+sidebar_current: docs-funcs-compare
+description: The built-in function `compare` compares two values.
+layout: docs
+---
+
+# Builtin Function: compare
+
+**_compare(value1, value2)_**
+
+The built-in function `compare` compares two values. The only valid types that
+can be provided are integers, floats or strings. Strings are compared according
+to lexicographic ordering; which is comparable to alphabetical
+ordering, but also takes into account symbols.
+
+The following table provides an overview of the possible return values:
+
+| Result | Description |
+|--------|-------------|
+| -1 | `value1` is less than `value2` |
+| 0 | `value1` is equal to `value2` |
+| +1 | `value1` is greater than `value2` |
+
+## Examples
+
+```sentinel
+// ints
+compare(1, 4) // -1
+compare(1, 4) // 0
+compare(4, 1) // +1
+
+// floats
+compare(1.0, 4.0) // -1
+compare(1.0, 4.0) // 0
+compare(4.0, 1.0) // +1
+
+// strings
+compare("apple", "banana") // -1
+compare("apple", "apple") // 0
+compare("banana", "apple") // +1
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/functions/delete.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/functions/delete.mdx
new file mode 100644
index 0000000000..3b60803997
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/functions/delete.mdx
@@ -0,0 +1,25 @@
+---
+page_title: 'Sentinel Language - Builtin Function: delete'
+sidebar_current: docs-funcs-delete
+description: The built-in function `delete` deletes an element from a map.
+layout: docs
+---
+
+# Builtin Function: delete
+
+The built-in function `delete` deletes an element from a map.
+
+If the element to delete does not exist, the map is left unchanged.
+Calling `delete` on a value that is not a map or
+[undefined](/sentinel/language/undefined)
+is an immediate error. The result of `delete` is always `undefined`.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/functions/error.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/functions/error.mdx
new file mode 100644
index 0000000000..96d253b662
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/functions/error.mdx
@@ -0,0 +1,15 @@
+---
+page_title: 'Sentinel Language - Builtin Function: error'
+sidebar_current: docs-funcs-error
+description: >-
+ The built-in function `error` is used to exit immediately with an error
+ message.
+layout: docs
+---
+
+# Builtin Function: error
+
+The built-in function `error` is used to exit immediately with an error message.
+
+Please see the [page on errors](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/functions/index.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/functions/index.mdx
new file mode 100644
index 0000000000..4e12143ad0
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/functions/index.mdx
@@ -0,0 +1,13 @@
+---
+page_title: Sentinel Language - Builtin Functions
+sidebar_current: docs-funcs
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Language: Builtin Functions
+
+Builtin functions are
+[functions](/sentinel/language/functions)
+that are available in all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in function.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/functions/keys.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/functions/keys.mdx
new file mode 100644
index 0000000000..a96cafe897
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/functions/keys.mdx
@@ -0,0 +1,22 @@
+---
+page_title: 'Sentinel Language - Builtin Function: keys'
+sidebar_current: docs-funcs-keys
+description: The built-in function `keys` returns the keys of a map.
+layout: docs
+---
+
+# Builtin Function: keys
+
+The built-in function `keys` returns the keys of a map.
+
+`keys` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned keys are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/functions/length.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/functions/length.mdx
new file mode 100644
index 0000000000..8b874b14c4
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/functions/length.mdx
@@ -0,0 +1,23 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Length'
+sidebar_current: docs-funcs-length
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Builtin Function: length
+
+The built-in function `length` returns the length of a collection or string.
+
+The length of a string is the number of bytes in the string. It is not
+necessarilly the number of characters. The length of a collection is the
+number of elements in that collection. The length of
+[undefined](/sentinel/language/undefined) is `undefined`.
+
+Examples:
+
+```sentinel
+length("foo") // 3
+length([1, 2, 3, 4]) // 4
+length({ "key": "value" }) // 1
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/functions/print.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/functions/print.mdx
new file mode 100644
index 0000000000..bcf441d09d
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/functions/print.mdx
@@ -0,0 +1,13 @@
+---
+page_title: 'Sentinel Language - Builtin Function: print'
+sidebar_current: docs-funcs-print
+description: The built-in function `print` is used for logging and debugging.
+layout: docs
+---
+
+# Builtin Function: print
+
+The built-in function `print` is used for logging and debugging.
+
+Please see the [page on logging](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/functions/range.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/functions/range.mdx
new file mode 100644
index 0000000000..0f86bfc402
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/functions/range.mdx
@@ -0,0 +1,49 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Range'
+sidebar_current: docs-funcs-range
+description: The built-in function `range` returns a list of numbers in a range.
+layout: docs
+---
+
+# Builtin Function: range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it
+defaults to `1`.
+
+Negative steps are permitted and count down from `start` towards `end`, instead
+of up.
+
+The range ends when the next value given by the sum of the last value and `step`
+would exceed `end`, regardless of if it has been reached.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4] - starts at 1 instead of 0
+range(1, 5, 2) // [1,3] - 3+2 does not satisfy r[-1] < end
+range(0, -3, -1) // [0,-1,-2] - example of negative range
+```
+
+Impossible ranges, or ranges where `start` will never reach `end` given `step`,
+yield an empty list.
+
+```
+range(1, 1) // [] - already starting at 1
+range(0, 1, -1) // [] - negative step will never reach 1
+```
+
+Supplying an undefined value to any argument of `range` yields an undefined
+value back.
+
+A range with a zero step is a runtime error.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/functions/values.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/functions/values.mdx
new file mode 100644
index 0000000000..8f98d3ab56
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/functions/values.mdx
@@ -0,0 +1,22 @@
+---
+page_title: 'Sentinel Language - Builtin Function: values'
+sidebar_current: docs-funcs-values
+description: The built-in function `values` returns the values of a map.
+layout: docs
+---
+
+# Builtin Function: values
+
+The built-in function `values` returns the values of a map.
+
+`values` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned values are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+values(data) // [3, 2]
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/imports/base64.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/imports/base64.mdx
new file mode 100644
index 0000000000..4e58429268
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/imports/base64.mdx
@@ -0,0 +1,46 @@
+---
+page_title: 'Import: base64'
+sidebar_current: docs-imports-base64
+description: The base64 import enables a Sentinel policy to encode and decode Base64 values.
+layout: docs
+---
+
+# Import: base64
+
+The base64 import enables a Sentinel policy to encode and decode Base64 values.
+
+### base64.encode(str)
+
+Encodes the string `str` into a Base64 encoded string, using the standard
+encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-4).
+
+```sentinel
+enc = base64.encode("foo") // "Zm9v"
+```
+
+### base64.decode(str)
+
+Decodes the Base64 encoded string `str`, returning the string result,
+using the standard encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-4).
+
+```sentinel
+dec = base64.decode("Zm9v") // "foo"
+```
+
+### base64.urlencode(str)
+
+Encodes the string `str` into a Base64 encoded string, using the alternate
+encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-5).
+
+```sentinel
+enc = base64.urlencode("foo") // "Zm9v"
+```
+
+### base64.urldecode(str)
+
+Decodes the Base64 encoded string `str`, returning the string result, using the
+alternate encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-5).
+
+```sentinel
+dec = base64.urldecode("Zm9v") // "foo"
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/imports/collection/index.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/imports/collection/index.mdx
new file mode 100644
index 0000000000..ae6db20bbb
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/imports/collection/index.mdx
@@ -0,0 +1,207 @@
+---
+page_title: 'Import: collection'
+sidebar_current: docs-imports-collection
+description: The collection import provides useful helpers for working with maps and lists.
+layout: docs
+---
+
+# Import: collection
+
+The `collection` import provides helpers for working with [maps](/sentinel/language/maps) and [lists](/sentinel/language/lists).
+
+## filter
+
+**_filter(items, predicate)_**
+
+Calls [predicate](#predicates) for each element in collection, returning a list of elements that the
+predicate __does__ return true for.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| items | the list or map to perform the filter against. |
+| predicate | a [predicate](#predicates) function to call for each element in the collection. A response of `true` will add the element to the result list. |
+
+### Examples
+
+Return all even numbers:
+
+```sentinel playground
+import "collection"
+
+items = [2, 3, 4, 5, 6, 7, 8]
+result = collection.filter(items, func(el) {
+ return el % 2 is 0
+})
+main = result is [2, 4, 6, 8]
+```
+
+## find
+
+**_find(items, predicate)_**
+
+Find and return an element within a collection according to the provided [predicate](#predicates). If nothing is found, returns [undefined](/sentinel/language/undefined).
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| items | the list or map to perform the find against. |
+| predicate | a [predicate](#predicates) function to call for each element in the collection. A response of `true` will return the element and complete the find. |
+
+### Examples
+
+Find an element in a collection based on its value:
+
+```sentinel playground
+import "collection"
+
+items = ["foo", "bar", "qux"]
+item = collection.find(items, func(el) {
+ return el is "bar"
+})
+main = item is "bar"
+```
+
+Find an element in a collection based on its key:
+
+```sentinel playground
+import "collection"
+
+items = {"foo": 2, "bar": 4}
+item = collection.find(items, func(el, key) {
+ return key is "bar"
+})
+main = item is 4
+```
+
+## matches
+
+**_matches(items, partial)_**
+
+Compare each element in a collection against a partial map using deep comparison. Returns
+the list of elements that returned true for the partial comparison. If no matches are found, returns an empty list.
+
+### Arguments
+
+| Name | Description |
+|---------|-------------|
+| items | the list or map to perform the match against. |
+| partial | the map used for partial deep comparison against each element in the collection. |
+
+### Examples
+
+Return all items that contain `{"foo": {"bar": "wip"}}`:
+
+```sentinel playground
+import "collection"
+
+items = [
+ # This item should match
+ {
+ "foo": { "bar": "wip"},
+ "baz": "qux",
+ },
+ # This item will not match
+ {
+ "foo": "bar",
+ "baz": "bar",
+ },
+]
+result = collection.matches(items, {"foo": {"bar": "wip"}})
+main = result is [{"foo": {"bar": "wip"}, "baz": "qux"}]
+```
+
+## reduce
+
+**_reduce(items, accumulator[, initial])_**
+
+Call an accumulator function for each element in collection, supplying the
+previous accumulated value as the accumulation parameter.
+
+### Arguments
+
+| Name | Description |
+|-------------|-------------|
+| items | the list or map to perform the reduce against. |
+| accumulator | a function that is called for each element in the collection, used to accumulate the value. The first argument is the current accumulated value, the remaining arguments use the same rules as [predicate](#predicates) functions. |
+| initial | the initial value to use. It is an optional argument, and if not provided the first element in the collection is used as the initial value. |
+
+### Examples
+
+Reduce the collection by adding each element together:
+
+```sentinel playground
+import "collection"
+
+items = [1, 2, 3, 4, 5]
+result = collection.reduce(items, func(acc, el) {
+ return acc + el
+}, 0)
+main = result is 15
+```
+
+## reject
+
+**_reject(items, predicate)_**
+
+Calls [predicate](#predicates) for each element in collection, returning a list of elements that the
+predicate __does not__ return true for.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| items | the list or map to perform the reject against. |
+| predicate | a [predicate](#predicates) function to call for each element in the collection. A response of `false` will add the element to the result list. |
+
+### Examples
+
+Return all odd numbers by rejecting even ones:
+
+```sentinel playground
+import "collection"
+
+items = [2, 3, 4, 5, 6, 7, 8]
+result = collection.reject(items, func(el) {
+ return el % 2 is 0
+})
+main = result is [3, 5, 7]
+```
+
+## Predicates
+
+Some helpers accept a predicate function, which has the purpose of making an
+assertion. Each predicate may accept differing arguments and return differing
+types. If the collection is a list, the parameters will be the element and index.
+If the collection is a Map, the parameters will be the value and the key.
+
+### Examples
+
+A predicate for a list of items:
+
+```sentinel
+// including index
+func(item, index) {
+ return true
+}
+
+// excluding index
+func(item) {
+ return true
+}
+```
+
+A predicate for a map of items:
+```sentinel
+// including key
+func(value, key) {
+ return true
+}
+
+// excluding key
+func(value) {
+ return true
+}
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/imports/collection/lists.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/imports/collection/lists.mdx
new file mode 100644
index 0000000000..cf258a3956
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/imports/collection/lists.mdx
@@ -0,0 +1,211 @@
+---
+page_title: 'Import: collection/lists'
+sidebar_current: docs-imports-collection-lists
+description: The collection/lists import provides useful helpers for working with lists.
+layout: docs
+---
+
+# Import: collection/lists
+
+The `collection/lists` import provides helpers for working with [lists](/sentinel/language/lists).
+
+## concat
+
+**_concat(items, others[, ...additional])_**
+
+Join multiple lists together, returning the resulting list. This helper must
+have at least two arguments supplied. Order is important,
+as the order of arguments is the order that lists will be appended.
+
+### Arguments
+
+| Name | Description |
+|------------|-------------|
+| items | the first list to add to the resulting value |
+| others | the second list to add to the resulting value |
+| additional | any number of additional lists to add to the resulting value |
+
+### Examples
+
+Concatenate two lists:
+
+```sentinel playground
+import "collection/lists" as lists
+
+result = lists.concat([1], [2, 3])
+main = result is [1, 2, 3]
+```
+
+Concatenate many lists:
+```sentinel playground
+import "collection/lists" as lists
+
+result = lists.concat([1, 2], [10, 20], [100, 200])
+main = result is [1, 2, 10, 20, 100, 200]
+```
+
+## difference
+
+**_difference(items[, ...additional])_**
+
+Difference returns a new list with all values that exist in the first list
+that are not present in all remaining provided lists.
+
+### Arguments
+
+| Name | Description |
+|------------|-------------|
+| items | the first list, used as the source for ordering |
+| additional | any number of additional lists used to subtract from the source list |
+
+### Aliases
+
+__diff__
+
+### Examples
+
+Difference between two lists:
+
+```sentinel playground
+import "collection/lists" as lists
+
+result = lists.difference([1, 2], [2, 3])
+main = result is [1]
+```
+
+Difference between many lists:
+```sentinel playground
+import "collection/lists" as lists
+
+result = lists.difference([1, 2, 3, 4, 5], [2, 5], [1])
+main = result is [3, 4]
+```
+
+## intersection
+
+**_intersection(items[, ...additional])_**
+
+Intersection returns a list consisting of unique values that are present in all
+of the provided lists.
+
+### Arguments
+
+| Name | Description |
+|------------|-------------|
+| items | the first list to use for intersection |
+| additional | any number of additional lists used to intersect |
+
+### Examples
+
+Intersection between three lists:
+
+```sentinel playground
+import "collection/lists" as lists
+
+result = lists.intersection([1, 2], [2, 3], [3, 2])
+main = result is [2]
+```
+
+## sort
+
+**_sort(items, sort_function)_**
+
+Sort will sort a list of elements according to the provided sort function, returning
+a new, sorted list.
+
+### Arguments
+
+| Name | Description |
+|------|-------------|
+| items | the list to sort |
+| sort_function | the function that is used to perform the sort |
+
+### Sort Function
+
+The provided sort function accepts two arguments, which can be considered as the
+`current` and `next` item. The function should return one of the following values:
+
+| Result | Description |
+|--------|-------------|
+| -1 | `current` is less than `next` |
+| 0 | `current` is equal to `next` |
+| +1 | `current` is greater than `next` |
+
+The [compare](/sentinel/functions/compare) built-in method provides a way of
+performing comparisons against integers, floats or strings to return a supported
+value.
+
+### Examples
+
+Sort a list of words:
+
+```sentinel playground
+import "collection/lists" as lists
+
+items = ["zebra", "bat", "horse"]
+result = lists.sort(items, func(x, y) {
+ return compare(x, y)
+})
+main = result is ["bat", "horse", "zebra"]
+```
+
+Sort a list of objects by a key:
+
+```sentinel playground
+import "collection/lists" as lists
+
+items = [{"foo": 8}, {"foo": 4}, {"foo": 100}]
+result = lists.sort(items, func(x, y) {
+ return compare(x.foo, y.foo)
+})
+main = result is [{"foo": 4}, {"foo": 8}, {"foo": 100}]
+```
+
+## sum
+
+**_sum(items)_**
+
+Sum will add all elements within the provided list. The
+list must only contain integers or floats. The return value will always be a float.
+
+### Arguments
+
+| Name | Description |
+|------|-------------|
+| items | the list to sum |
+
+### Examples
+
+Perform a sum on a list of numbers:
+
+```sentinel playground
+import "collection/lists" as lists
+
+items = [2, 5, 10, 500]
+main = lists.sum(items) is 517
+```
+
+## union
+
+**_union(items[, ...additional])_**
+
+Union will return a list that contains unique values from all provided lists.
+
+### Arguments
+
+| Name | Description |
+|------------|-------------|
+| items | the first list of values |
+| additional | any number of additional lists of values |
+
+### Examples
+
+Perform a union on multiple lists:
+
+```sentinel playground
+import "collection/lists" as lists
+
+items = lists.union([1, 3], [2, 3, 5], [6, 2, 1])
+
+main = items is [1, 3, 2, 5, 6]
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/imports/collection/maps.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/imports/collection/maps.mdx
new file mode 100644
index 0000000000..d96bb15639
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/imports/collection/maps.mdx
@@ -0,0 +1,360 @@
+---
+page_title: 'Import: collection/maps'
+sidebar_current: docs-imports-collection-maps
+description: The collection/maps import provides useful helpers for working with maps.
+layout: docs
+---
+
+# Import: collection/maps
+
+The `collection/maps` import provides helpers for working with [maps](/sentinel/language/maps).
+
+## get
+
+**_get(object, path[, default])_**
+
+Get the value from the provided object using the [path](#paths).
+When the path is invalid or the object doesn't contain a value at the path, then the default is returned. The default return value is undefined.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the get against. |
+| path | a [path](#paths) to a key within object to retreive the value |
+| default | an optional value that will return if the path does not exist or returns undefined |
+
+### Examples
+
+Get a value from a simple object:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+item = maps.get(object, "foo")
+main = item is "bar"
+```
+
+Get a nested value from a complex object:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [{"bar": {"baz": 4}}]}
+item = maps.get(object, "foo.0.bar.baz")
+main = item is 4
+```
+
+Get a list of values using [splat](#splat):
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [{"bar": 4}, {"bar": 8}, {"bar": 45}]}
+item = maps.get(object, "foo.*.bar")
+main = item is [4, 8, 45]
+```
+
+Get an invalid path, providing a default:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {}
+item = maps.get(object, "foo", "bar")
+main = item is "bar"
+```
+
+Get an invalid path, not providing a default:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {}
+item = maps.get(object, "foo")
+main = item is not defined
+```
+
+## has
+
+**_has(object, path)_**
+
+Return a boolean value if the object has the path within its structure.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the has against. |
+| path | a [path](#paths) to a key within object to determine if it exists |
+
+### Examples
+
+An object has a valid key:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+main = maps.has(object, "foo")
+```
+
+An object does not have a valid key:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {}
+main = maps.has(object, "foo") is false
+```
+
+## omit
+
+**_omit(object, paths)_**
+
+Return a map, removing each path from the paths provided from the source object.
+The original structure of the map is maintained, as seen in the below examples.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the omit against. |
+| paths | a list of [paths](#paths) to be removed from the source object |
+
+### Examples
+
+Omitting a single value:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+main = maps.omit(object, ["foo"]) is {}
+```
+
+Omitting multiple values:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar", "baz": "qux", "nit": "nat"}
+main = maps.omit(object, ["foo", "baz"]) is {"nit": "nat"}
+```
+
+Omitting deep values:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {
+ "foo": "bar",
+ "baz": [
+ { "qux": 4 },
+ { "qux": 10 }
+ ],
+ "nit": {
+ "nat": "pak"
+ }
+}
+main = maps.omit(object, ["baz.1.qux", "nit.nat"]) is {"foo": "bar", "baz": [{"qux": 4}]}
+```
+
+## pick
+
+**_pick(object, paths)_**
+
+Return a map consisting of each value found in object from the paths provided.
+The original structure of the map is maintained, as seen in the below examples.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the pick against. |
+| paths | a list of [paths](#paths), each used to select a value from the object. |
+
+### Examples
+
+Picking a single value:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+main = maps.pick(object, ["foo"]) is {"foo": "bar"}
+```
+
+Picking multiple values:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar", "baz": "qux", "nit": "nat"}
+main = maps.pick(object, ["foo", "baz"]) is {"foo": "bar", "baz": "qux"}
+```
+
+Picking deep values:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {
+ "foo": "bar",
+ "baz": [
+ { "qux": 4 },
+ { "qux": 10 }
+ ],
+ "nit": {
+ "nat": "pak"
+ }
+}
+main = maps.pick(object, ["baz.1.qux", "nit.nat"]) is {"baz": [{"qux": 10}], "nit": {"nat": "pak"}}
+```
+
+## set
+
+**_set(object, path, value)_**
+
+Return a new map, assigning the provided value to the provided path. It will
+not modify the provided map in place.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the set against. |
+| path | the [path](#paths) to assign value |
+| value | the value to be assigned |
+
+### Examples
+
+Set a value on a simple path:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+object = maps.set(object, "foo", "qux")
+main = object is {"foo": "qux"}
+```
+
+Set a value on a deep path:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {}
+object = maps.set(object, "foo.bar.baz", 10)
+main = object is {"foo": { "bar": { "baz": 10 } } }
+```
+
+Set a value on an index in a list:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [ 2, 5 ] }
+object = maps.set(object, "foo.0", 10)
+main = object is {"foo": [ 10, 5 ] }
+```
+
+Set a value on every key within a list key:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [ { "bar": true }, { "bar": false } ] }
+object = maps.set(object, "foo.*.bar", true)
+main = object is {"foo": [ { "bar": true }, { "bar": true } ] }
+```
+
+## unset
+
+**_unset(object, path)_**
+
+Return a new map, removing the provided path from the source object. It will
+not modify the provided map in place.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the unset against. |
+| path | the [path](#paths) to remove |
+
+### Examples
+
+Unset on a simple path:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+object = maps.unset(object, "foo")
+main = object is {}
+```
+
+Unset on a deep path:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": { "bar": { "baz": 10, "qux": 15 } } }
+object = maps.unset(object, "foo.bar.baz")
+main = object is {"foo": { "bar": { "qux": 15 } } }
+```
+
+Unset an index in a list:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [ 2, 5 ] }
+object = maps.unset(object, "foo.0")
+main = object is {"foo": [ 5 ] }
+```
+
+Unset a list:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [ { "bar": true }, { "bar": false } ] }
+object = maps.unset(object, "foo.*", true)
+main = object is {"foo": [] }
+```
+
+## Paths
+
+Map helpers often receive a **path** argument that allows for looking up a nested
+key. Generally speaking, a path is a series of keys separated by `.`, however there
+are some additional capabilites that need to be explained.
+
+### Lists
+
+When traversing a nested map that contains a list, a specific index can be retrieved
+by providing the index as the part of the path.
+
+In the following code sample, the path will first enter the key `"foo"` within the
+map, followed by entering the first index of the list.
+
+```sentinel
+path = "foo.0"
+object = {"foo": [1]}
+```
+
+### Splat
+
+To provide advanced capabilities when using paths, you can also use the splat (`*`)
+operator to iterate through **all** elements in a list, with all parts of the path
+following the splat occuring on each entry.
+
+In the following code sample, the path will first enter the key `"foo"` within the map.
+It will then enter each item in the list, entering the `"bar"` key for each nested object.
+
+```sentinel
+path = "foo.*.bar"
+object = {"foo": [{"bar": 1}, {"bar": 2}]}
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/imports/decimal.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/imports/decimal.mdx
new file mode 100644
index 0000000000..a6633a38e0
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/imports/decimal.mdx
@@ -0,0 +1,318 @@
+---
+page_title: 'Import: decimal'
+sidebar_current: docs-imports-decimal
+description: |-
+ The decimal import provides functions for operating on numbers represented
+ as decimals.
+layout: docs
+---
+
+# Import: decimal
+
+The decimal import provides functions for operating on numbers represented
+as decimals.
+
+Binary floating-point numbers provided by the `float` type are unable to
+fully represent numbers like `1.1` and `2.2`. The decimal import can
+represent these numbers exactly, and so should be used when exactness is
+required.
+
+Decimals are created through the `new` function, which takes any type that
+can represent a number. Arguments to the decimal operators can also take
+a value of any of these types. The full list is:
+
+- float
+- int
+- string
+- existing decimal value
+
+### decimal.infinity(sign)
+
+Creates an infinite-value decimal. Infinite-value decimals are always larger
+or smaller, depending on sign, than any non-infinite decimals.
+
+```sentinel
+decimal.infinity(1) // +Infinity
+decimal.infinity(-1) // -Infinity
+```
+
+Also available as `decimal.inf`.
+
+### decimal.nan
+
+A decimal representing a "not-a-number" value. NaNs are not equal to any
+other number, even themselves.
+
+```sentinel
+decimal.new(-1).sqrt() // NaN
+decimal.new(0).mul(decimal.inf(1)) // NaN
+decimal.nan.is(decimal.nan) // false
+```
+
+### decimal.is_infinite(d)
+
+Evaluates to true if the decimal is infinite.
+
+```sentinel
+decimal.is_infinite(100) // false
+decimal.is_infinite("Infinity") // true
+```
+
+Also available as `decimal.is_inf`, `decimal.isinf` and `decimal.isinfinite`.
+
+### decimal.is_nan(d)
+
+Evaluates to true if the decimal is not-a-number.
+
+```sentinel
+decimal.is_nan("NaN") // true
+```
+
+Also available as `decimal.isnan`.
+
+### decimal.new(v)
+
+Constructs a decimal from another value.
+
+```sentinel
+decimal.new("1.1") // Constructs a decimal from a string.
+decimal.new(2.2) // Constructs a decimal from a float.
+decimal.new(3) // Constructs a decimal from an integer.
+decimal.new("1.1234E+400") // Constructs a decimal from a string using E-notation.
+```
+
+## Type: number
+
+Numbers are represented internally by a sign, coefficient, and exponent.
+The value is calculated with the formula `sign * coefficient * 10^exponent`.
+Exponent is limited to 32 bits, and the coefficient is limited by the total
+precision.
+
+The maximum precision a number can represent is 100 digits. This includes
+both before and after the decimal place.
+
+### number.string
+
+A string representation of the decimal.
+
+```sentinel
+decimal.new(1.5).string // 1.5
+```
+
+### number.sign
+
+A number between `-1` and `+1` representing the sign of the decimal.
+
+```sentinel
+decimal.new(1.5).sign // 1
+```
+
+### number.coefficient
+
+The coefficient component of the decimal.
+
+```sentinel
+decimal.new(1.5).coefficient // 15
+```
+
+### number.exponent
+
+The exponent component of the decimal.
+
+```sentinel
+decimal.new(1.5).exponent // -1
+```
+
+### number.float
+
+A floating-point representation of the decimal.
+
+```sentinel
+decimal.new(1.5).float // 1.500000
+```
+
+### number.int
+
+An integer representation of the decimal. This always rounds down to the nearest integer.
+
+```sentinel
+decimal.new(1.5).int // 1
+```
+
+### number.is(v)
+
+Test for equality with another value.
+
+```sentinel
+decimal.new(1.5).is(1) // false
+```
+
+### number.is_not(v)
+
+Test for inequality with another value.
+
+```sentinel
+decimal.new(1.5).is_not(1) // true
+```
+
+### number.less_than(v)
+
+Test that the decimal is less than another value.
+Can also be called as `number.lt(v)`
+
+```sentinel
+decimal.new(1.5).less_than(1) // false
+```
+
+### number.less_than_or_equals(v)
+
+Test that the decimal is less than or equals to another value.
+Can also be called as `number.lte(v)`
+
+```sentinel
+decimal.new(1.5).less_than_or_equals(1) // false
+```
+
+### number.greater_than(v)
+
+Test that the decimal is greater than another value.
+Can also be called as `number.gt(v)`
+
+```sentinel
+decimal.new(1.5).greater_than(1) // true
+```
+
+### number.greater_than_or_equals(v)
+
+Test that the decimal is greater than or equals to another value.
+Can also be called as `number.gte(v)`
+
+```sentinel
+decimal.new(1.5).greater_than_or_equals(1) // true
+```
+
+### number.add(v)
+
+Add another number to the decimal.
+
+```sentinel
+decimal.new(1.5).add(1) // 2.5
+```
+
+### number.subtract(v)
+
+Subtract another number from the decimal.
+Can also be called as `number.sub(v)`
+
+```sentinel
+decimal.new(1.5).subtract(1) // 0.5
+```
+
+### number.multiply(v)
+
+Multiply the decimal by a number.
+Can also be called as `number.mul(v)`
+
+```sentinel
+decimal.new(1.5).multiply(2) // 3.0
+```
+
+### number.divide(v)
+
+Divide the decimal by a number.
+Can also be called as `number.div(v)`
+
+```sentinel
+decimal.new(3.0).divide(1.5) // 2
+```
+
+### number.modulo(v)
+
+Find the remainder after dividing the decimal by a number.
+Can also be called as `mod`, `remainder`, or `rem`.
+
+```sentinel
+decimal.new(1.5).modulo(1) // 0.5
+```
+
+### number.power(v)
+
+Raise the decimal to a power.
+Can also be called as `number.pow(v)`
+
+```sentinel
+decimal.new(1.5).power(2) // 2.25
+```
+
+### number.exp()
+
+The natural exponent of the decimal. This calculates `e^number`.
+
+```sentinel
+decimal.new(1.5).exp() // 4.4816...
+```
+
+### number.loge()
+
+The natural logarithm of the decimal.
+Can also be called as `number.ln()`
+
+```sentinel
+decimal.new(1.5).loge() // 0.4054...
+```
+
+### number.log10()
+
+The base-10 logarithm of the decimal.
+Can also be called as `number.log()`
+
+```sentinel
+decimal.new(1.5).log10() // 0.1760...
+```
+
+### number.square_root()
+
+The square root of the decimal.
+Can also be called as `number.sqrt()`
+
+```sentinel
+decimal.new(1.5).square_root() // 1.2247...
+```
+
+### number.ceiling()
+
+Round the decimal to the smallest integer greater or equal to itself.
+Can also be called as `number.ceil()`
+
+```sentinel
+decimal.new(1.5).ceiling() // 2
+```
+
+### number.floor()
+
+Round the decimal to the largest integer less than or equal to itself.
+
+```sentinel
+decimal.new(1.5).floor() // 1
+```
+
+### number.absolute()
+
+The absolute value of the decimal.
+Can also be called as `number.abs()`
+
+```sentinel
+decimal.new(1.5).absolute() // 1.5
+decimal.new(-1.5).absolute() // 1.5
+```
+
+### number.negate()
+
+The negated value of the decimal. A positive decimal will become negative,
+and a negative decimal will become positive.
+Can also be called as `number.neg()`
+
+```sentinel
+decimal.new(1.5).negate() // -1.5
+decimal.new(-1.5).negate() // 1.5
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/imports/http.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/imports/http.mdx
new file mode 100644
index 0000000000..f94ae98c27
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/imports/http.mdx
@@ -0,0 +1,370 @@
+---
+page_title: 'Import: http'
+sidebar_current: docs-imports-http
+description: The http import enables the use of HTTP-accessible data from outside the runtime in Sentinel policy rules.
+layout: docs
+---
+
+# Import: http
+
+The http import enables the use of HTTP-accessible data from outside
+the runtime in Sentinel policy rules.
+
+The simplest example of the import in use would be:
+
+```sentinel
+import "http"
+
+resp = http.get("https://example.hashicorp.com")
+main = rule { resp.body contains "something" }
+```
+
+By default, any request response that is not a successful "200 OK" HTTP
+response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information and
+how to customize this behavior.
+
+For more advanced requests which require setting request headers, use a
+[`request` type](#type-request):
+
+```sentinel
+import "http"
+import "json"
+
+param token
+
+req = http.request("https://example.hashicorp.com").with_header("Authorization", "token "+token)
+resp = json.unmarshal(http.get(req).body)
+main = rule { resp["some_key"] is true }
+```
+
+The `with_header` function above returns the `request` object with the
+`Authorization` HTTP header set to value of the variable `some_value`. Many
+of the methods within the http import API are designed around this concept
+of method chaining, allowing for complex building of HTTP requests
+encapsulated in functions. The following is an idiomatic example further
+demonstrating this pattern:
+
+```sentinel
+import "http"
+import "json"
+
+param github_user
+param github_token
+
+client = func(req) {
+ return http.with_timeout(5).with_retries(3)
+}
+
+github_request = func(path) {
+ full_url = "https://api.github.com" + path
+ return http.request(full_url).
+ with_basic_auth(github_user, github_token).
+ with_header("Accept", "application/vnd.github.v3+json").
+ with_header("Custom", "foo"),
+}
+
+default_response = client.get(github_request("/example"))
+
+# Override the Custom header for this single request
+custom_header_response = json.unmarshal(
+ client.get(
+ github_request("/example").with_header("Custom", "bar"),
+ ),
+)
+
+# Override the timeout value for a known slower request
+slow_response = json.unmarshal(
+ client.with_timeout(15).get(github_request("/slow-endpoint")),
+)
+
+some_key_is_true = rule { json.unmarshal(default_response.body)["some_key"] is true }
+body_contains_text = rule { custom_header_response.body contains "something" }
+response_is_ok = rule { slow_response.status_code is 200 }
+
+main = rule { some_key_is_true and body_contains_text and response_is_ok }
+```
+
+### http.client
+
+Retrieve the base HTTP client with default settings. The returned client may be
+configured by calling configuration functions on it; all of these configuration
+functions on are also aliased as top-level functions in this namespace. See
+[`client`](#type-client) below.
+
+### http.request(url)
+
+Create a new [`request`](#type-request) to the specified `url` (string). A
+`request` type enables customization of headers and other concerns before then
+providing the constructed `request` to [`get()`](#client-get-url_or_request),
+[`post()`](#client-post-url_or_request) or [`send()`](#client-send-request).
+
+```sentinel
+req = http.request("example.hashicorp.com/foo").with_header("Foo", "bar")
+resp = http.get(req)
+```
+
+### http.methodPost
+
+Returns a string containing the accepted verb for POST requests, `"POST"`.
+
+### http.methodGet
+
+Returns a string containing the accepted verb for GET requests, `"GET"`.
+
+## Type: client
+
+All of the following configuration functions on the default client are also
+aliased as top-level functions in the import. This allows a short-hand (and
+idiomatic) way of configuring a new client without having to directly access
+`http.client` each time:
+
+```sentinel
+# The following two lines are semantically the same:
+resp = http.client.with_timeout(5).get(url)
+resp = http.with_timeout(5).get(url)
+```
+
+### url_or_request
+
+The [`get()`](#client-get-url_or_request) and [`post()`](#client-post-url_or_request)
+functions accept either a URL string or a request object, noted as
+`url_or_request` throughout the documentation.
+
+When `url_or_request` is a string, a request is made with the URL
+specified by the string. To customize HTTP headers and other settings, pass
+a request. By default, a request has a timeout value of 10 seconds and 1
+retry. These settings can be adjusted with [`with_timeout()`](#clientwith_timeoutinteger) and
+[`with_retries()`](#clientwith_retriesinteger), respectively.
+
+### Redirects
+
+If the response is one of the following redirect codes, the client follows the
+redirect, up to a maximum of 10 redirects:
+
+- 301 (Moved Permanently)
+- 302 (Found)
+- 303 (See Other)
+- 307 (Temporary Redirect)
+- 308 (Permanent Redirect)
+
+If the redirect limit is exceeded, the result is a policy error.
+
+By default, after any redirects are followed (up to the maximum), any request
+response that is not a successful "200 OK" response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information
+and how to customize this behavior.
+
+### client.get(url_or_request)
+
+Issue a GET request with a URL string or a `request` type. Returns a `response`
+type.
+
+```sentinel
+import "http"
+
+resp = http.get("https://example.hashicorp.com")
+main = rule { resp.body contains "something" }
+```
+
+### client.post(url_or_request)
+
+Issue a POST request with a URL string or a `request` type. Returns a `response`
+type.
+
+```sentinel
+import "http"
+
+req = http.request("https://example.hashicorp.com")
+ .with_body(json.marshal({"foo": "bar"}))
+resp = http.post(req)
+main = rule { resp.body contains "something" }
+```
+
+### client.send(request)
+
+Make a request using the supplied `request` type. Returns a `response`.
+
+```sentinel
+import "http"
+
+req = http.request("https://example.hashicorp.com")
+resp = http.send(req)
+main = rule { resp.body contains "something" }
+```
+
+### client.accept_status_codes(list)
+
+Configures a client to not automatically cause a policy failure if
+the resulting response's status code is in `list`. Any status code received
+that is not present in this list will trigger a runtime error.
+
+By default, any request response that is not a successful "200 OK" response
+causes a policy error. This is for convenience, as the most common scenario
+by far is to receive a response and immediately signal a policy error if the
+status code is not 200. Use this scoping to specify a list of non-redirect
+HTTP codes that you wish to accept without error and evaluate the response
+yourself.
+
+Redirects are not considered final status codes in this context. That is,
+redirects are always followed up to the maximum allowed value (see [`Redirects`](#redirects))
+and the final response code in the redirect chain is validated against
+`list`.
+
+```sentinel
+resp = http.accept_status_codes([200, 404]).get(url)
+main = rule { resp.status_code is 404 }
+```
+
+### client.accepted_status_codes
+
+Returns a list of the client's accepted status codes.
+
+```sentinel
+client = http.accept_status_codes([200, 404])
+main = rule { client.accepted_status_codes contains 404 }
+```
+
+### client.with_retries(integer)
+
+Sets the maximum number of retries, specified by `integer`, that should be
+attempted on an unsuccessful request. An unsuccessful request is considered
+to be any 5xx response except `501 (Not Implemented)` or a request timeout.
+By default, one retry is attempted.
+
+The maximum number of retries is 10, for a total of 11 request attempts.
+When a request exceeds the maximum number of retries without a response, the
+result is a policy error. Specifying a number larger than this will result
+in a runtime error.
+
+Between each retry, an exponentially increasing delay is observed, allowing
+time for the server to recover. This delay starts at 1 second for the first
+retry and increases each attempt thereafter. The maximum delay for a single
+attempt is 30 seconds.
+
+Retries can be disabled by specifying 0.
+
+### client.retries
+
+Returns the client's configured number of retries as an integer.
+
+### client.with_timeout(integer)
+
+Sets the request timeout to the number of seconds indicated by `integer`.
+
+Each individual request performed by the HTTP client will be limited by the
+given request timeout. This timeout includes connection time, any redirects,
+and reading the response body.
+
+A maximum of 30 seconds is allowed. Specifying a number larger than this
+will result in a runtime error.
+
+By default, requests will time out after 10 seconds.
+
+### client.timeout
+
+Returns the client's configured timeout in whole seconds as an integer.
+
+### client.without_certificate_verification()
+
+Skips verification of TLS certificates during secure HTTP operations,
+allowing for requests to `https://` endpoints which are secured with a TLS
+certificate signed by an untrusted certificate authority. Takes no arguments.
+
+By default, the HTTP client will trigger a runtime error if a TLS certificate
+presented by a server is from an untrusted certificate authority.
+
+Do not change this setting unless you are aware of the risks involved.
+Without verification, TLS accepts any certificate presented by the server
+and any host name in that certificate. In this mode, TLS is susceptible to
+man-in-the-middle attacks. This option should only be used for testing or in
+trusted networks with self-signed certificates.
+
+```sentinel
+http.without_certificate_verification().get(url)
+```
+
+### client.certificate_verification
+
+Returns true if the client requires TLS certificates to be verifiable.
+
+## Type: request
+
+### request.url
+
+Returns the request URL as a string.
+
+### request.headers
+
+Returns the configured request headers as a string-keyed map of strings.
+
+### request.body
+
+Returns the configured body as a string.
+
+### request.with_header(key, value)
+
+Returns a `request` with the header `key` (string) set to `value` (string).
+
+Values are overridden on subsequent calls to the same `key`. To specify
+multiple values, use the existing value when setting the new one.
+
+```sentinel
+original = http.request("http://example.hashicorp.com").with_header("Foo", "bar")
+overridden = original.with_header("Foo", "baz")
+multi_value = original.with_header("Foo", original.headers["Foo"] + ",baz")
+
+main = rule {
+ original.headers["Foo"] is "bar" and
+ overridden.headers["Foo"] is "baz" and
+ multi_value.headers["Foo"] is "bar,baz"
+}
+```
+
+### request.with_basic_auth(username, password)
+
+Returns a `request` with the `Authorization` header set to use HTTP Basic
+Authentication with the provided username and password.
+
+Note that with HTTP Basic Authentication the provided username and password
+are encoded, but not encrypted.
+
+### request.with_body(body)
+
+Returns a `request` with the `body` set. This value will then be sent as a body
+as part of the request. Commonly used along with the [`post()`](#client-post-url_or_request) function.
+
+```sentinel
+req = http.request("http://example.hashicorp.com")
+ .with_header("Content-Type", "application/json")
+ .with_body(json.marshal({ "foo": "bar" }))
+
+resp = http.post(req)
+```
+
+## Type: response
+
+### response.status_code
+
+The response status code as an integer, e.g. `200`.
+
+### response.headers
+
+The response headers as a map.
+
+### response.body
+
+The response body as a string. Callers should unmarshal the content to a useful
+representation as necessary based on the content type. For example, if the
+response is in JSON:
+
+```sentinel
+import "http"
+import "json"
+
+# This example endpoint returns {"some_key": true}
+req = http.request("https://example.hashicorp.com/some-json")
+
+resp = json.unmarshal(http.get(req).body)
+main = rule { keys(resp) contains "some_key" and resp["some_key"] is true }
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/imports/index.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/imports/index.mdx
new file mode 100644
index 0000000000..e45bf81b81
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/imports/index.mdx
@@ -0,0 +1,18 @@
+---
+page_title: Sentinel Language - Standard Imports
+sidebar_current: docs-imports
+description: >-
+ Standard Imports are the built-in imports that are available to all Sentinel
+ policies.
+layout: docs
+---
+
+# Standard Imports
+
+Standard Imports are the built-in [imports](/sentinel/language/imports)
+that are available to all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in import.
+
+Sentinel-embedded applications can choose to allow or deny certain
+standard imports. Please reference the documentation for the Sentinel-enabled
+application you're using to determine if all standard imports are available.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/imports/json.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/imports/json.mdx
new file mode 100644
index 0000000000..deb10ae98a
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/imports/json.mdx
@@ -0,0 +1,48 @@
+---
+page_title: 'Import: json'
+sidebar_current: docs-imports-json
+description: The json import enables a Sentinel policy to parse and access a JSON document.
+layout: docs
+---
+
+# Import: json
+
+The json import enables a Sentinel policy to parse and access a JSON
+document.
+
+### json.unmarshal(obj)
+
+Unmarshals the JSON object `obj` into a native Sentinel structure.
+
+All native JSON types can be represented perfectly as Sentinel native types.
+
+The `obj` argument must be a string.
+
+```sentinel
+// Typically the input for this would come from an external source.
+config = json.unmarshal("{ \"foo\": 42 }")
+config.foo // 42
+config.bar // undefined (as usual for accessing a non-existent map key)
+```
+
+### json.marshal(obj)
+
+Marshals the Sentinel object `obj` into a JSON object encoded as a string.
+
+Sentinel's functions cannot be natively encoded, and will cause an error if
+it is present within the provided object.
+
+Sentinel's `undefined` cannot be natively encoded as JSON. If `undefined`
+is supplied directly to `marshal`, it will return `undefined`. If `undefined`
+is present within any map or list within the provided object, the associated
+element will be removed prior to performing the JSON encoding.
+
+### json.valid(obj)
+
+Validates the object `obj` is valid JSON format.
+
+The `obj` argument must be a string.
+
+```sentinel
+json.valid("{ \"foo\": 42 }") // true
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/imports/runtime.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/imports/runtime.mdx
new file mode 100644
index 0000000000..6493bbbb22
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/imports/runtime.mdx
@@ -0,0 +1,16 @@
+---
+page_title: 'Import: runtime'
+sidebar_current: docs-imports-runtime
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: runtime
+
+The runtime import contains various information about the Sentinel runtime. It
+can be used to get information about the runtime as it's embedded in the CLI or
+a specific integration, such as validating a specific runtime version.
+
+### runtime.version
+
+Returns the version of Sentinel within this implementation.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/imports/sockaddr.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/imports/sockaddr.mdx
new file mode 100644
index 0000000000..a6842ba5f0
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/imports/sockaddr.mdx
@@ -0,0 +1,41 @@
+---
+page_title: 'Import: sockaddr'
+sidebar_current: docs-imports-sockaddr
+description: The sockaddr import makes working with IP addresses easy.
+layout: docs
+---
+
+# Import: sockaddr
+
+The sockaddr import makes working with IP addresses easy.
+
+### sockaddr.is_contained(outer, inner)
+
+The is_contained function returns true if `inner` is an address that
+is contained within `outer`.
+
+```sentinel
+sockaddr.is_contained("192.168.0.0/24", "192.168.0.32") // true
+sockaddr.is_contained("192.168.0.0/24", "192.174.1.1") // false
+```
+
+### sockaddr.is_equal(addr1, addr2)
+
+The is_equal function returns true if addr1 is equal to addr2.
+
+```sentinel
+sockaddr.is_equal("192.168.12.24", "192.168.12.24") // true
+```
+
+### sockaddr.is_ipv4(addr)
+
+The is_ipv4 function returns true if addr is an IPv4 address.
+
+```sentinel
+sockaddr.is_ipv4("192.168.12.24") // true
+sockaddr.is_ipv4("2001:0db8:85a3:0000:0000:8a2e:0370:7334") // false
+```
+
+### sockaddr.is_ipv6(addr)
+
+The is_ipv6 function returns true if addr is an IPv6 address.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/imports/strings.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/imports/strings.mdx
new file mode 100644
index 0000000000..e1ba9a9e2a
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/imports/strings.mdx
@@ -0,0 +1,150 @@
+---
+page_title: 'Import: strings'
+sidebar_current: docs-imports-strings
+description: The strings import exposes common string operations.
+layout: docs
+---
+
+# Import: strings
+
+The strings import exposes common string operations.
+
+### strings.has_prefix(s, prefix)
+
+Returns true if `s` starts with `prefix`.
+
+```sentinel
+strings.has_prefix("billing-id", "billing-") // true
+strings.has_prefix("bill-id", "billing-") // false
+```
+
+### strings.has_suffix(s, suffix)
+
+Returns true if `s` ends with `suffix`.
+
+```sentinel
+strings.has_suffix("billing-id", "id") // true
+strings.has_suffix("billing-name", "id") // false
+```
+
+### strings.join(a, sep)
+
+Joins a list with the string supplied by `sep`.
+
+Multi-dimensional lists are supported, and non-string primitive
+types will be converted to their string equivalents. Maps and
+other complex non-list types are not supported.
+
+```sentinel
+strings.join(["foo", "bar", "baz"], ".") // "foo.bar.baz"
+strings.join([["foo", "bar"], "baz"], ".") // "foo.bar.baz"
+strings.join(["a", 1, 1.01, true], ",") // "a,1,1.01,true"
+```
+
+### strings.replace(s, old, new, times)
+
+Replace the substring `old` with the substring `new` in the string `s`
+by how many times the int parameter `times` is specified.
+If the string `s` doesn't the substrings or if `times` is zero,
+then the string is returned without any changes applied.
+
+```
+strings.replace("foobar", "foo", "bar") // "barbar"
+strings.replace("foofoofoobar", "foo", "bar", 1) // "barfoofoobar"
+strings.replace("foofoofoobar", "foo", "bar", 2) // "barbarfoobar"
+strings.replace("foobar", "test", "bar") // "foobar"
+strings.replace("foobar", "foo", "bar", 0) // "foobar"
+```
+
+### strings.trim(s, cutset)
+
+Trim the leading and trailing characters in `cutset` from the string `s`.
+If the string doesn't have the cutset, then the string is returned
+unmodified.
+
+```
+strings.trim("iiifoobariii", "i") // "foobar"
+strings.trim("!1!bar foo!!1", "!1") // "bar foo"
+```
+
+### strings.trim_left(s, cutset)
+
+Trim the leading characters contained in `cutset` from the string `s`.
+If the string doesn't have the cutset, then the string is returned
+unmodified.
+
+```sentinel
+strings.trim_left("aaaaaaaafoo", "a") // "foo"
+strings.trim_left("!!!bar!!!", "!") // "bar!!!"
+```
+
+### strings.trim_right(s, cutset)
+
+Trim the trailing characters contained in the `cutset` from the
+string `s`. If the string doesn't have the cutset, then the string
+is returned unmodified.
+
+```sentinel
+strings.trim_right("foo_bar...", ".") // "foo_bar"
+strings.trim_right("billing---","-") // "billing"
+```
+
+### strings.trim_space(s)
+
+Trim leading and trailing white space from the string `s`. If the
+string doesn't have any surrounding white space, then the string is
+returned unmodified.
+
+```sentinel
+strings.trim_space(" foo ") // "foo"
+strings.trim_space(" bar foo ") // "bar foo"
+```
+
+### strings.trim_prefix(s, prefix)
+
+Trim the prefix from the string `s`. If the string doesn't have the
+prefix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_prefix("billing-id", "billing-") // "id"
+strings.trim_prefix("bill-id", "billing-") // "bill-id"
+```
+
+### strings.trim_suffix(s, suffix)
+
+Trim the suffix from the string `s`. If the string doesn't have the
+suffix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_suffix("billing-id", "-id") // "billing"
+strings.trim_suffix("bill-id", "-foo") // "bill-id"
+```
+
+### strings.to_lower(s)
+
+Lowercase the string s.
+
+```sentinel
+strings.to_lower("FoO") // "foo"
+```
+
+### strings.to_upper(s)
+
+Uppercase the string s.
+
+```sentinel
+strings.to_upper("FoO") // "FOO"
+```
+
+### strings.split(s, sep)
+
+Split the string 's' via a substring 'sep'. If 'sep' is not contained in
+'s', the return value will be a single-element list containing only 's'.
+As a special-case, if the input is already a list, it will be returned
+as-is. This allows calling the split function when not knowing if the input
+is already a list or not.
+
+```sentinel
+strings.split("foo,bar,baz", ",") // ["foo", "bar", "baz"]
+strings.split(["foo", "bar", "baz"], ",") // ["foo", "bar", "baz"]
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/imports/time.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/imports/time.mdx
new file mode 100644
index 0000000000..3a675e2bbd
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/imports/time.mdx
@@ -0,0 +1,257 @@
+---
+page_title: 'Import: time'
+sidebar_current: docs-imports-time
+description: The time import provides access to the execution time and time functions.
+layout: docs
+---
+
+# Import: time
+
+The time import provides access to the execution time and time functions.
+
+During the execution of a policy the time is fixed to the moment of
+execution. This gives the illusion that a policy instantly executes at a
+single moment of time.
+
+The import uses Coordinated Universal Time (UTC).
+
+As an example of this, if a policy accessed `time.now.second` multiple times,
+for each access the same value would be returned even if they are several seconds apart.
+This is the execution `timespace`, which is mapped to `time.now`.
+
+It is also possible to run functions on a custom time by using the
+`time.load()` function to create a new timespace from the specified value.
+See the documentation for available actions on timespaces.
+
+Times specified as the reference time or via the `time.load()` function can
+be specified in a `timeish` fashion. This is either one of:
+
+- The number of seconds since the Unix epoch (Thursday, 1 January 1970,
+ 00:00:00 UTC),
+- A timestamp matching the format outlined in RFC3339, either in the
+ format `2006-01-02T15:04:05+07:00`, which includes a timezone offset, or
+ `2006-01-02T15:04:05Z`, which indicates UTC.
+
+### time.now
+
+Create a `timespace` locked either to execution time or, if set, the
+reference time. In a given execution, this will always produce the same
+value.
+
+```sentinel
+time.now // {"day": 17, "hour": 23, "minute": 19, "month": 11 ... }
+```
+
+### time.load(timeish)
+
+Create a timespace using the given value. Please see the import
+documentation for valid values for "timeish".
+
+```sentinel
+time.load("2019-11-16T01:20:30Z") // {"day": 16, "hour": 1, "minute": 20, "month": 11 ... }
+```
+
+### time.nanosecond
+
+A nanosecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.nanosecond * 1) // 2019-11-16T01:20:30.000000001Z
+```
+
+### time.microsecond
+
+A microsecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.microsecond * 1) // 2019-11-16T01:20:30.000001Z
+```
+
+### time.millisecond
+
+A millisecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.millisecond * 1) // 2019-11-16T01:20:30.001Z
+```
+
+### time.second
+
+A second duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.second * 1) // 2019-11-16T01:20:31Z
+```
+
+### time.minute
+
+A minute for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.minute * 1) // 2019-11-16T01:21:30Z
+```
+
+### time.hour
+
+An hour duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+## Type: timespace
+
+### timespace.hour
+
+The hour from 0 to 23.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").hour // 1
+```
+
+### timespace.minute
+
+The minute from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").minute // 20
+```
+
+### timespace.second
+
+The second from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").second // 30
+```
+
+### timespace.day
+
+The day of the month, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").day // 16
+```
+
+### timespace.month
+
+The month of the year as an integer, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month // 11
+```
+
+### timespace.month_name
+
+The name of the month of the year, in English. Example: `January`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month_name // November
+```
+
+### timespace.weekday
+
+The weekday as an integer, where 0 is Sunday and 6 is Saturday.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday // 6
+```
+
+### timespace.weekday_name
+
+The name of the day of the week, in English. Example: `Sunday`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday_name // Saturday
+```
+
+### timespace.year
+
+The year as an integer.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").year // 2019
+```
+
+### timespace.unix
+
+The number of seconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix // 1573867230
+```
+
+### timespace.unix_nano
+
+The number of nanoseconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix_nano // 1573867230000000000
+```
+
+### timespace.zone
+
+The timezone offset, in seconds. This can be a negative number to indicate
+time west of UTC.
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone // -28800
+```
+
+### timespace.zone_string
+
+The timezone offset, in the format `+HH:MM` (example: `-08:00` for PST).
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone_string // -08:00
+```
+
+### timespace.before(timeish_or_timespace)
+
+Check if the time in the timespace is before the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").before("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.after(timeish_or_timespace)
+
+Check if the time in the timespace is after the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").after("2019-11-15T01:20:30Z") // true
+```
+
+### timespace.equal(timeish_or_timespace)
+
+Check if two times are equivalent
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").equal("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.add(duration)
+
+Add a duration in nanoseconds to the time in the timespace and return a new timespace. The
+duration can be negative. The duration should be specified as an integer
+multiplied with the correct unit.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+### timespace.sub(timeish_or_timespace)
+
+Subtract a time from the time in the timespace and return a duration in nanoseconds.
+
+```sentinel
+// Gives a duration of 3 hours.
+duration = time.load(
+ "2019-11-16T01:20:30Z",
+).sub("2019-11-16T04:20:30Z")
+
+// Returns 2019-11-16T01:20:30Z
+time.load(
+ "2019-11-16T04:20:30Z",
+).add(duration)
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/imports/types.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/imports/types.mdx
new file mode 100644
index 0000000000..fcfdbd2ae5
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/imports/types.mdx
@@ -0,0 +1,35 @@
+---
+page_title: 'Import: types'
+sidebar_current: docs-imports-types
+description: The types import enables a Sentinel policy to parse an object's type.
+layout: docs
+---
+
+# Import: types
+
+The types import enables a Sentinel policy to parse an object's type.
+
+### types.type_of(obj)
+
+Returns the object's type as a string
+
+```sentinel playground
+import "types"
+
+// Typically the inputs would come from an external source.
+is_boolean = rule { types.type_of(true) is "bool" }
+is_string = rule { types.type_of("Hello!") is "string" }
+is_integer = rule { types.type_of(42) is "int" }
+is_float = rule { types.type_of(42.123) is "float" }
+is_null = rule { types.type_of(null) is "null" }
+is_undefined = rule { types.type_of(undefined) is "undefined" }
+
+main = rule {
+ is_boolean and
+ is_string and
+ is_integer and
+ is_float and
+ is_null and
+ is_undefined
+}
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/imports/units.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/imports/units.mdx
new file mode 100644
index 0000000000..e2ad3442f9
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/imports/units.mdx
@@ -0,0 +1,39 @@
+---
+page_title: 'Import: units'
+sidebar_current: docs-imports-units
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: units
+
+The units import provides access to quick calculations for various byte
+units.
+
+Note that this import uses base-2 terminology:
+
+- One kilobyte is 1024 bytes
+- One megabyte is 1024^2 = 1048576 bytes
+- One gigabyte is 1024^3 = 1073741824 bytes
+- One terabyte is 1024^4 = 1099511627776 bytes
+- One petabyte is 1024^5 = 1125899906842624 bytes
+
+### units.kilobyte
+
+The base-2 representation of a kilobyte (1024 bytes).
+
+### units.megabyte
+
+The base-2 representation of a megabyte (1048576 bytes).
+
+### units.gigabyte
+
+The base-2 representation of a gigabyte (1073741824 bytes).
+
+### units.terabyte
+
+The base-2 representation of a terabyte (1099511627776 bytes).
+
+### units.petabyte
+
+The base-2 representation of a petabyte (1125899906842624 bytes).
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/imports/version.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/imports/version.mdx
new file mode 100644
index 0000000000..59366c1087
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/imports/version.mdx
@@ -0,0 +1,151 @@
+---
+page_title: 'Import: version'
+sidebar_current: docs-imports-version
+description: |-
+ The version import provides functions for parsing versions and version constraints,
+ and verifying versions against a set of constraints.
+layout: docs
+---
+
+# Import: version
+
+The version import provides functions for parsing versions and version constraints,
+and verifying versions against a set of constraints.
+
+Versions are created through the `new` function, which accepts a string type in dotted-tri format (i.e. 1.0.0-alpha.1+001) that can represent a version.
+
+### version.new(v)
+
+Constructs a version from string value.
+
+```sentinel
+version.new("1.0.0-alpha.1+001")
+```
+
+### version.major
+
+An integer representation of the Major number for a given version .
+
+```sentinel
+version.new("1.0.0-alpha.1+001").major // 1
+```
+
+### version.minor
+
+An integer representation of the Minor number for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").minor // 0
+```
+
+### version.patch
+
+An integer representation of the Patch number for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").patch // 0
+```
+
+### version.prerelease
+
+A string representation of the Prerelease for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").prerelease // "alpha.1"
+```
+
+### version.metadata
+
+A string representation of the Metadata for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").metadata // "001"
+```
+
+### version.version
+
+A string representation of the version including pre-release and metadata information.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").version // "1.0.0-alpha.1+001"
+```
+
+### version.greater_than(v)
+
+Tests that the version is greater than a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").greater_than("0.12.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").gt("1.0.1-alpha.1+001") // False
+```
+
+### version.greater_than_or_equals(v)
+
+Tests that the version is greater than or equal to a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").greater_than_or_equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").gte("1.0.1-alpha.1+001") // False
+```
+
+### version.less_than(v)
+
+Tests that the version is less than a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").less_than("1.0.1-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").lt("0.12.0-alpha.1+001") // False
+```
+
+### version.less_than_or_equals(v)
+
+Tests that the version is less than or equal to a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").less_than_or_equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").lte("0.12.0-alpha.1+001") // False
+```
+
+### version.less_than_or_equals(v)
+
+Tests that the version equals a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").eq("0.12.0-alpha.1+001") // False
+```
+
+### version.compare(v)
+
+Compares one version with a given version. Compare returns -1, 0, or 1 if the version is less, equal, or greater than the other version, respectively.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").compare("0.12.0-alpha.1+001") // -1
+version.new("1.0.0-alpha.1+001").compare("1.0.0-alpha.1+001") // 0
+version.new("0.12.0-alpha.1+001").cmp("1.0.0-alpha.1+001") // 1
+```
+
+### version.satisfies(v, x)
+
+Tests that a version adheres to one or more version constraints. Satisfies supports the following restriction operators:
+
+- =
+- !=
+- \>
+- <
+- \>=
+- <=
+- ~>
+
+Satisfies supports the following usage scenarios:
+
+- A constraint with a pre-release can only match a pre-release version that contains the same major, minor and patch identifiers.
+- A constraint without a pre-release can only match a version without a pre-release.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").satisfies("> 1.0.0-alpha.1+001") // false
+version.new("1.0.0-alpha.1+001").satisfies(">= 1.0.0-alpha.1+001") // true
+version.new("1.0.0-alpha.1+001").satisfies(">= 1.0.0-alpha.1+001, < 1.0.0-beta+001") // true
+version.new("1.0.0").sat("= 1.0.0") // true
+version.new("0.12.7").sat("~> 0.12.0") // true
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/index.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/index.mdx
new file mode 100644
index 0000000000..c68ccc265b
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/index.mdx
@@ -0,0 +1,31 @@
+---
+layout: 'docs'
+page_title: 'Documentation'
+sidebar_current: 'docs-home'
+description: |-
+ Sentinel is a language and framework for policy built to be embedded in existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+["Why Sentinel?"](/sentinel/why) to understand what Sentinel is, how it
+compares to other software, and more.
+
+
\ No newline at end of file
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/intro.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/intro.mdx
new file mode 100644
index 0000000000..b9a2675f2b
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/intro.mdx
@@ -0,0 +1,75 @@
+---
+page_title: Introduction to Sentinel
+sidebar_current: docs-intro
+description: >-
+ Sentinel is a language and framework for policy built to be embedded in
+ existing software to enable fine-grained, logic-based policy decisions.
+layout: docs
+---
+
+# Introduction to Sentinel
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+["Why Sentinel?"](/sentinel/docs/why) to understand what Sentinel is, how it
+compares to other software, and more.
+
+## What is Sentinel?
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+Sentinel provides a language for writing policy and a workflow for developing
+and testing policies independent of the software they'll be written to. We
+also provide a framework for developers to build plugins that allow a
+Sentinel-enabled system to access external information to make policy
+decisions.
+
+Most systems today have some degree of access control. You are able to define
+identities and what they have access to. These ACL systems solve an immediate
+and necessary problem of locking down a system in very broad strokes. Sentinel
+is a reusable system for more advanced software policy decisions. Sentinel
+enables:
+
+- **Fine-Grained Policy**: Most ACL systems only enable coarse-grained
+ behaviors: "read", "write", etc. Sentinel enables fine-grained behavior such
+ as disallowing a certain API call when specific parameters are present.
+
+- **Logic-Based Policy**: You can write policy using full
+ conditional logic. For example, you may only allow a certain application behavior
+ on Monday to Thursday unless there is a manager override.
+
+- **Accessing External Information**: Sentinel can source external information
+ to be used in policy decisions. For example, a policy that restricts the size
+ of a payload may read data from [Consul](https://www.consul.io) to determine
+ the payload size limit.
+
+- **Enforcement levels**: Sentinel allows policies to be defined along
+ with an "enforcement level" that dictates the pass/fail behavior of a policy.
+ Advisory policies warn if they fail, soft mandatory policies can have their
+ failures overridden, and hard mandatory policies must pass under all
+ circumstances. Having this as a built-in concept enables you to model
+ policy more accurately and completely for your organization.
+
+
+
+
+## Next steps
+
+Refer the page on [Why Sentinel?](/sentinel/why) to learn more about the origins of Sentinel.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/language/arithmetic.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/language/arithmetic.mdx
new file mode 100644
index 0000000000..c49f2fd0ce
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/language/arithmetic.mdx
@@ -0,0 +1,67 @@
+---
+page_title: Sentinel Language - Arithmetic
+sidebar_current: docs-language-arith
+description: >-
+ Sentinel supports arithmetic operators for integers and floats. Sentinel
+ supports sum, difference, product, quotient, and remainder.
+layout: docs
+---
+
+# Language: Arithmetic
+
+Sentinel supports arithmetic operators for integers and floats. Sentinel
+supports sum, difference, product, quotient, and remainder.
+
+```plaintext
++ sum
+- difference
+* product
+/ quotient
+% remainder
+```
+
+These operators work in a typical infix style:
+
+```sentinel
+4 + 8 // 12
+8 * 2 // 16
+8 / 4 // 2
+8 / 5 // 1
+8 % 5 // 3
+```
+
+## Order of Operations
+
+Arithmetic follows a standard mathematical order of operations.
+Grouping with parentheses can be used to affect ordering.
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+(4 + 5) * 2 // 18
+```
+
+A full table of operator precendence can be found on the
+[boolean expressions page](/sentinel/language/boolexpr). This
+shows how arithmetic operators relate to other operators.
+
+## Integer Division
+
+Integer division with a remainder rounds down to the nearest integer.
+Example: `8 / 3 is 2`.
+
+If the divisor is zero, an error occurs.
+
+## Mixed Numeric Operations
+
+Mixed numeric operations between integer and floating-point values are
+permitted. The result is a floating-point operation with the integer converted
+to a floating-point value for purposes of calculation.
+
+```sentinel
+import "types"
+
+a = 1.1 + 1 // 2.1
+types.type_of(a) // "float"
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/language/boolexpr.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/language/boolexpr.mdx
new file mode 100644
index 0000000000..f1c518c3bf
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/language/boolexpr.mdx
@@ -0,0 +1,225 @@
+---
+page_title: Sentinel Language - Boolean Expressions
+sidebar_current: docs-language-boolexpr
+description: >-
+ Boolean expressions are expressions that evaluate to a boolean value from the
+ result of a combination of one or more comparisons and logical operators.
+layout: docs
+---
+
+# Language: Boolean Expressions
+
+Boolean expressions are expressions that evaluate to a boolean value
+from the result of a combination of one or more comparisons and logical
+operators. Boolean expressions form the basis of policy since policy can be broken
+down to a set of logical decisions that turn into true or false.
+
+A single boolean expression is the body of a [rule](/sentinel/language/rules).
+Additionally, boolean expressions are used with [conditionals](/sentinel/language/conditionals),
+and more.
+
+## Order of Operations
+
+The precedence of operators is shown below. Operators with higher
+precedence values (larger numbers) are evaluated first. Operators with
+the same precedence value are evaluated left-to-right.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Examples of this precedence are shown below:
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+```
+
+## Logical Operators
+
+Logical operators are used to change or combine logical expressions.
+There are three binary operators `and`, `or`, and `xor`. There is
+a single unary operator `not` (which can also be specified as `!`).
+
+The binary operators require two boolean operands and the unary
+operator requires a single boolean operand. In both case, the operands
+are values or expressions that are boolean types.
+
+Below is a basic explanation of the logical operators directly from
+the specification:
+
+```sentinel
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+Using parentheses to group boolean expressions, you can combine
+boolean expressions to become more complex or affect ordering:
+
+```sentinel
+(p and q) or r
+p and (q or r)
+```
+
+## Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+For any comparison, the two operands must be equivalent types. The one
+exception are integers and floats. When an integer is compared with
+a float, the integer is promoted to a floating point value.
+
+The available comparison operators are:
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+The behavior of `is` with `==` and `is not` with `!=` is identical.
+
+Example comparisons:
+
+```sentinel
+name is "Mitchell"
+idnumber > 42
+idnumber <= 12
+```
+
+Using the logical operators, these can be combined:
+
+```sentinel
+name is "Mitchell" and idnumber > 42
+```
+
+The language specification provides more detail on exactly
+[how comparison behaves](/sentinel/language/spec#comparison-operators).
+
+## Set Operators
+
+The set operators `contains` and `in` test for inclusion in a collection
+(a list or map).
+
+Set operators may 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.
+
+`contains` tests if the left-hand collection contains the right-hand value.
+`in` tests if the right-hand collection contains the left-hand value. For
+maps, "contains" looks at the keys, not the values.
+
+Examples:
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+```
+
+## Matches Operator
+
+The `matches` operator tests if a string matches a regular expression.
+The `matches` operator can be negated by prefixing the operator with
+`not`, such as `not matches`.
+
+The left-hand value is the string to test. The right-hand value is
+a string value representing a regular expression. The syntax of the regular
+expression is the same general syntax used by Python, Ruby, and other languages.
+The precise syntax accepted is the syntax accepted by RE2.
+
+Regular expressions are not anchored by default; any anchoring must be
+explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+## Any, All Expressions
+
+`any` and `all` expressions allow testing that any or all elements of a
+collection match some boolean expression. The result of an `any` and `all`
+expression is a boolean.
+
+`any` returns the boolean value `true` if any value in the collection expression
+results in the body expression evaluating to `true`. If the body expression
+evalutes to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression
+result in the body expression evaluating to `true`. If any value in the
+collection expression result in the body expression evaluating to `false`,
+the `all` expression returns `false`.
+
+For empty collections, `any` returns `false` and `all` returns `true`.
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" }
+any group.tasks as t { t.driver is "vmware" }
+```
+
+Since `any` and `all` expressions are themselves boolean expressions, they
+can be combined with other operators:
+
+```sentinel
+any ["a", "b"] as char { char is "a" } or
+other_value is "another"
+```
+
+## Emptiness Comparison
+
+The expressions `is empty` and `is not empty` provide a convenience
+method for determining the emptiness of a value. It supports the same types
+as the [built-in length](/sentinel/functions/length), collections and strings.
+
+```sentinel
+[] is empty // true
+[] is not empty // false
+["foo"] is empty // false
+["foo"] is not empty // true
+undefined is empty // undefined
+undefined is not empty // undefined
+```
+
+## Defined Comparison
+
+The expressions `is defined` and `is not defined` provide a convenience
+method for determining if a value has been defined. In other words, any value
+other than `undefined` can be considered as `defined`.
+
+```sentinel
+[] is defined // true
+4 is defined // true
+true is defined // true
+{} is defined // true
+undefined is defined // false
+[] is not defined // false
+4 is not defined // false
+true is not defined // false
+undefined is not defined // true
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/language/collection-operations.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/language/collection-operations.mdx
new file mode 100644
index 0000000000..41da8f3584
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/language/collection-operations.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Sentinel Language - Collection Operations
+sidebar_current: docs-language-collection-operations
+description: |-
+ Operations for interacting with collections (list, map).
+layout: docs
+---
+
+# Language: Collection Operations
+
+Collection operations are expressions that are performed on a list or map to
+return a variation of the initial data.
+
+At the moment, `filter` is the only collection operation available.
+
+## Filter Expression
+
+`filter` is a [quantifier
+expression](/sentinel/language/spec#quantifier-expressions-any-all-filter-map) that
+returns a subset of the provided collection. Only elements whose filter body
+returns true will be returned. If any of the elements filter body returns
+undefined, the final result will be undefined.
+
+Filter uses the same syntax as the `any` and `all` [boolean
+expressions](/sentinel/language/boolexpr#any-all-expressions):
+
+```sentinel
+filter list as value { condition } // Single-iterator, list
+filter list as idx, value { condition } // Double-iterator, list
+
+filter map as key { condition } // Single-iterator, map
+filter map as key, value { condition } // Double-iterator, map
+```
+
+Examples:
+
+```sentinel
+l = [1, 1, 2, 3, 5, 8]
+evens = filter l as v { v % 2 is 0 } // [2, 8]
+
+m = { "a": "foo", "b": "bar" }
+matched_foo = filter m as _, v { v is "foo" } // { "a": "foo" }
+```
+
+## Map Expression
+
+`map` is a [quantifier
+expression](/sentinel/language/spec#quantifier-expressions-any-all-filter-map) that
+returns a list, regardless of the input collection type. Each element within the
+input collection is evaluted according to the map expression body and appended
+to the result.
+
+```sentinel
+l = [1, 2]
+r = map l as v { v % 2 } // [false, true]
+
+m = { "a": "foo", "b": "bar" }
+r = map m as k, v { v } // ["foo", "bar"]
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/language/conditionals.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/language/conditionals.mdx
new file mode 100644
index 0000000000..1736795dbb
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/language/conditionals.mdx
@@ -0,0 +1,161 @@
+---
+page_title: Sentinel Language - Conditionals
+sidebar_current: docs-language-conditional
+description: |-
+ Conditional statements allow your policy to behave differently depending on a condition.
+layout: docs
+---
+
+# Language: Conditionals
+
+Conditional statements allow your policy to behave differently depending
+on a condition.
+
+Conditional statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## If Statements
+
+`if` statements only execute their bodies if a condition is met.
+The syntax of an `if` statement is:
+
+```sentinel
+if condition {
+ // ... this is executed if condition is true
+}
+```
+
+The `condition` must result in a boolean, such as by calling a function
+or evaluating a [boolean expression](/sentinel/language/boolexpr). If
+the `condition` is `true`, the body (within the `{}`) is executed. Otherwise,
+the body is skipped.
+
+Examples:
+
+```sentinel
+// This would execute the body
+value = 12
+if value is 18 {
+ print("condition met")
+}
+
+// Direct boolean values can be used
+value = true
+if value {
+ print("condition met")
+}
+
+// This would not execute the body since the boolean expression will
+// result in undefined.
+value = {}
+if value["key"] > 12 {
+ print("condition met")
+}
+```
+
+### Else, Else If
+
+An `else` clause can be given to an `if` statement to execute a body
+in the case the condition is _not met_. By putting another `if` statement
+directly after the `else`, multiple conditions can be tested for.
+The syntax is:
+
+```sentinel
+if condition {
+ // ...
+} else {
+ // ...
+}
+
+if condition {
+ // ...
+} else if other_condition {
+ // ...
+} else {
+ // ...
+}
+```
+
+### Scoping
+
+The body of an `if` statement does not create a new
+[scope](/sentinel/language/scope). Any variables assigned within the body
+of an if statement will modify the scope that the `if` statement itself
+is in.
+
+Example:
+
+```sentinel
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+```sentinel
+a = 18
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+## Case Statements
+
+`case` statements are a selection control mechanism that execute a clause based
+on matching expressions. It is worth noting that the expression for `case` is
+optional. When no expression is provided, it defaults the expression to `true`.
+Additionally, the order of clauses is important, as they are evaluated from top
+to bottom, executing the first match.
+The syntax of a case statement is:
+
+```sentinel
+case expression {
+ when clause_expression:
+ // executed when clause_expression and expression are equal
+ else:
+ // executed if no clause matches expression
+}
+```
+
+### When Clause
+
+Any clause that has an expression for comparison must use the `when` keyword.
+It accepts a list of expressions, seperated by a `,`.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+}
+```
+
+```sentinel
+case {
+ when x > 40:
+ return true
+}
+```
+
+### Else Clause
+
+The `else` keyword allows for capturing any expressions that have no matching
+`when` clause.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+ else:
+ return false
+}
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/language/functions.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/language/functions.mdx
new file mode 100644
index 0000000000..8214078b6b
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/language/functions.mdx
@@ -0,0 +1,229 @@
+---
+page_title: Sentinel Language - Functions
+sidebar_current: docs-language-functions
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Functions
+
+Functions allow you to create reusable code to perform computations.
+
+Below is a trivial example function that doubles a number and shows
+the main rule using the function:
+
+```sentinel playground
+double = func(x) {
+ return x * 2
+}
+
+main = rule { double(12) is 24 }
+```
+
+Functions may contain any expressions,
+[conditionals](/sentinel/language/conditionals),
+and [loops](/sentinel/language/loops). They must return a value
+at the end of their execution. If no reasonable return value exists,
+the function should return [undefined](/sentinel/language/undefined).
+
+## Function Types
+
+### Named Functions
+
+-> **NOTE:** Named functions must be created within the [package scope](/sentinel/language/scope#package-scope).
+
+Named functions are declared using the `func` keyword as its own statement.
+They provide a safe method of creating functions and have additional
+restrictions that do not apply to anonymous functions.
+
+Firstly, named functions cannot be re-assigned, and also cannot use a name
+that is already used elsewhere. For instance, the below example will error due
+to the attempt to reassign the named function identifier to a new value:
+
+```sentinel
+func sum(a, b) {
+ return a + b
+}
+
+sum = 4
+```
+
+Additionally, the following will error due to the named function attempting
+to make use of an already assigned identifier:
+
+```sentinel
+sum = 4
+
+func sum(a, b) {
+ return a + b
+}
+```
+
+Named functions are helpful for policy authors to declare critical functions
+whose value or implementation should not be changed.
+
+### Anonymous Functions
+
+An anonymous function is created by assigning a variable to a `func`. The
+variable can be reassigned at any time including to different value types.
+Anonymous functions are helpful for use cases like closures, where a function
+can return another function.
+
+```sentinel
+func makeAdder(a) {
+ return func(b) {
+ return a + b
+ }
+}
+```
+
+## Creating a Function
+
+A function can have zero or more parameters. These parameters are specified
+within parentheses `()` separated by commas. If a function has no parameters,
+the parentheses must still be present.
+
+A function must terminate with a `return` statement to return a value back to
+the caller. Only a single value can be returned. The type of a return can
+vary between return statements.
+
+Anonymous function example:
+
+```sentinel
+add1 = func(x) { return x + 1 }
+```
+
+Named function example:
+
+```sentinel
+func add1(x) {
+ return x + 1
+}
+```
+
+Both examples create a function that adds 1 to the parameter `x`.
+
+## Calling a Function
+
+Functions are called by accessing their name followed by parentheses with
+a list of comma-separated values for the parameters. If the function takes no
+parameters, an empty set of parentheses must still be used to denote a
+function call.
+
+To call the function in the example above:
+
+```sentinel
+x = 1
+y = add1(x)
+```
+
+## Scoping
+
+The body of a `func` creates a new [scope](/sentinel/language/scope).
+
+The parent scope is the scope in which the function is created. This allows
+for the creation of functions within functions that can access their outer
+scopes.
+
+Example:
+
+```sentinel
+f = func() {
+ a = 42
+ print(a) // 42
+ return undefined
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+f = func() {
+ a = 42
+ return undefined
+}
+
+print(a) // 18
+```
+
+And below is an example of creating a function in a function which uses
+outer values:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double()
+}
+
+print(f()) // 84
+```
+
+A more complex example below shows how scoping works when passing
+functions around as arguments or results:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double
+}
+
+double = f()
+print(double()) // 84
+```
+
+## Pass By Value
+
+The parameters to a function are passed by value, but not deep copied.
+This means that elements of collections can still be modified but the
+root value cannot be changed.
+
+Example:
+
+```sentinel
+f = func(x) {
+ x = "value"
+ return x
+}
+
+x = "outside"
+f(x)
+print(x) // "outside"
+```
+
+```sentinel
+f = func(x) {
+ append(x, "value")
+ return x
+}
+
+x = []
+f(x)
+print(x) // ["value"]
+```
+
+## Recursion
+
+A function is allowed to call other functions, including itself.
+This can be used to implement recursion. For example, the fibonacci sequence
+is implemented below:
+
+```sentinel
+fib = func(x) {
+ if x <= 0 {
+ return undefined
+ }
+
+ if x == 1 {
+ return 1
+ } else {
+ return x + fib(x - 1)
+ }
+}
+```
+
+Note that this example also shows using
+[undefined](/sentinel/language/undefined)
+as a return value in cases where a function has undefined behavior.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/language/imports.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/language/imports.mdx
new file mode 100644
index 0000000000..0ed1541b85
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/language/imports.mdx
@@ -0,0 +1,116 @@
+---
+page_title: Sentinel Language - Imports
+sidebar_current: docs-language-imports
+description: >-
+ Imports enable a Sentinel policy to access reusable libraries and external
+ data and functions.
+layout: docs
+---
+
+# Language: Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external
+data and functions. The exact list of available imports is dependent on the
+host system. Host systems may also make the available imports configurable
+via plugins.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that
+the import `calendar` allows access to engineer calendars. This import
+only exists for the purpose of this example and at the time of writing is
+not a real available import.
+
+```sentinel
+import "calendar"
+
+// Get the calendar for Bob for today
+bob_calendar = calendar.for("bob").today
+
+// Allow this policy to pass if Bob is not on vacation.
+main = rule { not bob_calendar.has_event("vacation") }
+```
+
+This example shows an import being used to deny a behavior if Bob is on
+vacation. Hopefully real policies do not depend on a single person being
+in the office, but the example shows the power of imports.
+
+In addition to consuming imports, you can also
+[extend Sentinel by writing custom imports](/sentinel/extending)
+This allows you to add any arbitrary behavior to your Sentinel-protected
+systems.
+
+## Importing
+
+Whether accessing a built-in library or an external plugin, the syntax
+for an import is the same:
+
+```sentinel
+import "name"
+```
+
+This adds the import with the name `name`. It can be referenced using
+the identifier `name`. For example, if the import above exposed first
+and last name data, you could access it via `name.first` or `name.last`.
+
+You can shadow imports by assigning a variable. In the example below,
+the import `name` becomes unavailable within the function because it is
+shadowed by a variable of the same name.
+
+Shadowing an import is not the same as overwriting a variable. Imports
+are not part of the [scope](/sentinel/language/scope), meaning that
+the shadow only exists for the scope it is created within. For everything
+else, the import works even after it is shadowed. The example below shows that
+as well.
+
+```sentinel
+import "name"
+
+f = func() {
+ name = "jane"
+ return name
+}
+
+print(f()) // "jane"
+print(name.first) // "bob"
+```
+
+## Aliases
+
+An import can be aliased to another name using the syntax below:
+
+```sentinel
+import "name" as other
+```
+
+This would make the import "name" available as `other`.
+
+An import may only be imported once, regardless of aliases. The following
+would be **invalid** and would result in an error:
+
+```sentinel
+import "name" as one
+import "name" as two
+```
+
+## Accessing Import Data
+
+Imports are accessed with dot-separated identifiers, such as `name.first` or
+`name.stage.first`. These are called _selector expressions_. An import may
+return nested data that further can be accessed via selector expressions.
+
+In the first example on this page with the "calendar" import, we see this:
+
+```sentinel
+import "calendar"
+
+a = calendar.for("bob") // selector expression calendar.for
+b = a.today // selector expression on the result
+c = b.vacation // accessing further nested data
+```
+
+The exact structure of an import is dependent on the import. Please reference
+the documentation for an import to learn more.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/language/index.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/language/index.mdx
new file mode 100644
index 0000000000..7f588c94cd
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/language/index.mdx
@@ -0,0 +1,114 @@
+---
+page_title: Sentinel Language
+sidebar_current: docs-language-basics
+description: |-
+ Sentinel policies are written using the Sentinel language. This language
+ is easy to learn and easy to write. You can learn the Sentinel language
+ and be productive within an hour. Learning Sentinel doesn't require any
+ formal programming experience.
+layout: docs
+---
+
+# Sentinel Language
+
+Sentinel policies are written using the Sentinel language. This language
+is easy to learn and easy to write. You can learn the Sentinel language
+and be productive within an hour. Learning Sentinel doesn't require any
+formal programming experience.
+
+This language guide will contain many details that are not necessary to
+be productive with Sentinel or are targeted to those who are looking for
+exact information about the language (such as what types of line endings are
+allowed). You can safely ignore these sentences.
+
+You may also view the
+[official language specification](/sentinel/language/spec)
+This is a specific and detailed document on the syntax and behavior of
+the language primarily intended for implementation creators and to disambiguate
+the language.
+
+## Simplest Example
+
+The example below is about the simplest practical example of Sentinel.
+It is reasonable to imagine this as a realistic policy. This shows that
+in most cases, Sentinel will be extremely simple:
+
+```sentinel
+main = rule { request.method is "GET" and request.headers contains "X-Key" }
+```
+
+## Files
+
+Sentinel policies are single files that end in the `.sentinel` file extension.
+There is currently no built-in mechanism to Sentinel for merging multiple
+files. This is purposefully done to make Sentinel policies easy to submit
+to systems that support Sentinel policies.
+
+Sentinel policy files must be UTF-8 encoded and can end in both
+Unix (LF) or Windows (CRLF) line breaks. When running the
+[auto-formatter](#),
+line endings will always use Unix line breaks.
+
+## Ordering
+
+Sentinel policies are executed top-down. For example:
+
+```sentinel
+a = 1 // a = 1 here
+b = a + 1 // b = 2 here
+a = 3 // a = 3, b = 2
+```
+
+In this example, the value of `a` and `b` is shown at each line. Since Sentinel
+executes values top-down, the final value of `a` is 3 and `b` is 2. `b` _does
+not_ become 4.
+
+## Main
+
+Sentinel expects there to be a `main` [rule](/sentinel/language/rules).
+The value of this rule is the result of the entire policy.
+
+The result of the policy depends on the evaluated contents of the `main` rule.
+For booleans, a policy passes on a `true` value, and fails on a `false` value.
+Other types generally follow a zero or zero-length pattern for determining
+success.
+
+| Type | Passing Condition | Failing Condition |
+| ------- | ----------------- | -------------------------- |
+| Boolean | `true` | `false` |
+| String | `""` | Any non-zero length string |
+| Integer | `0` | Any non-zero value |
+| Float | `0.0` | Any non-zero value |
+| List | `[]` | Any non-zero length list |
+| Map | `{}` | Any non-zero length map |
+
+A value of main that falls outside of the above types will result in a policy
+error. As a special case, if `main` evaluates to an undefined value, the error
+message will indicate as such, with a reference to where the undefined value was
+encountered.
+
+## More Complex Example
+
+The simple example above is a full working example. In our experience with
+Sentinel, many policies can be representing using this simple form. However,
+to show more features of the language, a more complex example is shown below.
+This example is also a realistic example of what Sentinel may be used for.
+
+```sentinel
+import "units"
+
+memory = func(job) {
+ result = 0
+ for job.groups as g {
+ for g.tasks as t {
+ result += t.resources.memory else 0
+ }
+ }
+
+ return result
+}
+
+main = rule {
+ memory(job) < 1 * units.gigabyte
+}
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/language/lists.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/language/lists.mdx
new file mode 100644
index 0000000000..bc5269d8eb
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/language/lists.mdx
@@ -0,0 +1,135 @@
+---
+page_title: Sentinel Language - Lists
+sidebar_current: docs-language-lists
+description: Lists are a collection of zero or more values.
+layout: docs
+---
+
+# Language: Lists
+
+Lists are a collection of zero or more values.
+
+Lists can be created using by wrapping values in `[]` and separating them
+by commas. An optional trailing comma is allowed. List elements can be
+differing types. Examples:
+
+```sentinel
+[] // An empty list
+["foo"] // Single element list
+["foo", 1, 2, true] // Multi element list with different types
+["foo", [1, 2]] // List containing another list
+```
+
+A list can be [sliced](/sentinel/language/slices) to create sublists.
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for value inclusion in a list.
+
+## Accessing Elements
+
+List elements can be accessed with the syntax `name[index]` where
+`index` is zero-indexed.
+
+A negative index accesses the list in reverse. It is the same as
+reversing a list and then using a positive index. Similar to a positive
+index, it is bounded by the length of the list as a negative value.
+
+Accessing beyond the length of the list results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+a = ["foo", 1, true, [1, 2]]
+
+a[0] // "foo"
+a[2] // true
+a[4] // undefined
+a[-2] // true
+a[-4] // "foo"
+a[-5] // undefined
+a[3][1] // 2
+```
+
+## List Append
+
+Values can be appended to a list using the built-in
+[append](/sentinel/functions/append) function.
+
+This modifies the list in-place and returns
+[undefined](/sentinel/language/undefined). For more information on
+why `append` behaves this way, please read the full documentation for the
+[append](/sentinel/functions/append) function.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append([1,2], "foo") // [1, 2, "foo"]
+append([1,2], [3]) // [1, 2, [3]]
+append(1, 3) // error()
+```
+
+## List Concatenation
+
+Two lists can be concatenated using the `+` operator or the shorthand
+`+=` assignment operator. For the `+` operator, a new list is returned.
+For `+=`, the left-hand list is modified in place.
+
+Examples:
+
+```sentinel
+[1] + [2] // [1, 2]
+[1] + [[1]] // [1, [1]]
+[1] + 1 // error
+
+a = [1]
+a += [2] // a = [1, 2]
+a += 3 // error
+```
+
+## List Length
+
+The length of a list can be retrieved using the
+[length](/sentinel/functions/length) function.
+
+Examples:
+
+```sentinel
+length([]) // 0
+length(["foo"]) // 1
+```
+
+## Removing Items From a List
+
+You can use a combination of [list concatenation](#list-concatenation) and
+[slicing](/sentinel/language/slices) to remove elements from a list.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+a = a[:2] + a[3:] // [1, 2, 4, 5]
+```
+
+The shorthand shown here is effectively the same as `a[0:2] + a[3:length(a)]`,
+which creates a new list out of the concatenation two sub-lists composed of the
+first two elements, and the rest of the list starting at index 3. This
+effectively removes the 3rd element from the list (index 2).
+
+## List Comparison
+
+Lists may be [compared](/sentinel/language/boolexpr#comparison-operators)
+for equality. Lists are equal if they are of equal length and their
+corresponding elements are comparable and equal. Lists with the same elements
+but in a different order are not equal.
+
+```sentinel
+[1, 2] is [1, 2] // true
+[1, 2] is [2, 1] // false
+["a"] is ["a", "b"] // false
+["a", ["b", "c"]] is ["a", ["b", "c"]] // true
+```
+
+List comparison speed is O(N), meaning that the speed of the comparison is
+_linearly_ proportional to the number of elements in the list. The more
+elements, the more iterations that are necessary to verify equality.
+
+-> The N-value quoted above should account for the sum of the elements of _all_
+lists in the subjects of comparison, as list comparison will recurse into these
+lists to check for equality.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/language/logging-errors.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/language/logging-errors.mdx
new file mode 100644
index 0000000000..705a72cbd4
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/language/logging-errors.mdx
@@ -0,0 +1,52 @@
+---
+page_title: Sentinel Language - Logging and Errors
+sidebar_current: docs-language-log
+description: The built-in functions `print` and `error` can be used for logging and errors.
+layout: docs
+---
+
+# Language: Logging and Errors
+
+The built-in functions `print` and `error` can be used for logging
+and errors. Logging is helpful to understand how a policy is executing
+in development. And errors are useful to halting execution immediately in
+certain scenarios.
+
+## Print
+
+The `print` function is used to output debug information. The exact location
+where this print statements go is dependent on the host application and
+the Sentinel runtime itself.
+
+The `print` function takes zero or more Sentinel values as arguments.
+Each value is formatted for human-friendly output and space-separated.
+Example:
+
+```sentinel
+value = 42
+print("the value is", value) // the value is 42
+
+map = { "foo": false }
+print(map) // { "foo": false }
+
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // false
+```
+
+## Errors
+
+Certain actions in Sentinel, such as accessing an undefined variable,
+attempting to add two incompatible types, etc. result in _runtime errors_.
+These errors halt the execution of a policy immediately. The pass/fail result
+of a policy is considered fail.
+
+Runtime errors can be manually triggered using the `error` function.
+The `error` function behaves exactly like the `print` function except that
+execution is halted immediately.
+
+This should only be used in scenarios where the policy _must fail_ due to
+some unexpected or invalid condition. The line between `error` and
+[undefined](/sentinel/language/undefined) can sometimes be unclear. Undefined
+is recommended when a policy can conceivably recover or safely ignore the
+undefined behavior. An error should be used when the policy should fail and
+notify someone regardless.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/language/loops.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/language/loops.mdx
new file mode 100644
index 0000000000..622732bd35
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/language/loops.mdx
@@ -0,0 +1,92 @@
+---
+page_title: Sentinel Language - Loops
+sidebar_current: docs-language-loops
+description: >-
+ Loop statements allow you to execute a body of code for each element in a
+ collection or for some fixed number of times.
+layout: docs
+---
+
+# Language: Loops
+
+Loop statements allow you to execute a body of code for each element in
+a collection or for some fixed number of times.
+
+Loop statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## For Statements
+
+`for` statements allow repeated execution of a block for each
+element in a collection.
+
+Example:
+
+```sentinel
+// A basic sum
+count = 0
+for [1, 2, 3] as num {
+ count += num
+}
+```
+
+The syntax is `for COLLECTION as value`. This will iterate over the
+collection, assigning each element to `value`. In the example above,
+each element is assigned to `num`. The body is executed for each element.
+In the example above, the body adds `num` to the `count` variable. This
+creates a basic sum of all values in the collection.
+
+For a map, the assigned element is the key in the map. In the example
+below, `name` would be assigned map keys.
+
+```sentinel
+list = []
+for { "a": 1, "b": 2 } as name {
+ append(list, name)
+}
+
+print(list) // ["a" "b"]
+```
+
+An alternate syntax is `for COLLECTION as key, value`. This will assign
+both the key and value to a variable. For a list, the key is the element
+index. For a map, it is the key and value is assigned the element value.
+Example:
+
+```sentinel
+count = 0
+for { "a": 1, "b": 2 } as name, num {
+ count += num
+}
+
+print(count) // 3
+```
+
+## Scoping
+
+The body of a `for` statement creates a new
+[scope](/sentinel/language/scope). If a variable is assigned within
+the body of a for statement that isn't assigned in a parent scope,
+that variable will only exist for the duration of the body execution.
+
+Example:
+
+```sentinel
+for list as value {
+ a = 42
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+for list as value {
+ a = 42
+}
+
+print(a) // 18
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/language/maps.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/language/maps.mdx
new file mode 100644
index 0000000000..298bf9a567
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/language/maps.mdx
@@ -0,0 +1,121 @@
+---
+page_title: Sentinel Language - Maps
+sidebar_current: docs-language-maps
+description: >-
+ Maps are a collection of zero or more key/value pairs. These are useful for
+ storing values that are looked up by a unique key.
+layout: docs
+---
+
+# Language: Maps
+
+Maps are a collection of zero or more key/value pairs. These are useful
+for storing values that are looked up by a unique key.
+
+Maps can be created using `{}` and specifying key/value pairs. Keys and
+values can be differing types. An optional trailing comma is allowed.
+Examples:
+
+```sentinel
+// Empty map
+{}
+
+// Map with a single value on one line
+{ "key": "value" }
+
+// Map with multiple values with differing types on multiple lines
+{
+ "key": "value",
+ 42: true,
+}
+```
+
+Maps are **unordered**. When
+[looping](/sentinel/language/loops)
+over a map, the key/value pairs can be returned in any order.
+
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for key inclusion in a map.
+
+## Accessing Elements
+
+Map elements can be accessed with the syntax `name[key]`. This looks up
+a value by a key.
+
+Accessing a key that doesn't exist results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+map = { "key": "value", 42: true, }
+
+map["key"] // "value"
+map[42] // true
+map[0] // undefined
+```
+
+## Modifying or Adding Elements
+
+Elements can be added or modified in a map by assigning to `name[key]`.
+If the key doesn't exist, the value is added. If the key already exists,
+the value is overridden.
+
+```sentinel
+map = { "key": "value" }
+
+map[42] = true // Add a new key/value
+map["key"] = 12 // Modify the value of "key"
+```
+
+## Deleting Elements
+
+An element can be deleted from a map using the
+[delete](/sentinel/functions/delete) function.
+
+Examples:
+
+```sentinel
+map = { "key": "value" }
+delete(map, "key") // map is now empty
+delete(map, "other") // no effect for non-existent key
+```
+
+## Keys and Values
+
+The keys and values of a map can be retrieved as
+[lists](/sentinel/language/lists) using the
+[keys](/sentinel/functions/keys) and
+[values](/sentinel/functions/values) functions.
+
+Because maps are unordered, the keys and values are returned in an
+unspecified order. It should not be assumed that keys and values will be
+returned in a consistent order.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+## Map Comparison
+
+Maps may be [compared](/sentinel/language/boolexpr#comparison-operators) for
+equality. Maps are equal if they are of equal length and both their
+corresponding keys and values are comparable and equal.
+
+```sentinel
+{"foo": "bar"} is {"foo": "bar"} // true
+{"foo": "bar"} is {"baz": "bar"} // false
+{"foo": "bar"} is {"foo": "baz"} // false
+{"foo": "bar"} is {"foo": "bar", "baz": "qux"} // false
+{1: "a"} is {1.0: "a"} // true (int/float comparable)
+
+// also true (maps are not ordered):
+{"m": {"a": "b"}, "l": ["a"]} is {"l": ["a"], "m": {"a": " b"}}
+```
+
+-> The current worst-case comparison speed for maps is O(N²), or _quadratic
+time_. This is the square of the cumulative elements or the parent and all child
+maps contained within the map. Consider this when making comparisons on larger,
+more complex maps. This may be optimized in the future.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/language/parameters.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/language/parameters.mdx
new file mode 100644
index 0000000000..2e008d469b
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/language/parameters.mdx
@@ -0,0 +1,146 @@
+---
+page_title: Sentinel Language - Parameters
+sidebar_current: docs-language-parameters
+description: >-
+ Parameteres allow a Sentinel policy to describe variables that are expected
+ to be supplied by the calling application, in addition to any default value.
+layout: docs
+---
+
+# Language: Parameters
+
+Sentinel allows a policy author to supply parameters to help facilitate policy
+reuse and ensure sensitive values do not need to be hard-coded in a policy.
+
+Parameters are supplied by using the `param` keyword, followed by an identifier.
+A default value can also be supplied by using the `default` keyword.
+
+```sentinel
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+```
+
+Once declared, parameters can be used like any other variable, including being
+re-assigned.
+
+```sentinel
+param foo default 1 // 1 (default)
+
+foo = foo + 1 // 2
+```
+
+## Variable Descriptions
+
+You can supply a description to a parameter by adding a comment at the top of
+it. This value can be communicated to a specific implementation of Sentinel to
+provide information about what the parameter is for during configuration.
+
+```sentinel
+// An example parameter. Must be supplied or the policy will fail.
+param foo
+```
+
+## Supplying Parameter Values Using the Sentinel CLI
+
+In a production implementation, supplying parameters to a policy is an
+implementation-specific detail - see the documentation for your particular
+implementation for details.
+
+Using the [Sentinel CLI](/sentinel/commands), you can supply parameters one of
+four ways.
+
+### Supplying Parameter Values Using the Configuration File
+
+You can supply parameters using the
+[`param`](/sentinel/configuration#parameters) section of the
+[configuration file](/sentinel/configuration).
+
+```hcl
+param "foo" {
+ value = "bar"
+}
+```
+
+This method works for both `sentinel apply` and `sentinel test`.
+
+In addition to the above, you can supply targeted parameters to each
+[policy block](/sentinel/configuration#policies) in the configuration file.
+
+```hcl
+policy "foo" {
+ source = "foo.sentinel"
+ enforcement_level = "hard-mandatory"
+ params = {
+ "name" = "Sample"
+ }
+}
+```
+
+### Supplying Parameter Values Using the Environment
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can supply a value using environment variables - prefix the parameter with
+`SENTINEL_PARAM_`, using the name of the parameter to supply.
+
+```plaintext
+SENTINEL_PARAM_foo=bar sentinel apply policy.sentinel
+```
+
+### Supplying Parameter Values Using CLI Arguments
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can also use the `-param` CLI argument to supply parameter in a `key=value`
+pair.
+
+```plaintext
+sentinel apply -param foo=bar policy.sentinel
+```
+
+### Interactive CLI Prompting
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+If a required value has not been supplied when a policy is run with `sentinel apply`, it will be prompted for, along with its description:
+
+
+
+ $ sentinel apply policy.sentinel
+
+ policy.sentinel:2:7: requires value for parameter foo
+
+ An example parameter. Must be supplied or the policy will fail.
+
+
+ Values can be strings, floats, or JSON array or object values. To
+ force
+
+ strings, use quotes.
+
+
+ Enter a value: bar
+
+
+
+ Pass
+
+
+
+
+### CLI Value Format
+
+-> **NOTE:** This section contains details for the parameter features supported by `sentinel apply`.
+
+The CLI takes either strings, or JSON numbers, arrays, or maps. If you need a
+literal string value, quote the value.
+
+```sentinel
+foo // string
+42 // number (float)
+"42" // string ("42", without quotes)
+[1, 2] // array (list)
+{"a": "b"} // object (map)
+```
+
+-> **NOTE:** Boolean values are not supported by this method.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/language/rules.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/language/rules.mdx
new file mode 100644
index 0000000000..e31924d414
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/language/rules.mdx
@@ -0,0 +1,204 @@
+---
+page_title: Sentinel Language - Rules
+sidebar_current: docs-language-rules
+description: >-
+ Rules form the basis of a policy by representing behavior that is either
+ passing or failing (true or false).
+layout: docs
+---
+
+# Language: Rules
+
+Rules form the basis of a policy by representing behavior that is either passing
+or failing. A policy can be broken down into a set of rules. Breaking down a
+policy into a set of rules can make it more understandable and aids with
+testing.
+
+An example is shown below:
+
+```sentinel playground
+weather = "sunny"
+day = "wednesday"
+is_sunny = rule { weather is "sunny" }
+is_wednesday = rule { day is "wednesday" }
+
+main = rule {
+ is_sunny and
+ is_wednesday
+}
+```
+
+A rule contains a single expression. This can be a simple [boolean
+expression](/sentinel/language/boolexpr), or an expression representing the
+discovery of a set of violations using more in-depth expressions like [`filter`
+and `map`](/sentinel/language/collection-operations). You can split expressions
+into multiple lines for readability, as you would with any other expression
+within Sentinel.
+
+A rule is also _lazy_ and _memoized_. _Lazy_ means that the rule is only
+evaluated when it is actually required, not at the point that it is
+created. This is covered in more detail in the [lazy](#lazy-and-memoized) section.
+_Memoized_ means that the value is only computed once and then saved. Once
+a rule is evaluated, the result of it is reused anytime the rule is referenced
+again.
+
+## Making rules easier to understand
+
+Rules are an abstraction that make policies much more understandable
+by making it easier for humans to see what the policy is trying to do.
+
+For example, consider the policy before which doesn't abstract into rules:
+
+```sentinel
+main = rule {
+ ((day is "saturday" or day is "sunday") and homework is "") or
+ (day in ["monday", "tuesday", "wednesday", "thursday", "friday"] and
+ not school_today and homework is "")
+}
+```
+
+Assume that `day`, `homework`, and `school_day` are available.
+
+In plain English, this policy is trying to say: you can play with your friends
+only on the weekend as long as there is no homework, or during the week if
+there is no school and no homework.
+
+Despite the relatively simple nature of this policy (a few lines, about 5
+logical expressions), it is difficult to read and understand, especially if
+you've not seen it before.
+
+The same policy using rules as an abstraction:
+
+```sentinel
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+is_valid_weekend = rule { is_weekend and homework is "" }
+is_valid_weekday = rule { not is_weekend and not school_today and homework is "" }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+By reading the names of the rules, its much clearer to see what each individual
+part of the policy is trying to achieve. The readability is improved even
+further when adding comments to the rules to explain them further, which is
+a recommended practice:
+
+```sentinel
+// A weekend is Sat or Sun
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+// A valid weekend is a weekend without homework
+is_valid_weekend = rule { is_weekend and homework is "" }
+
+// A valid weekday is a weekday without school
+is_valid_weekday = rule { not is_weekend and not school_today and homework is "" }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+## "When" Predicates
+
+A rule may have an optional `when` predicate attached to it. When this is
+present, the rule is only evaluated when the predicate results in `true`.
+Otherwise, the rule is not evaluated and the result of the rule is always
+`true`.
+
+"When" predicates should be used to define the context in which the rule
+is semantically meaningful. It is common when translating human language
+policy into Sentinel to have scenarios such as "when the key has a prefix
+of `/account/`, the remainder must be numeric." In that example, when the
+key _does not_ have the specified prefix, the policy doesn't apply.
+
+Assume you have rules `is_prefix` and `is_numeric` to check both the
+conditions in the example above. An example is shown with and without
+the "when" predicate:
+
+```sentinel
+example_no_when = rule { (is_prefix and is_numeric) or not is_prefix }
+
+example_when = rule when is_prefix { is_numeric }
+```
+
+The rules are equivalent in behavior for all values of `is_prefix` and
+`is_numeric`, but the second rule is more succint and easier to understand.
+
+## Testing
+
+To verify a policy works correct, the
+[built-in Sentinel test framework](/sentinel/writing/testing)
+uses rules as the point where you can assert behavior. You say that
+you expect certain rules to be true or false. By doing so, you ensure that
+the policy results in the value you expect using the rule flow that you
+expect.
+
+Therefore, in addition to readability, we recommend splitting policies into
+rules to aid testability.
+
+## Non-Boolean Values
+
+Sentinel supports non-boolean values in rules. This can be useful for passing
+more detail along to a calling integration when more detail is required than a
+boolean value would be able to communicate. This data shows up in the [policy
+trace](/sentinel/writing/tracing) and can be utilized in different ways
+depending on the integration you are using Sentinel with.
+
+Consider a different take on our policy above:
+
+```sentinel
+// A policy to check a set of scheduled days to determine what days you can't
+// come out and play, based on the day not being a weekend day and there being
+// homework to do.
+
+param days
+
+main = rule {
+ filter days as d {
+ d.day not in ["saturday", "sunday"] and
+ d.homework is not ""
+ }
+}
+```
+
+When given a value in the set of `days` that has a `day` that is a weekday, and
+`homework` present, the policy will fail. Additional, when tracing was enabled,
+the days that were detected as violating the policy would be given in the trace
+for the `main` rule.
+
+Generally, for non-boolean values, a policy fails on non-zero data. See the
+details on [the `main` rule](/sentinel/language#main) for more details.
+
+The result of a rule must be either a boolean, string, integer, float, list, or
+map. All other types will result in a runtime error.
+
+## Lazy and Memoized
+
+A rule is _lazy_ and _memoized_.
+
+_Lazy_ means that the rule is only evaluated when it is actually required,
+not at the point that it is created. And _memoized_ means that the value is
+only computed once and then saved. Once a rule is evaluated, the result of it
+is reused anytime the rule is referenced again.
+
+Both of these properties have important implications for Sentinel
+policies:
+
+**Performance:** Rules are a way to improve the performance of a Sentinel
+policy. If you have a boolean expression that is reused a lot, a rule will
+only be evaluated once. In the example shown above, `is_weekend` is used
+multiple times, but will only have to be evaluated once.
+
+**Behavior:** Rules accessing variables see the value of those variables
+at _the time they are evaluated_. This can lead to surprising behavior
+in some cases. For example:
+
+```sentinel playground
+a = 1
+b = rule { a == 1 }
+a = 2
+main = b
+```
+
+In this example, `main` will actually result to `false`. It is evaluated
+when it is needed, which is when the policy executes `main`. At this point,
+`a` is now 2 and `b` has not been evaluated yet. Therefore, `b` becomes `false`.
+All future references to `b` will return `false` since a rule is memoized.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/language/scope.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/language/scope.mdx
new file mode 100644
index 0000000000..f498e26dc2
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/language/scope.mdx
@@ -0,0 +1,47 @@
+---
+page_title: Sentinel Language - Scope
+sidebar_current: docs-language-scope
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Scope
+
+Scope is the context in which variables are created and accessed. If a
+variable exists in a parent scope, it is accessed and can be modified.
+Otherwise, the variable is created in your current scope.
+
+Scopes are created with _blocks_. A block is a possibly empty sequence
+of statements within matching brace brackets `{}`. You may nest blocks
+to create nested scopes.
+
+## Package Scope
+
+The package scope is the top level scope within a policy file and encapsulates
+the entire file contents. Imports, parameters and named functions must be
+declared within the package scope.
+
+## Implicit Scopes
+
+Each `any`, `all`, and `for` statement is considered to be in its own block.
+Note that `if` statements _do not_ create their own block.
+
+## Examples
+
+The example policy below shows various effects of scopes:
+
+```sentinel
+a = 1
+print(a) // 1
+
+f = func() {
+ print(a) // 1
+ a = 12
+ b = 1
+ return undefined
+}
+f()
+
+print(a) // 12
+print(b) // undefined
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/language/slices.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/language/slices.mdx
new file mode 100644
index 0000000000..82dc7f4c18
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/language/slices.mdx
@@ -0,0 +1,45 @@
+---
+page_title: Sentinel Language - Slices
+sidebar_current: docs-language-slices
+description: >-
+ Slices are an efficient way to create a substring or sublist from an existing
+ string or list, respectively.
+layout: docs
+---
+
+# Language: Slices
+
+Slices are an efficient way to create a substring or sublist from an
+existing string or list, respectively.
+
+The syntax for creating a slice is:
+
+```sentinel
+a[low : high]
+```
+
+`low` is the lowest index to slice from. The resulting slice includes
+the value at `low`. `high` is the index to slice to. The resulting slice
+will not include the value at `high`. The length of the resulting list
+or string is always `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+
+a = "hello"
+b = a[1:4] // "ell"
+```
+
+## Convenience Shorthands
+
+Some convenience shorthands are available by omitting the value for either the
+low or high index in the slice expression: omitting `low` implies a low index of
+0, and omitting `high` implies a high index of the length of the list.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+
+b = a[:2] // [1, 2] (same as a[0:2])
+b = a[2:] // [3, 4, 5] (same as a[2:length(a)])
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/language/spec.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/language/spec.mdx
new file mode 100644
index 0000000000..d8efb378cd
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/language/spec.mdx
@@ -0,0 +1,1333 @@
+---
+page_title: Sentinel Language Specification
+sidebar_current: docs-language-spec
+description: This is the specification for the Sentinel policy language.
+layout: docs
+---
+
+# Sentinel Language Specification
+
+This is the specification for the Sentinel policy language.
+
+The Sentinel language is designed with policy enforcement in mind. It is dynamically typed and garbage collected and has explicit support for _rule_ construction representing boolean logic.
+
+The language is designed to be easy to learn and use by non-programmers. It is expected to be embedded within applications.
+
+## Table of Contents
+
+
+
+
+- [Source code representation](#source-code-representation)
+- [Declarations and Scope](#declarations-and-scope)
+- [Blocks](#blocks)
+- [Lexical Elements](#lexical-elements)
+ - [Comments](#comments)
+ - [Identifiers](#identifiers)
+ - [Keywords](#keywords)
+ - [Operators and Delimiters](#operators-and-delimiters)
+ - [Integer Literals](#integer-literals)
+ - [Floating-point Literals](#floating-point-literals)
+ - [String Literals](#string-literals)
+ - [Implicit Line Joining](#implicit-line-joining)
+ - [Whitespace](#whitespace)
+ - [Semicolons](#semicolons)
+- [Variables](#variables)
+- [Undefined](#undefined)
+- [Expressions](#expressions)
+ - [Operand](#operand)
+ - [Primary Expressions](#primary-expressions)
+ - [Null](#null)
+ - [Booleans](#booleans)
+ - [Boolean Literals](#boolean-literals)
+ - [Boolean Expressions](#boolean-expressions)
+ - [List Literals](#list-literals)
+ - [Map Literals](#map-literals)
+ - [Function Literals](#function-literals)
+ - [Rule Expressions](#rule-expressions)
+ - [Index Expressions](#index-expressions)
+ - [Selectors](#selectors)
+ - [Slice Expressions](#slice-expressions)
+ - [Calls](#calls)
+ - [Operators](#operators)
+ - [Operator Precedence](#operator-precedence)
+ - [Arithmetic Operators](#arithmetic-operators)
+ - [Integer operators](#integer-operators)
+ - [Integer overflow](#integer-overflow)
+ - [Floating-point operators](#floating-point-operators)
+ - [String Concatenation](#string-concatenation)
+ - [List Concatenation](#list-concatenation)
+ - [Comparison Operators](#comparison-operators)
+ - [Logical Operators](#logical-operators)
+ - [Set Operators](#set-operators)
+ - [Matches Operator](#matches-operator)
+ - [Else Operator](#else-operator)
+ - [Quantifier Expressions (any, all, filter, map)](#quantifier-expressions-any-all-filter-map)
+- [Statements](#statements)
+ - [Expression Statements](#expression-statements)
+ - [Assignments](#assignments)
+ - [If Statements](#if-statements)
+ - [Case Statements](#case-statements)
+ - [For Statements](#for-statements)
+ - [Break Statements](#break-statements)
+ - [Continue Statements](#continue-statements)
+ - [Return Statements](#return-statements)
+- [Imports](#imports)
+- [Parameters](#parameters)
+- [Built-in Functions](#built-in-functions)
+ - [Length](#length)
+ - [Collections](#collections)
+ - [List Append](#list-append)
+ - [Map Delete](#map-delete)
+ - [Keys and Values](#keys-and-values)
+ - [Range](#range)
+ - [Type Conversion](#type-conversion)
+ - [Printing](#printing)
+ - [Errors](#errors)
+- [Program Execution](#program-execution)
+
+
+
+## Source code representation
+
+Source code is Unicode text encoded in UTF-8. A "character" referenced in this document refers to a Unicode code point. Each code point is distinct: upper and lower case letters are different characters.
+
+The underscore character \_ (U+005F) is considered a "letter".
+
+```ebnf
+letter = unicode_letter | "_" .
+decimal_digit = "0" … "9" .
+octal_digit = "0" … "7" .
+hex_digit = "0" … "9" | "A" … "F" | "a" … "f" .
+```
+
+## Declarations and Scope
+
+A declaration binds an identifier to a value. An identifier is declared at the point it is first assigned. An assignment is considered a first assignment if the identifier hasn't already been previously declared in the current scope or any parent scopes.
+
+The scope of an identifier is the extent of source text in which the identifier denotes the specified value. Sentinel is lexically scoped using blocks.
+
+An identifier declared in a block may be redeclared in an inner block. While the identifier of the inner declaration is in scope, it denotes the value assigned in the inner declaration.
+
+## Blocks
+
+A block is a possibly empty sequence of statements within matching brace brackets.
+
+```ebnf
+Block = "{" StatementList "}" .
+StatementList = { Statement ";" } .
+```
+
+Blocks nest and affect scoping.
+
+In addition to explicit blocks in the source code, there are implicit blocks:
+
+1. The universe block encompasses all source text.
+2. Each program has a program block containing all source text for that program.
+3. Each "any", "all", and "for" statements is considered to be in its own implicit block. "if" does not create an implicit block.
+
+## Lexical Elements
+
+### Comments
+
+Comments are sections of source text used for documentation.
+
+```plaintext
+# Single line comment
+// Single line comment
+/* multi
+line
+ comment */
+```
+
+Three forms of comments are supported: two single-line forms and one multi-line form. A single-line comment begins with the token `//` or `#`. Everything between the starting token and the end of the line is ignored.
+
+A multi-line comment begins with the token `/*` and ends with the token `*/`. Everything between `/*` and `*/` is ignored.
+
+A comment may not start inside a string literal or inside another comment.
+
+A multi-line comment containing no newlines acts like a space.
+
+### Identifiers
+
+Identifiers name program entities such as rules, variables, and functions. An identifier is a sequence of one or more letters and digits. The first character in an identifier must be a letter.
+
+```ebnf
+identifier = letter { letter | unicode_digit } .
+```
+
+```sentinel
+a
+_a
+_A
+αβ
+```
+
+#### Pre-Declared Identifiers
+
+The following identifiers are implicitly declared in the [universe block](#blocks):
+
+```plaintext
+Constants:
+ false null true undefined
+
+Functions:
+ append bool delete error float int keys length print range string values
+```
+
+### Keywords
+
+The following keywords are reserved and may not be used as identifiers:
+
+```plaintext
+all
+any
+as
+break
+case
+continue
+default
+else
+empty
+filter
+for
+func
+if
+import
+map
+param
+return
+rule
+when
+```
+
+### Operators and Delimiters
+
+The following character sequences represent operators, delimiters, and other special tokens:
+
+```plaintext
++
+-
+*
+/
+%
++=
+-=
+*=
+/=
+%=
+==
+<
+>
+=
+!
+!=
+<=
+>=
+( )
+[ ]
+{ }
+,
+.
+:
+;
+and
+contains
+else
+in
+is
+matches
+not
+or
+xor
+```
+
+As a special case, `else` is both a keyword and operator, depending on the context. See [Else Operator](#else-operator) for more details.
+
+### Integer Literals
+
+An integer literal is a sequence of digits representing an integer constant. An optional prefix sets a non-decimal base: `0` for octal, `0x` or `0X` for hexadecimal. In hexadecimal literals, letters `a-f` and `A-F` represents values 10 through 15.
+
+Integers are signed 64-bit values (-9223372036854775808 to 9223372036854775807).
+
+```ebnf
+int_lit = decimal_lit | octal_lit | hex_lit .
+decimal_lit = ( "1" … "9" ) { decimal_digit } .
+octal_lit = "0" { octal_digit } .
+hex_lit = "0" ( "x" | "X" ) hex_digit { hex_digit } .
+```
+
+```
+42
+0600
+0xBadFace
+170141183460469231731687303715884105727
+```
+
+### Floating-point Literals
+
+A floating-point literal is a decimal representation of a floating-point constant. It has an integer part, a decimal point, a fractional part, and an exponent part. The integer and fractional part comprise decimal digits; the exponent part is an e or E followed by an optionally signed decimal exponent. One of the integer part or the fractional part may be elided; one of the decimal point or the exponent may be elided.
+
+Floating-point numbers are IEEE-754 64-bit floating numbers.
+
+```ebnf
+float_lit = decimals "." [ decimals ] [ exponent ] |
+ decimals exponent |
+ "." decimals [ exponent ] .
+decimals = decimal_digit { decimal_digit } .
+exponent = ( "e" | "E" ) [ "+" | "-" ] decimals .
+```
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+### String Literals
+
+String literals are character sequences between double quotes, as in "bar". Within the quotes, any character may appear except newline and unescaped double quote. The text between the quotes forms the value of the literal.
+
+A multi-character sequence beginning with a backslash encode values in various formats.
+
+Backslash escapes allow arbitrary values to be encoded as ASCII text. There are four ways to represent the integer value as a numeric constant: `\x` followed by exactly two hexadecimal digits; `\u` followed by exactly four hexadecimal digits; `\U` followed by exactly eight hexadecimal digits, and a plain backslash `\` followed by exactly three octal digits. In each case the value of the literal is the value represented by the digits in the corresponding base.
+
+After a backslash, certain single-character escapes represent special values:
+
+```plaintext
+\a U+0007 alert or bell
+\b U+0008 backspace
+\f U+000C form feed
+\n U+000A line feed or newline
+\r U+000D carriage return
+\t U+0009 horizontal tab
+\v U+000b vertical tab
+\\ U+005c backslash
+\" U+0022 double quote
+```
+
+The three-digit octal (\nnn) and two-digit hexadecimal (\xnn) escapes represent individual bytes of the resulting string; all other escapes represent the (possibly multi-byte) UTF-8 encoding of individual characters. Thus inside a string literal \377 and \xFF represent a single byte of value 0xFF=255, while ÿ, \u00FF, \U000000FF and \xc3\xbf represent the two bytes 0xc3 0xbf of the UTF-8 encoding of character U+00FF.
+
+A string value is a (possibly empty) sequence of bytes. Strings are immutable: once created, it is impossible to change the contents of a string.
+
+The length of a string s (its size in bytes) can be discovered using the built-in function `length`. An individual character (of type string) can be accessed by integer indices 0 through `length(s)-1`.
+
+```ebnf
+string_lit = `"` { unicode_value | byte_value } `"` .
+unicode_value = unicode_char | little_u_value | big_u_value | escaped_char .
+byte_value = octal_byte_value | hex_byte_value .
+octal_byte_value = `\` octal_digit octal_digit octal_digit .
+hex_byte_value = `\` "x" hex_digit hex_digit .
+little_u_value = `\` "u" hex_digit hex_digit hex_digit hex_digit .
+big_u_value = `\` "U" hex_digit hex_digit hex_digit hex_digit
+ hex_digit hex_digit hex_digit hex_digit .
+escaped_char = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | `"` ) .
+```
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+### Implicit Line Joining
+
+Expressions can be split over more than one line. For example:
+
+```sentinel
+a or
+b or # This is a comment
+c
+```
+
+Implicitly continued lines can have trailing comments. Blank continued lines are allowed.
+
+### Whitespace
+
+Whitespace is needed to separate tokens, but no distinction is made between the number and combination of whitespace characters. Whitespace characters can be space (U+0020), horizontal tabs (U+0009), carriage returns (U+000D), and newlines (U+000A).
+
+```sentinel
+a or b
+
+a or b
+
+a or
+ b
+
+rule { a or b }
+
+rule {
+ a or b }
+
+rule {
+ a or
+ b
+}
+```
+
+### Semicolons
+
+The formal grammar uses semicolons ";" as terminators in a number of productions.
+It is idiomatic Sentinel source to omit semicolons in most cases.
+
+A semicolon is automatically inserted into the token stream immediately after
+a line's final token if that token is:
+
+- An identifier
+- An integer, float, or string literal
+- The keyword `break`, `continue`, or `return`
+- The delimiter `)`, `]`, or `}`
+
+## Variables
+
+A variable is a storage location for holding a value.
+
+A variable has a dynamic type, which is the concrete type of the value assigned to the variable at run time. The dynamic type may vary during execution and change as a result of further assignments.
+
+A variable's value is retrieved by referring to the variable in an expression; it is the most recent value assigned to the variable. If a variable has not yet been declared (initially assigned), it is an error.
+
+```sentinel
+x = 7 // x is type int
+x = "foo" // x is now type string
+
+x = y // error if y is not previously assigned
+```
+
+## Undefined
+
+The value denoted by the keyword `undefined` represents undefined behavior or values. It can be created directly using the keyword `undefined`. It is also returned as a result of expressions in specified cases.
+
+`undefined` is a valid operand for any operations. Only `undefined or true` will result in true. All other operations result in `undefined`. An exception is if `undefined` is not reached as a result of short-circuit operations.
+
+```sentinel
+undefined OR true = true
+undefined OR false = undefined
+undefined OR undefined = undefined
+undefined AND true = undefined
+undefined AND false = undefined
+undefined AND undefined = undefined
+undefined XOR true = undefined
+undefined XOR false = undefined
+undefined XOR undefined = undefined
+
+// Short-circuit examples
+false OR true OR undefined = true
+false OR undefined OR true = true
+true AND false AND undefined = false
+true AND undefined AND false = undefined
+
+// Non-logical operators
+undefined + 5 = undefined
+-undefined = undefined
+!undefined = undefined
+```
+
+If the result of the main rule is `undefined`, it is treated as `false` but is indicative of erroneous logic.
+
+## Expressions
+
+### Operand
+
+Operands denote the elementary values in an expression. An operand may be a literal, an identifier denoting a variable, rule, or function, or a parenthesized expression.
+
+```ebnf
+Operand = Literal | OperandName | MethodExpr | "(" Expression ")" .
+Literal = BasicLit | MapLit | ListLit | FunctionLit | RuleLit .
+BasicLit = int_lit | float_lit | string_lit .
+OperandName = identifier .
+```
+
+### Primary Expressions
+
+Primary expressions are the operands for unary and binary expressions.
+
+```ebnf
+PrimaryExpr =
+ Operand |
+ PrimaryExpr Selector |
+ PrimaryExpr Index |
+ PrimaryExpr Slice |
+ PrimaryExpr Arguments .
+
+Selector = "." identifier .
+Index = "[" Expression "]" .
+Slice = "[" [ Expression ] ":" [ Expression ] "]" .
+Arguments = "(" [ Expression { "," Expression } ] ")" .
+```
+
+```sentinel
+x
+2
+(s + ".txt")
+f(3.1415, true)
+m["foo"]
+s[i : j + 1]
+obj.color
+f.p[i].x()
+x is empty
+x is not empty
+```
+
+### Null
+
+The reserved word `null` denote the singleton value `null`. Null represents the explicit absence of a value. Behavior of `null` within expressions is specified explicitly for each expression.
+
+### Booleans
+
+### Boolean Literals
+
+The reserved words `true` and `false` denote objects that represent the boolean values `true` and `false`, respectively. These are boolean literals.
+
+```ebnf
+BoolLit = "true" | "false" .
+```
+
+#### Boolean Expressions
+
+Boolean expressions are expressions that must result in a boolean value. Any other value type becomes the `undefined` value.
+
+```ebnf
+BoolExpr = Expression .
+```
+
+### List Literals
+
+A list literal denotes a list, which is an integer indexed collection of values.
+
+```ebnf
+ListLit = "[" [ ElementList [ "," ] ] "]" .
+ElementList = Element { "," Element } .
+Element = Expression | LiteralValue .
+```
+
+A list may contain zero or more values. The number of values in a list is its length.
+
+A list has a set of indices. An empty list has an empty set of indices. A non-empty list has the index set `{0...n - 1}` where `n` is the length of the list. Attempting to access a list using an index that is not a member of its set of indices results in the `undefined` value.
+
+### Map Literals
+
+A map literal denotes a map, which is an unordered group of elements indexed by a set of unique keys.
+
+```ebnf
+MapLit = "{" [ KeyedElementList [ "," ] ] "}" .
+KeyedElementList = KeyedElement { "," KeyedElement } .
+KeyedElement = Element ":" Element .
+```
+
+Keys can only be a boolean, numeric, or string type.
+
+The value of a non-existent key is the `undefined` value.
+
+### Function Literals
+
+A function literal represents a function.
+
+```ebnf
+FunctionLit = "func" Function .
+Function = Parameters FunctionBody .
+FunctionBody = Block .
+Parameters = "(" [ IdentifierList [ "," ] ] ")" .
+IdentifierList = identifier { "," identifier } .
+```
+
+```sentinel
+func(a, b) { return b }
+```
+
+Function literals are only allowed in the file scope. They may refer to variables defined in surrounding blocks.
+
+A function must terminate with a return statement. If a function has no meaningful return value, it should return `undefined`.
+
+### Rule Expressions
+
+A rule is an expression that is evaluated lazily and the result is memoized.
+
+If the optional "when" predicate is present, the rule is evaluated only when
+the "when" boolean expression results in true. If the predicate is false,
+the rule is not evaluated and returns true. The predicate is evaluated when
+the rule would be evaluated; it is also lazy and memoized in the same way.
+
+```ebnf
+RuleExpr = "rule" [ "when" BoolExpr ] "{" Expr "}" .
+```
+
+```sentinel
+rule { x is y }
+
+rule {
+ x == "value" or
+ y == "other"
+}
+
+rule when x is y { y > 42 }
+
+rule { map ["a", "b", "c"] as _, id {
+ { "id": id }
+}}
+```
+
+### Index Expressions
+
+A primary expression of the form `a[x]` denotes the element of a list or map indexed by x. The value x is called the index or key.
+
+For `a` of type map:
+
+- If `a` contains a key `x`, `a[x]` is the map value with key `x`.
+- If `a` does not contain key `x`, `a[x]` is the `undefined` value.
+
+For `a` of type list:
+
+- `x` must be an integer
+- `x` must be in the range `[-1 * length(a), length(a)-1]`
+- If `x` is contained in the set of indices of `a`, `a[x]` is the list element at index `x`. If `x` is negative, `a[x]` is equivalent to `a[length(a)+x]`.
+- If `x` is not contained in the set of indices of `a`, `a[x]` is the `undefined` value.
+
+For `a` of value `null`:
+
+- `a[x]` is the `undefined` value.
+
+Otherwise `a[x]` is an error.
+
+### Selectors
+
+For a primary expression x, the selector expression `x.f` denotes the field `f` of the value `x`. The identifier `f` is called the selector. The type of the selector expression is the type of the selector.
+
+As a special case, selectors can be [reserved words](#keywords) and keyword [operators](#operators-and-delimiters), but cannot be any other non-identifier element.
+
+Selectors are used to access data from [imports](#imports). The first primary expression `x` in `x.f` denotes the import name. The field `f` is the selector to access data from the import.
+
+Selectors may also be used to access map data with static keys. They are syntactic sugar over index expressions. `math.pi` is equivalent to `math["pi"]` and exhibit the same limitations and behavior as an index expression. Selectors cannot, however, be used to assign values to map data.
+
+Selectors on undefined result in undefined.
+
+```sentinel
+math.pi
+time.pst.hour
+```
+
+### Slice Expressions
+
+Slice expressions construct a substring or list from a string or list.
+
+The primary expression
+
+```sentinel
+a[low : high]
+```
+
+constructs a substring or list. The indices `low` and `high` select which elements of operand `a` appear in the result. The result has indices starting at 0 and length equal to `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+```
+
+For convenience, any of the indices may be omitted. A missing `low` index defaults to zero; a missing `high` index defaults to the length of the sliced operand:
+
+```sentinel
+a[2:] // same as a[2 : length(a)]
+a[:3] // same as a[0 : 3]
+a[:] // same as a[0 : length(a)]
+```
+
+The indices are in range if `0 <= low <= high <= length(a)`, otherwise they are out of range. If the indices are out of range at run time, the result is the `undefined` value.
+
+If `a` is the value `null`, the result is the `undefined` value.
+
+If `a` is any other value type, it is an error.
+
+### Calls
+
+```ebnf
+CallExpr = identifier Arguments .
+```
+
+Given an expression `f` where `f` is a function value:
+
+```sentinel
+f(a1, a2, … an)
+```
+
+calls `f` with arguments `a1, a2, ... an`. The type of the expression is the result of `f`. The arguments are evaluated left to right. Arguments are passed by value.
+
+### Operators
+
+```ebnf
+Expression = UnaryExpr | Expression binary_op Expression .
+UnaryExpr = PrimaryExpr | unary_op UnaryExpr .
+
+binary_op = logical_op | set_op | rel_op | add_op | mul_op | else_op .
+logical_op = "and" | "or" | "xor" .
+set_op = ["not"] ( "contains" | "in" ).
+rel_op = "==" | "!=" | "<" | "<=" | ">" | ">=" |
+ "is" | "is not" | "matches" | "not matches" .
+add_op = "+" | "-" .
+mul_op = "*" | "/" | "%" .
+else_op = "else" .
+unary_op = "+" | "-" | "!" | "not" | empty_op | defined_op .
+empty_op = "is empty" | "is not empty" .
+defined_op = "is defined" | "is not defined" .
+```
+
+#### Operator Precedence
+
+Unary operators have the highest precedence.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Binary operators of the same precedence associate from left to right. For instance, x / y _ z is the same as (x / y) _ z.
+
+### Arithmetic Operators
+
+Arithmetic operators apply to numeric values and yield a result of the same type when both operands are the same.
+
+Arithmetic operations between integer and floating-point types result in a floating-point value with the integer operand treated as a floating-point value for purposes of calculation.
+
+Arithmetic operations between numeric values (integer, floating-point) and non-numeric values (strings, lists) are not permitted.
+
+All five supported arithmetic operators (+, -, \*, /, %) apply to both integer and floating-point types; + also applies to strings.
+
+```plaintext
++ sum integers, floats, strings, lists
+- difference integers, floats
+* product integers, floats
+/ quotient integers, floats
+% remainder integers, floats
+```
+
+#### Integer operators
+
+For two integer values x and y, the integer quotient `q = x / y` and remainder `r = x % y` satisfy the following relationships:
+
+```sentinel
+x = q*y + r and |r| < |y|
+```
+
+with x / y truncated towards zero.
+
+```plaintext
+ x y x / y x % y
+ 5 3 1 2
+-5 3 -1 -2
+ 5 -3 -1 2
+-5 -3 1 -2
+```
+
+As an exception to this rule, if the dividend x is the most negative value for the int type of x (-9223372036854775808), the quotient `q = x / -1` is equal to x (and r = 0).
+
+If the divisor is a constant, it must not be zero. If the divisor is zero at run time, an error occurs.
+
+#### Integer overflow
+
+For signed integers, the operations `+`, `-`, and `*` may legally overflow and the resulting value exists and is deterministically defined by the signed integer representation, the operation, and its operands. No exception is raised as a result of overflow.
+
+#### Floating-point operators
+
+For floating-point numbers, +x is the same as x, while -x is the negation of x. The result of a floating-point division by zero is not specified beyond the IEEE-754 standard.
+
+#### String Concatenation
+
+Strings can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = "hi"
+y = "hello"
+x = x + ", " + y // "hi, hello"
+x += " and good bye" // "hi, hello and good bye"
+```
+
+String addition creates a new string by concatenating the operands.
+
+#### List Concatenation
+
+Lists can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = [1, 2]
+x = x + [2, 3] // [1, 2, 2, 3]
+x += [4] // [1, 2, 2, 3, 4]
+```
+
+List addition creates a new list by concatenating the operands.
+
+### Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+In any comparison, the two operands must be equivalent types. The only exception are integers and floats, which are considered numeric and may be compared. Any comparison between other non-matching types results in the `undefined` value.
+
+The equality operators `==`, `!=`, `is`, and `is not` apply to operands that are comparable. The ordering operators `<`, `<=`, `>`, and `>=` apply to operands that are ordered. The behavior of `is` with `==` and `is not` with `!=` is identical. These terms and the result of the comparisons are defined as follows:
+
+- Boolean values are comparable. Two boolean values are equal if they are either both true or both false.
+- Integer values are comparable and ordered, in the usual way.
+- Floating point values are comparable and ordered, as defined by the IEEE-754 standard.
+- An integer compared with floating point value treats the integer as the converted floating point value.
+- String values are comparable and ordered, lexically byte-wise.
+- Lists are comparable. Lists are equal if they are of equal length and their
+ corresponding elements are comparable and equal.
+- Maps are comparable. Maps are equal if they are of equal length and both their
+ corresponding keys and values are comparable and equal.
+
+If either operand is the `undefined` value, the result of the expression is the `undefined` value.
+
+### Emptiness Comparisons
+
+Checking the emptiness of an object can be achieved by using one of the emptiness expressions, `is empty` or `is not empty`. Although similar to [Comparison Operators](#comparison-operators) in that they yield a boolean value and read as though a comparison is taking place, both `is empty` and `is not empty` are evaluated as a multi-word expression.
+
+An emptiness comparison can only be performed against collections, strings or `undefined`, with the same rules as the [built-in length](/sentinel/functions/length) function.
+
+```sentinel
+"" is empty // true
+"foo" is empty // false
+[] is empty // true
+[1] is empty // false
+{} is empty // true
+{"a": "b"} is empty // false
+
+"" is not empty // false
+"foo" is not empty // true
+[] is not empty // false
+[1] is not empty // true
+{} is not empty // false
+{"a": "b"} is not empty // true
+
+undefined is empty // undefined
+undefined is not empty // undefined
+```
+
+### Logical Operators
+
+Logical operators apply to boolean values and yield a boolean result.
+
+Logical operators are evaluated left-to-right and perform short-circuit logic. The right-hand side is not guaranteed to be evaluated if a short-circuit can be performed.
+
+```plaintext
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+### Set Operators
+
+The set operators `contains` and `in` test for set inclusion for lists
+and maps, and substring inclusion for strings.
+
+Set operators may be negated by prefixing the operator with `not`: `not contains` and `not in`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+`contains` tests if the left-hand collection contains the right-hand value. For lists, this tests equality of any value. For maps, this tests equality of any key. For strings, this tests for substring existence.
+
+`in` tests if the right-hand collection contains the left-hand value. The behavior is equivalent to `contains`.
+
+The collection must be a list, map, or string. If it is the `undefined` value, the result is the `undefined` value. For any other value, the result is an error.
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+
+"test" contains "est" // true
+"test" contains "best" // false
+"test" in "testing" // true
+"best" in "testing" // false
+```
+
+### Matches Operator
+
+The matches operator tests if a string matches a regular expression.
+
+The matches operators may be negated by prefixing the operator with `not`: `not matches`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+The left-hand value is the string to test. The right-hand value is a string value representing a regular expression.
+
+The syntax of the regular expression is the same general syntax used by Python, Ruby, and other languages. More precisely, it is the syntax accepted by RE2. The regular expression is not anchored by default; any anchoring must be explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+If either operand is `undefined`, the result is the `undefined` value. For any other non-string value, the result is an error.
+
+### Else Operator
+
+Else operators can be used to provide a default value for an expression that may evaluate to the `undefined` value. Else operators return their left-hand value unless it is `undefined`, otherwise they return their right-hand value.
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else null
+```
+
+### Quantifier Expressions (any, all, filter, map)
+
+Quantifiers are expressions that apply a predicate to a collection (list or map). The purpose of the quantifier is to produce a result based on its particular type.
+
+`any` and `all` expressions are existential and universal quantifiers, respectively. `any` expressions are equivalent to a chain of `or` and `all` expressions are equivalent to a chain of `and`. Both expressions implement short-circuiting equivalent to logical operators.
+
+The `map` expression is an apply-to-all quantifier and returns a new list for all values in the input collection. The output list will be the same length as the input collection.
+
+The `filter` expression is a subset quantifier and returns the subset of all values in the input collection for which the supplied predicate applies. This will be zero or more elements, up to the length of the input.
+
+The body of a quantifier expression is a boolean expression.
+
+`any` returns the boolean value `true` if any value in the collection expression results in the body expression evaluating to `true`. If the body expression evaluates to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression result in the body expression evaluating to `true`. If any value in the collection expression result in the body expression evaluating to `false`, the all expression returns `false`.
+
+For empty collections, `any` returns false and `all` returns `true`.
+
+`map` always returns a list, regardless of if the input collection is a list itself; maps (as in the data type) also return lists when processed through `map`. Each element is the result of the supplied expression body.
+
+`filter` returns a list or map, the same type as its input collection. Only the elements for which the expression body evaluated to `true` will be returned. If an iteration of the expression body results in `undefined`, the entire result set is `undefined`.
+
+When the collection is a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When the collection is a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+QuantExpr = QuantOp Expression "as" [ identifier "," ] identifier "{" BoolExpr "}" .
+QuantOp = "all" | "any" | "filter" | "map" .
+```
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" } // true if every iterations is true
+any group.tasks as t { t.driver is "vmware" } // true if any iteration is true
+map group.tasks as t { { "driver": t.driver } } // [{"driver": "vmware"}, ...]
+filter group.tasks as t { t.driver is "vmware" } // subset of tasks that is true
+```
+
+## Statements
+
+### Expression Statements
+
+Function call expressions may exist in the statement context.
+
+```ebnf
+ExpressionStmt = Expression .
+```
+
+### Assignments
+
+Assignments set the value of an expression to the value of another expression.
+
+```ebnf
+Assignment = AssignExpr assign_op Expression .
+AssignExpr = identifier | IndexExpr .
+assign_op = [ add_op | mul_op ] "=" .
+```
+
+The assignment `x op= y` is equivalent to `x = x op (y)` for supported values of `op`.
+
+```sentinel
+a = 1
+b[12] = 42
+c["key"] = "value"
+
+a *= 12
+b[12] += 8
+```
+
+Assignments to lists and maps can be carried out using [index expressions](#index-expressions), with the operands of the index expression being evaluated alongside the right hand side expression in a right-to-left (RHS, LHS) order.
+
+In addition to the rules detailed in the section describing their use, the following rules apply when assigning to maps or lists using index expressions:
+
+- The [variable](#variables) must exist and be a list or map. Attempts to use an index assignment on an unknown variable, or a variable that not a list or map, results in a runtime error.
+- Assigning to a list or map index that already exists overwrites that index's data with evaluated right hand side's value.
+- Assigning to an unknown key in a map creates a key for that map with the evaluated right hand side's value assigned to that key.
+- Attempting to assign a value to a list index that is out of range results in a runtime error.
+
+### If Statements
+
+If statements specify the conditional execution of two branches according to the value of a boolean expression. If the expression evaluates to true, the "if" branch is executed, otherwise, if present, the "else" branch is executed.
+
+```ebnf
+IfStmt = "if" BoolExpr Block [ "else" ( IfStmt | Block ) ] .
+```
+
+```sentinel
+if x < y {
+ return x
+} else if x > z {
+ return z
+} else {
+ return y
+}
+```
+
+### Case Statements
+
+Case statements specify a selection control mechanism to conditionally execute a branch based on expression equality.
+
+Each clause that wishes to provide an expression must use the "when" keyword. Multiple expressions can be provided, separated with a comma (",").
+
+There can be one, optional, "else" clause within a `case` statement. The "else" clause is executed if all other clauses failed to run.
+
+The clauses are evaluated in the order they are listed, with the first successful evaluation being executed.
+
+A `case` statement can optionally provide an expression. If no expression is provided, it is the equivalent of writing `case true {`.
+
+```ebnf
+CaseStmt = "case" [ Expression ] "{" { CaseWhenClause } "}" .
+CaseWhenClause = CaseWhenCase ":" StatementList .
+CaseWhenCase = "when" Expression { "," Expression } | "else" .
+```
+
+```sentinel
+case x {
+when y, z:
+ return true
+else:
+ return false
+}
+
+case {
+when x > 42:
+ return true
+else:
+ return false
+}
+```
+
+### For Statements
+
+A `for` statement specifies repeated execution of a block.
+
+The expression must evaluate to a list or a map.
+
+When iterating over a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When iterating over a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+ForStmt = "for" Expressions "as" [ identifier "," ] identifier Block .
+```
+
+```sentinel
+for [1, 2, 3] as v { count += v } // basic sum
+
+for [1, 2, 3] as idx, v {
+ if idx > 1 { count += v }
+}
+
+data = { "a": 12, "b": 32 }
+for data as k { count += data[k] } // sum map values
+for data as k, v { count += v }
+```
+
+### Break Statements
+
+A `break` statement terminates execution of the innermost `for` statement
+within the same function.
+
+```ebnf
+BreakStmt = "break" .
+```
+
+```sentinel
+// Will only print "1"
+for [1, 2, 3] as v {
+ print(v)
+ break
+}
+```
+
+### Continue Statements
+
+A `continue` statement begins the next iteration of the innermost `for` loop.
+The `for` loop must be within the same function.
+
+```ebnf
+ContinueStmt = "continue" .
+```
+
+```sentinel
+// Will print 1, 3
+for [1, 2, 3] as v {
+ if v == 2 {
+ continue
+ }
+
+ print(v)
+ break
+}
+```
+
+### Return Statements
+
+A return statement in a function F terminates the execution of F and provides the result value.
+
+```ebnf
+ReturnStmt = "return" Expression .
+```
+
+A function must terminate with a return statement.
+
+The return statement can be invoked at any time during a function to terminate execution.
+
+## Imports
+
+An import adds an assignment to the global scope to external definitions.
+
+The import declarations must only appear at the top of the source file, before any other statements. Comments may appear before imports.
+
+```ebnf
+ImportDecl = "import" Name ["as" identifier] .
+Name = string_lit .
+```
+
+An import name and identifier must be distinct. Two names may not repeated even if they're declared with different identifiers.
+
+If an identifier is not specified, the identifier is the name of the import itself.
+
+An import is not a valid value. The identifier representing the import may not be used as an expression, such as in assignment statements or call expressions.
+
+Values in imports are not assignable directly via their selectors. Function calls may be used to alter the state of the external data represented by the import. Whether or not this is supported is specific to the import implementation. The exception to this assignment restriction is the memoized data returned by a call to an import (see below).
+
+Data returned by imports can be memoized, meaning that data is returned by the import in the form of a map which is then used as much as possible without having to return to the import for more data. For example, `t = time.now` returns a map so that `t.hour` will be able to be looked up without having to go back to the import for that data.
+
+Data returned by imports may be callable, meaning that methods may be called on the memoized data returned by an import. For example, given `t = time.now`, `t.after(some_previous_time)` is valid, with the receiver data being set to the local memoized data stored in `t`. It is a valid case to accept modifications to the receiver data (example: assignment to the memoized data via index expressions), as long as all data in the receiver continues to be string-keyed. It is an invalid case to accept receiver data with non-string-typed keys. Methods may also alter the contents of receiver data so that it is different after the call is finished.
+
+As with methods, imports may also have callable keys where the data may not be memoized. Example: in the event of `v = foo.bar`, if `v.baz` is not included in the memoized data, a call will be made to the import to fetch it. The same rules and restrictions to receiver data apply to callable keys as do to method calls.
+
+The support for callable methods and non-memoized keys on data returned by imports is specific to the import implementation.
+
+The handling of import loading is specific to the runtime implementation.
+
+Implicit imports may be added by the runtime, for example for standard library definitions. An explicit import of the same name should override a matching implicit import. For example, if "time" is an implicit import and the program specifies `import "time" as cal`, then only `cal` is defined.
+
+```sentinel
+import "time"
+import "math" as m
+```
+
+## Parameters
+
+```ebnf
+ParamDecl = "param" identifier [ "default" Expression ]
+```
+
+A parameter is a way for a policy to describe variables that are expected to be supplied by the calling application, in addition to any default value.
+
+Parameter declarations must appear at the top of the source file, following imports.
+
+Like imports, comments may appear before parameters; this is mainly pertinent when the policy is not importing anything, which would make parameters the first declarations that appear in a policy.
+
+A parameter declaration describes a valid variable that is added to the scope of a policy at runtime. This variable has the same properties and rules as other variables assigned during the course of policy evaluation as defined by the specification. This includes having a dynamic type and being able to be re-assigned during execution.
+
+Parameters may only be strings, integers, floating point numbers, booleans, lists, or maps. More complex types such as rules or functions are not allowed.
+
+A parameter can specify a default value by adding one in the declaration via use of the "default" keyword, and supplying a literal for the default value. The literal for a default is a very limited expression, comprising of:
+
+- For strings, a simple string literal;
+- For integers and floating-point numbers, either a literal denoting the value, or a unary expression with the literal, and the operators - or +;
+- For booleans, the pre-declared identifiers true or false;
+- For lists and maps, their respective literals composed of values and keys (for maps) of the above values only.
+
+Any other type of expression or identifier is not allowed.
+
+A parameter that does not have a default value defined is required by the policy. Evaluating a policy with a required parameter that has not been supplied at execution results in a runtime error.
+
+Parameters must not conflict with imports or any other identifiers currently defined in the global scope, including any reserved or pre-declared identifiers. Attempting to assign a parameter to an existing value results in a runtime error.
+
+```sentinel
+import "time"
+
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+param time default "11:00" // error, conflicts with "time" import
+param undefined // error, reserved identifier
+```
+
+## Built-in Functions
+
+Built-in functions are predeclared. They are called like any other function.
+
+### Length
+
+The built-in function `length` returns the length of a collection of string.
+
+The length of a string is the number of bytes in the string. It is not necessarilly the number of characters.
+
+The length of a collection is the number of elements in that collection.
+
+The length of `undefined` is `undefined`.
+
+### Collections
+
+#### List Append
+
+The built-in function `append` appends a value to the end of a list in-place.
+
+Appending to a non-list value results in an immediate `fail`.
+
+The return value of `append` is always the `undefined` value.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+#### Map Delete
+
+The built-in function `delete` deletes elements from a map by key. The map is modified in-place.
+
+Deleting a key that does not exist does nothing.
+
+Calling delete for a non-map value results in an immediate `fail`.
+
+The return value of `delete` is always the `undefined` value.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
+
+#### Keys and Values
+
+The built-in function `keys` and `values` return the keys and values of a map, respectively. The return value is a list. Both functions return values in an unspecified order.
+
+The `keys` or `values` of `undefined` is `undefined`.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+### Range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it defaults to `1`.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4]
+range(1, 5, 2) // [1,3]
+range(0, -3, -1) // [0,-1,-2]
+```
+
+### Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a value of that type according to the rules below.
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
+
+### Printing
+
+The built-in function `print` can be used to output formatted debug information. The runtime may omit print function calls depending on its execution mode. The runtime may choose where the output of a print function goes.
+
+`print` is a variadic function, accepting one or more values to be printed; values are separated by a single whitespace character (`" "`).
+
+The return value of `print` is always `true` to allow it to be used in boolean expressions.
+
+```sentinel
+print("hello") // "hello"
+print("hello", "world") // "hello world"
+print("The", "number", "is", 42) // "The number is 42"
+print([1, 2, 3]) // "[1, 2, 3]"
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // "false"
+```
+
+### Errors
+
+The built-in function `error` is equivalent to `print` but immediately causes execution to halt and the main rule to evaluate to `false`. The program execution is considered to have failed.
+
+## Program Execution
+
+Program execution begins by evaluating the source top to bottom. If evaluation is successful, the `main` rule is evaluated and its result is returned. Execution can be interrupted at any time by an error, which can be called explicitly or implicitly through illegal or undefined behavior.
+
+---
+
+> The content of this page is licensed under the [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
+>
+> Based on [The Go Programming Language Specification](https://golang.org/ref/spec) licensed under [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/language/undefined.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/language/undefined.mdx
new file mode 100644
index 0000000000..dc51531783
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/language/undefined.mdx
@@ -0,0 +1,97 @@
+---
+page_title: Sentinel Language - Undefined
+sidebar_current: docs-language-undefined
+description: >-
+ The value `undefined` is a special value representing undefined behavior or an
+ unknown value. Any operation on an `undefined` value results in `undefined`.
+layout: docs
+---
+
+# Language: Undefined
+
+The value `undefined` is a special value representing undefined behavior
+or an unknown value.
+
+Undefined is not an error because there are situations where the undefined
+value can be safely ignored and the language also provides construts for
+recovering from an undefined value.
+
+The undefined value can be created manually with `undefined`. In most cases,
+undefined is created by accessing a non-existent list element or value.
+The undefined value is created in a number of other circumstances. The documentation
+will note when an undefined value may be created.
+
+Almost any operation with `undefined` results in `undefined`. For example,
+performing math on undefined, attempting to call a function that is undefined,
+etc.
+
+## Main and Undefined
+
+If the value `undefined` is returned from the top-level `main` rule, then
+the policy is considered `false`. However, the runtime provides mechanisms
+for the host application to determine the policy failure was caused by
+an undefined value and report that to the user.
+
+The `main` rule returning the undefined value should be considered a logical
+error in most cases and should probably be corrected by the policy writer.
+
+## Debugging Undefined
+
+When an `undefined` value is first created (either explicitly or implicitly),
+the runtime will record the position where the undefined was created. This
+location can be used to find logic that could be erroneous.
+
+## Boolean Logic and Undefined
+
+Boolean logic is one way to safely recover from `undefined`. If boolean
+logic can safely determine a result despite an undefined value, that result
+will be returned.
+
+For example: `undefined or true` will result in `true`, because no matter
+what actual value `undefined` could've been if the policy behaved properly,
+the boolean expression would still be true.
+
+Another scenario where `undefined` can be safely ignored is short-circuit
+logic with `and`. `false and undefined` will result in `false`.
+
+The full table of logical operations on `undefined` is below:
+
+```sentinel
+undefined or true = true
+undefined or false = undefined
+undefined or undefined = undefined
+undefined and true = undefined
+undefined and false = undefined
+undefined and undefined = undefined
+undefined xor true = undefined
+undefined xor false = undefined
+undefined xor undefined = undefined
+
+// Short-circuit examples
+false or true or undefined = true
+false or undefined or true = true
+true and false and undefined = false
+true and undefined and false = undefined
+```
+
+## Else Expressions
+
+The `else` expression allows explicit recovery from undefined and is useful
+for setting default values.
+
+`else` is an operator where the left and right hand side are values. If the
+left-hand value is `undefined`, the right-hand value is returned. Otherwise,
+the left-hand value is returned.
+
+Examples:
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else "default"
+
+// In more complex scenarios
+
+foo() else 42 == 12
+a = config.value else "default"
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/language/values.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/language/values.mdx
new file mode 100644
index 0000000000..f932480340
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/language/values.mdx
@@ -0,0 +1,210 @@
+---
+page_title: Sentinel Language - Values
+sidebar_current: docs-language-values
+description: Values are the data that Sentinel policies operate on.
+layout: docs
+---
+
+# Language: Values
+
+Values are the _data_ that Sentinel policies operate on. You can create this
+data yourself, for example by just typing the number `42`, or you may access
+this data from an external source to make policy decisions.
+
+Values have a _type_, which determines what kind of operations can be performed
+on the data. Example types are booleans (true and false), numbers,
+and strings (text).
+
+This page documents all the available value types. You can also
+[convert between types](#type-conversion).
+
+## Boolean
+
+A boolean is a value that is either true or false.
+
+A boolean value is created literally with `true` or `false`.
+
+As a policy language, booleans are central to the behavior of Sentinel.
+Booleans are used as conditions in [if statements](/sentinel/language/conditionals),
+are the result of [rules](/sentinel/language/rules), and more. The ultimate
+result of a Sentinel policy is true or false.
+
+## Integer
+
+An integer is a whole number.
+
+An integer is a 64-bit value. This means it can represent numbers from
+-9,223,372,036,854,775,808 to 9,223,372,036,854,775,808.
+
+Integers are created by typing them out literally with no
+separators (such as a comma). An optional prefix can set a non-decimal base:
+`0` for octal, and `0x` for hexadecimal. In hexadecimal literals, letters
+`a-f` and `A-F` represent values 10 to 15.
+
+Example integers are shown below:
+
+```sentinel
+42
+0600
+0xBadFace
+170141183460469
+```
+
+Integers are used for math and numerical comparison.
+
+## Float
+
+A float is a number with a decimal point. It has an integer part, a decimal
+point, a fractional part, and optionally an exponent part. Example
+floats are shown below:
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+Floats are IEEE-754 64-bit floating point numbers.
+
+## String
+
+Strings are text values.
+
+Strings are created by wrapping text in double quotes, such as `"hello"`.
+Within the quotes, any character may appear except newline and an unescaped
+double quote. The text between the quotes is the value.
+
+Because Sentinel policies must be UTF-8 encoded text, strings themselves are
+UTF-8 encoded text. This means you can put any value UTF-8 character into
+a string, such as `"日本語"`.
+
+You can have a string with a literal double-quote by _escaping_ it with
+a backslash. For example: `"they said \"hello\""` turns into the value
+`they said "hello"`.
+
+Backslash escapes can be used for much more than only escaping a double quote.
+Newlines are `\n`, a literal backslash is `\\`, and many more. The full list
+of available backslash escapes can be found
+[in the language specification](/sentinel/language/spec#string-literals).
+
+Example strings:
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+Strings do not support indexing, however it is possible to achieve a similar
+result through the combination of the [strings](/sentinel/imports/strings)
+standard import and list indexing.
+
+```sentinel playground
+import "strings"
+
+asciistr = "Hello, world!"
+utf8str = "日本語"
+
+utf8split = rule {
+ strings.split(utf8str, "")[2] == "語"
+}
+
+asciisplit = rule {
+ strings.split(asciistr, "")[5] == ","
+}
+
+main = rule { utf8split and asciisplit }
+```
+
+Strings support [slicing](/sentinel/language/slices) to efficiently create
+substrings.
+
+## List
+
+See [Lists](/sentinel/language/lists).
+
+## Map
+
+See [Maps](/sentinel/language/maps).
+
+## Rule
+
+See [Rules](/sentinel/language/rules).
+
+## Function
+
+See [Functions](/sentinel/language/functions).
+
+## Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a
+value of that type. Some examples are shown below followed by a list of exact
+rules for type conversion.
+
+```sentinel
+int(42) // 42
+int("42") // 42
+int(42.8) // 42
+int(true) // 1
+
+float(1.2) // 1.2
+float(1) // 1.0
+float("4.2") // 4.2
+float(true) // 1.0
+
+string("foo") // "foo"
+string(88) // "88"
+string(0xF) // "15"
+string(true) // "true"
+
+bool("true") // true
+bool(1) // true
+bool(-1) // true
+bool(0.1) // true
+bool("false") // false
+bool(0) // false
+```
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/language/variables.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/language/variables.mdx
new file mode 100644
index 0000000000..7516890212
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/language/variables.mdx
@@ -0,0 +1,99 @@
+---
+page_title: Sentinel Language - Variables
+sidebar_current: docs-language-vars
+description: Variables store values that can be used later.
+layout: docs
+---
+
+# Language: Variables
+
+Variables store values that can be used later.
+
+Most notably, a variable assignment of `main` is required for all
+Sentinel policies. This is the main [rule](/sentinel/language/rules) that
+is executed to determine the result of a policy. The `main` rule may
+use other variables that are assigned in the policy.
+
+Example:
+
+```sentinel
+a = 1
+b = a + 1
+```
+
+In this example, two variables are assigned: `a` and `b`. `a` is given
+the value of "1" and `b` uses the `a` to calculate its own value.
+
+## Syntax
+
+The syntax for assigning a variable is:
+
+```sentinel
+IDENTIFIER = VALUE
+```
+
+On the left of the equal sign is an _identifier_. This is a name for your
+variable and will be how you reference it later. An identifier is any
+combination of letters and digits, but must start with a letter. An
+underscore `_` is also a valid letter.
+
+On the right is any valid Sentinel value or expression. A value is a
+literal value such as a number or string. An expression is some computed
+value such as doing math, calling a function, etc.
+
+## Assignment and Reassignment
+
+A variable is _assigned_ when it is given a value. You can also _reassign_
+variables at any time by setting it to a new value. The new value takes
+effect for any subsequent use of that variable. A variable can be reassigned
+to a different type.
+
+For example:
+
+```sentinel
+a = 1 // a = 1
+b = a // b = 1
+a = "value" // a = "value", b = 1
+c = a // c = "value", b = 1
+```
+
+In the above example you can see that the variables are set and reassigned.
+Notice that the value of a variable is the _current_ value, and that reassigning
+a variable only affects _future_ uses of that variable. You can see this with
+`c` and `b` being different values.
+
+## Unassigned Variables
+
+Using a variable that is _unassigned_ is an error.
+
+In the example below, the first line would result in the policy erroring.
+Sentinel is executed top-down and the value of `c` is not available yet
+on the first line.
+
+```sentinel
+a = c // Error!
+c = 1
+```
+
+## Type Conversion
+
+While a topic more related to [values][lang-values], variables can be assigned
+(or-reassigned) a different value type via type conversion.
+
+[lang-values]: /sentinel/language/values
+
+```sentinel
+s = "1.1"
+a = int(s) // a = 1
+a = float(s) // a = 1.1
+
+a = 1
+s = string(a) // s = "1"
+```
+
+For more details, see the type conversion sections in the
+[values][lang-values-type-conversion] page, or the [Sentinel Language
+Specification][lang-spec-type-conversion].
+
+[lang-values-type-conversion]: /sentinel/language/values#type-conversion
+[lang-spec-type-conversion]: /sentinel/language/spec#type-conversion
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/nomad.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/nomad.mdx
new file mode 100644
index 0000000000..a105b0337d
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/nomad.mdx
@@ -0,0 +1,51 @@
+---
+page_title: Nomad and Sentinel
+sidebar_current: docs-app-nomad
+description: >-
+ Nomad Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies can currently execute on job
+ submission (creation, update).
+layout: docs
+---
+
+# Nomad
+
+Nomad is a simple, flexible scheduler and workload orchestrator.
+[Nomad Enterprise](https://www.hashicorp.com/products/nomad/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies can currently execute on job submission (creation, update).
+
+Sentinel policies have full access to the job structure. This allows the
+Sentinel policy to control behavior based on any attribute within a job,
+such as the driver, resource requests, network configuration, volume
+configuration, and more. The information that Sentinel policies have access
+to will expand over time.
+
+Nomad fully supports all [enforcement levels](/sentinel/concepts/enforcement-levels).
+For soft mandatory policies, the `sentinel-override` capability must be
+available on the user's ACL policy to allow override. Overrides are always
+logged.
+
+The Nomad integration with Sentinel is documented in depth in the
+[Nomad Enterprise documentation](https://www.nomadproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Only allow Docker-based jobs.**
+
+```sentinel
+# Test policy only allows Docker based tasks
+main = rule { all_drivers_docker }
+
+# all_drivers_docker checks that all the drivers in use are Docker
+
+all_drivers_docker = rule {
+ all job.task_groups as tg {
+ all tg.tasks as task {
+ task.driver is "docker"
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/terraform.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/terraform.mdx
new file mode 100644
index 0000000000..9e4c430ddb
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/terraform.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Terraform and Sentinel
+sidebar_current: docs-app-terraform
+description: >-
+ Use Sentinel with HCP Terraform and Terraform Enterprise to enforce policy
+ on Terraform configurations, states, and plans.
+layout: docs
+---
+
+# Terraform
+
+[HCP Terraform and Terraform Enterprise](https://www.hashicorp.com/products/terraform/) use Sentinel to enforce
+policy on [Terraform](https://www.terraform.io) configurations, states, and plans.
+
+The Sentinel integration with Terraform runs within
+HCP Terraform and Terraform Enterprise
+after a `terraform plan` and before a `terraform apply`. The policies
+have access to the created plan, the state at the time of the plan,
+and the configuration at the time of the plan.
+
+The Terraform integration with Sentinel is documented in depth in the [HCP Terraform and Terraform Enterprise documentation](/terraform/cloud-docs/policy-enforcement/sentinel).
+Please read that page for full documentation. This page will only show basic examples.
+
+### Examples
+
+**Example: All AWS instances must have a tag**
+
+```sentinel
+import "tfplan"
+
+main = rule {
+ all tfplan.resources.aws*instance as *, instances {
+ all instances as \_, r {
+ (length(r.applied.tags) else 0) > 0
+ }
+ }
+}
+```
+
+**Example: Only allow GCP instance sizes smaller than `n1-standard-16`**
+
+```sentinel
+import "tfplan"
+
+allowed_machine_types = [
+ "n1-standard-1",
+ "n1-standard-2",
+ "n1-standard-4",
+ "n1-standard-8",
+]
+
+main = rule {
+ all tfplan.resources.google_compute_instance as _, instances {
+ all instances as _, r {
+ r.applied.machine_type in allowed_machine_types
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/vault.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/vault.mdx
new file mode 100644
index 0000000000..6db2f8af22
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/vault.mdx
@@ -0,0 +1,64 @@
+---
+page_title: Vault and Sentinel
+sidebar_current: docs-app-vault
+description: >-
+ Vault Enterprise uses Sentinel to augment the built-in policy system to
+ provide Role Governing Policies (RGPs) and Endpoint Governing Policies (EGPs)
+ to enable complex, flexible policies across identities and endpoints.
+layout: docs
+---
+
+# Vault
+
+[Vault Enterprise](https://www.hashicorp.com/products/vault/) uses Sentinel
+to augment the built-in policy system to provide Role Governing Policies (RGPs)
+and Endpoint Governing Policies (EGPs) to enable complex, flexible policies
+across identities and endpoints.
+
+**Role Governing Policies (RGPs)** are Sentinel policies that are tied to
+particular tokens, Identity entities, or Identity groups. They have access to
+a rich set of controls across various aspects of Vault. These are evaluated
+whenever a token they're attached to is used.
+
+**Endpoint Governing Policies (EGPs)** are Sentinel policies that are tied to
+particular paths instead of tokens. They have access to as much request
+information as possible, but they can take effect even on unauthenticated
+paths, such as login paths.
+
+The Vault integration with Sentinel is documented in depth in the
+[Vault Enterprise documentation](https://www.vaultproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Endpoint policy that requires MFA authentication from a corporate network.**
+
+```sentinel
+import "sockaddr"
+
+# We expect logins to come only from our private IP range
+cidrcheck = rule {
+ sockaddr.is_contained(request.connection.remote_addr, "10.20.0.0/16")
+}
+
+# Require Ping MFA validation to succeed
+ping_valid = rule {
+ mfa.methods.ping.valid
+}
+
+main = rule when request.path is "auth/ldap/login" {
+ ping_valid and cidrcheck
+}
+```
+
+**Example: Endpoint policy that disallows tokens created before a certain time.**
+
+```sentinel
+import "time"
+import "strings"
+
+main = rule when not strings.has_prefix(request.path, "auth/ldap/login") {
+ time.load(token.creation_time).unix > time.load("2017-09-17T13:25:29Z").unix
+}
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/why.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/why.mdx
new file mode 100644
index 0000000000..7e0d42e1ee
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/why.mdx
@@ -0,0 +1,47 @@
+---
+page_title: Why Sentinel?
+sidebar_current: docs-why
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: docs
+---
+
+# Why Sentinel?
+
+The growth of infrastructure and applications has been enabled in part
+by an increasing trend towards automation everywhere.
+Configuration as code (such as Chef or Puppet with [Packer](https://www.packer.io))
+has enabled automated machine configuration. Infrastructure
+as code (such as [Terraform](https://www.terraform.io)) has enabled
+automatic infrastructure creation. And schedulers (such as
+[Nomad](https://www.nomadproject.io) and Kubernetes) have enabled
+automatic application deployment.
+Sentinel enables guardrails to be put in place
+on automation while allowing the codification and automatic enforcement
+of business requirements in critical areas of your infrastructure.
+
+Meanwhile, businesses have business requirements and sometimes legal
+requirements which must be expressed in policies. Traditionally, these
+policies are enforced by humans. But in a highly automated world, the
+automation is only as fast as its slowest component. In many cases, this
+is the human verification step.
+
+As an example: before infrastructure as code and autoscaling, if an order
+came through for 5,000 new machines, a human would likely respond to the
+ticket verifying that the user really intended to order 5,000 new machines.
+Today, automation can almost always freely order 5,000 new compute instances
+without any hesitation, which can result in unintended expense or system
+instability.
+
+Sentinel introduces [policy as code](/sentinel/concepts/policy-as-code)
+and a powerful framework built-in to HashiCorp tooling to allow automation
+guardrails, business requirements, legal compliance, and more to be
+actively enforced by the running systems in realtime.
+
+With Sentinel, you can require an override to create certain numbers of
+infrastructure resources. You can disallow unsafe deployment configurations
+with Nomad. You can enforce certain key/value formats in Consul. You
+can restrict secret access by time in Vault. And more.
+
+Sentinel is available today in HashiCorp enterprise products.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/writing/basic.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/writing/basic.mdx
new file mode 100644
index 0000000000..ebcebf2788
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/writing/basic.mdx
@@ -0,0 +1,111 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-basic
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Policy Basics
+
+Sentinel policies are easy to write while still supporting advanced constructs
+for creating complex policies. This page will explain the basics of writing
+Sentinel policies to get started. You don't need any prior Sentinel knowledge,
+but we recommend reading the [language guide](/sentinel/language) after this.
+
+Sentinel policies are text files written using the [Sentinel language](/sentinel/language).
+The policies are evaluated top-to-bottom. The value of `main` after execution
+determines whether a policy passes or fails.
+
+## The Simplest Policy
+
+Sentinel only requires that a policy have a `main` variable that evaluates to
+a boolean value.
+
+A valid example is shown below:
+
+```sentinel playground
+main = 10 > 5
+```
+
+This type of minimal policy is not purely academic. In practice, simple
+policies can often be reduced to a single line logical statement resulting
+in `true` or `false`. However, the expression is usually wrapped in a
+[rule](/sentinel/language/rules) for testing reasons.
+
+You can verify Sentinel will execute this minimal policy using the CLI:
+
+```
+$ sentinel apply minimal.sentinel
+Pass
+```
+
+## Logical Expressions
+
+Policy is at its core a set of logic: you can or can not perform some action
+under a certain set of circumstances. Those circumstances are logical
+expressions. Therefore, Sentinel policies primarily translate into logical
+expressions.
+
+Detailed documentation on boolean expressions is
+[available in the language guide](/sentinel/language/boolexpr).
+
+A simple numerical comparison was seen in the first example on this page.
+Sentinel also provides inclusion operators such as `contains`, `any`, `all`, and
+more. Sentinel allows some operators to have aliases to promote readability
+while remaining programmer-familiar, such as `==` which can equivalently be `is`.
+
+The example below verifies that all numbers in a list are even:
+
+```sentinel playground
+numbers = [6, 22, 8, 4, 12]
+main = all numbers as n { n % 2 is 0 }
+```
+
+## Variables
+
+A policy will very often use variables. Applications such as
+[Nomad](https://www.nomadproject.io) inject variables into the global
+scope of a policy for making policy decisions. For example, Nomad injects
+the job that is being run into the policy scope. Knowing how to use
+variables is critical to effectively using Sentinel.
+
+Detailed documentation on how to define and access variables is
+[available in the language guide](/sentinel/language/variables).
+
+Variables can be defined and used explicitly. For example:
+
+```sentinel playground
+value = 10
+main = value > 5
+```
+
+But they may also be introduced implictly by the host system. Nomad
+injects `job` into policies to describe the job that is being run. The
+policy below is a valid policy that requires a job have two task groups.
+Notice that `job` is not defined anywhere. It is implicitly inserted by
+the host application (in this case Nomad). Refer to the application you're
+writing policy for to determine if it implicitly inserts values.
+
+```json sentinel
+{
+ "policy": "main = length(job.task_groups) is 2",
+ "globals": {
+ "job": {
+ "task_groups": ["foo", "bar"]
+ }
+ }
+}
+```
+
+## And more!
+
+The Sentinel language supports many more features such as functions,
+loops, and more. You can learn about all of this in the
+[complete language guide](/sentinel/language).
+
+The other pages in the [writing policy](/sentinel/writing)
+will cover other information you need to know about writing Sentinel
+policies that isn't simply a language reference.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/writing/debugging.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/writing/debugging.mdx
new file mode 100644
index 0000000000..7dc284d222
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/writing/debugging.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-debugging
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Debugging
+
+As policies become more complex, it becomes harder to understand exactly why
+a policy may be behaving differently than you expect.
+
+Prior to actively debugging, there are multiple best practices we recommend
+following. We recommend breaking your policy down into a set of
+[rules](/sentinel/writing/rules). This will make it easier to understand
+[traces](/sentinel/writing/tracing) and make it easier to
+[test](/sentinel/writing/testing) your policies.
+This should help to debug policies up to a significant complexity.
+
+## Print Logging
+
+The built-in [print function](/sentinel/language/logging-errors#print)
+can be used to log data. The print output is only shown during failures
+or when [tracing is explicitly enabled](/sentinel/writing/tracing).
+
+The `print` function outputs each argument, which can be of any type, converted
+to a human-friendly string format. Arguments are separated with a single space.
+
+Example:
+
+```sentinel playground
+value = 42
+print("the number is", value) // the number is 42
+
+object = { "foo": false }
+print(object) // { "foo": false }
+
+main = rule { true }
+```
+
+The `print` function returns the value `true` so that it can also be
+used within rule expressions. Note that the `print` function should be
+used at the beginning of statements otherwise they may never be
+reached. This is due to internal performance techniques within Sentinel
+like [short-circuiting](https://en.wikipedia.org/wiki/Short-circuit_evaluation).
+
+Example:
+
+```sentinel playground
+// rule_a and rule_b will evaluate to false
+rule_a = rule { print("rule_a is printed") and false } // "rule_a is printed"
+rule_b = rule { false and print("rule_b is printed") } // Nothing is printed
+
+// rule_c and rule_d will evaluate to true
+rule_c = rule { print("rule_c is printed") or true } // "rule_c is printed"
+rule_d = rule { true or print("rule_d is printed") } // Nothing is printed
+
+main = rule { rule_a or rule_b or rule_c and rule_d }
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/writing/imports.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/writing/imports.mdx
new file mode 100644
index 0000000000..74ac751ee4
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/writing/imports.mdx
@@ -0,0 +1,117 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom import](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only local
+context for making policy decisions.
+
+Sentinel also comes with a set of [standard imports](/sentinel/imports).
+Standard imports are available to every Sentinel policy to help policy writers
+with common tasks such as working with the time, network addresses, and more.
+
+-> **This page is about writing policies that _use_ imports.** If you're
+interested in _creating_ a new import, please see the section on [extending
+Sentinel](/sentinel/extending) for information on how to write modules and
+import plugins.
+
+## Using Imports
+
+To use an import, you use the `import` keyword at the top of your policy. This
+specifies the name of the import you want to use. The application you're writing
+the policy for must already be
+[configured](/sentinel/configuration#imports) to provide that
+import.
+
+Details on imports can be found in the
+[import section in the language reference](/sentinel/language/imports).
+
+In the example below, we use the [time](/sentinel/imports/time) import:
+
+```sentinel playground
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+There are two options to develop a policy locally when using imports:
+configure Sentinel to launch the import on `apply`, or mock the import
+using `mock`. The former requires access to the import while the
+latter is faster (doesn't have to launch a process for plugins) and
+doesn't require the import.
+
+## Mocking Imports
+
+The first option to developing policies locally is to mock the import values.
+When mocking an import, you don't need the import to be available. This can be
+useful since some imports may not be available as a plugin and may only be
+available to the application the policy runs in.
+
+Mocks are specified via the [configuration
+file](/sentinel/configuration). Mocks can also be used for
+[testing](/sentinel/writing/testing).
+
+You can supply mock configuration one of two ways, depending on your use case:
+
+- **[Using static data](/sentinel/configuration#mocking-static-data):**
+ Use this method when you can accurately represent your mock data in JSON and
+ do not need to mock complex Sentinel features such as functions.
+- **[Using Sentinel code](/sentinel/configuration#mocking-with-sentinel-code):**
+ Use this method when using a static JSON object is insufficient, such as when
+ you need to mock functions or other complex Sentinel features.
+
+Our example does not require complex data to be mocked, so a static object
+is sufficient:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ hour = 12
+ weekday_name = "Tuesday"
+ }
+ }
+}
+```
+
+This can be used via the CLI:
+
+```
+$ sentinel apply -config=config.hcl policy.sentinel
+Pass
+```
+
+## Launching Import Plugins
+
+If you have access to the plugin binary, you can launch the import. The
+benefit of this is that it is really using the import to test your
+policy. If the import changes, your policies may start failing. If you
+only use mock data and the import changes, your policies will still
+appear to work.
+
+Imports are configured in the configuration file:
+
+```hcl
+import "plugin" "custom_time" {
+ source = "/path/to/sentinel-time-import"
+}
+```
+
+This would require the `sentinel-time-import` binary. For this example
+this doesn't currently exist. We plan on writing one to provide for this
+section of the documentation.
+
+## Custom Imports
+
+You can also [create your own imports](/sentinel/extending).
+
+If your policy decisions could benefit from accessing external information,
+then you can use custom imports as a way to do this.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/writing/index.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/writing/index.mdx
new file mode 100644
index 0000000000..eec0b61fb5
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/writing/index.mdx
@@ -0,0 +1,34 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing
+description: >-
+ Sentinel provides a language and workflow for building policy across any
+ system that embeds Sentinel. By learning Sentinel once, you are able to
+ effectively control access to many systems using Sentinel's powerful features.
+ Basic concepts that are important to understand for Vault usage.
+layout: docs
+---
+
+# Writing Sentinel Policy
+
+This section covers how to write Sentinel policies.
+
+We recommend you complete the Get Started tutorials prior to
+reading this section of the documentation. The Get Started tutorials will
+explain all the basics of writing and testing policies. This section of the
+documentation will serve as more of a reference guide to all available
+features of Sentinel.
+
+Sentinel provides a language and workflow for building policy across any system
+that embeds Sentinel. By learning Sentinel once, you are able to effectively
+control access to many systems using Sentinel's powerful features.
+
+Sentinel also provides a [local CLI](/sentinel/commands) for developing and testing
+Sentinel policies. This CLI can be integrated into a daily developer's workflow
+along with CI to treat [policy as code](/sentinel/concepts/policy-as-code).
+
+Sentinel uses its own [language](/sentinel/language) for writing
+policies. You can view a [language reference](/sentinel/language)
+as well as the [specification](/sentinel/language/spec) for details. You
+don't have to read those documents immediately, since the language should
+be easy enough to pick up throughout this section.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/writing/rules.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/writing/rules.mdx
new file mode 100644
index 0000000000..ab4fc69d08
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/writing/rules.mdx
@@ -0,0 +1,65 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-rules
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Rules
+
+Rules form the basis of a policy by representing behavior that is either passing
+or failing. Rules are a first class language construct in Sentinel. A policy can
+and should be broken down into rules to aid with readability, testability, and
+performance.
+
+Rules provide:
+
+- **Readability:** A policy that is broken down into rules can be read
+ more easily. The logic becomes clearer to see for policy writers when
+ they come back to it.
+
+- **Reportability:** When [tracing](/sentinel/writing/tracing) is enabled,
+ rules form the foundation of the data returned. All evaluated rules are added
+ to the trace with the data the rule evaluated to, and their position within
+ policy or module source files.
+
+- **Testability:** [Policy testing](/sentinel/writing/testing) is
+ built on asserting the values of rules. By breaking logic down into
+ rules, you're able to more effectively test your policies.
+
+- **Performance:** Rules are only evaluated on demand, and are also only
+ evaluated once. This means that a rule referenced multiple times only has a
+ one-time performance cost. For complex logic, this could result in improved
+ performance.
+
+An example usage of rules is shown below:
+
+```json sentinel
+{
+ "policy": "is_sunny = rule { weather is \"sunny\" }\nis_wednesday = rule { day is \"wednesday\" }\nmain = rule { is_sunny and is_wednesday }",
+ "globals": {
+ "weather": "sunny",
+ "day": "wednesday"
+ }
+}
+```
+
+Rules can also contain non-boolean data. This can be useful for passing more
+detail to the caller than an opaque boolean value would be able to communicate.
+
+```sentinel playground
+days = [{"weather": "sunny"}]
+main = rule {
+ filter days as d {
+ d.weather is not "sunny"
+ }
+}
+```
+
+Details about rules and their behavior can be found in
+[the language reference](/sentinel/language/rules). Rules have important
+behavior so we recommend reading the language reference on rules.
+Rules will be used throughout the remainder of the documentation.
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/writing/testing.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/writing/testing.mdx
new file mode 100644
index 0000000000..5d8a522c28
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/writing/testing.mdx
@@ -0,0 +1,495 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-testing
+description: >-
+ Sentinel has a built-in test framework to validate a policy behaves as
+ expected.
+layout: docs
+---
+
+# Testing
+
+Sentinel has a built-in test framework to validate a policy behaves as expected.
+
+With an ever-increasing amount of automation surrounding technology,
+the guardrails provided by policies are a critical piece towards ensuring
+expected behavior. As a reliance on correct policy increases, it is important
+to test and verify policies.
+
+Testing is a necessary step to fully realize
+[policy as code](/sentinel/concepts/policy-as-code). Just as good software
+is well tested, a good set of policies should be equally well tested.
+
+## Test Folder Structure
+
+Policies are tested by asserting that [rules](/sentinel/writing/rules)
+are the expected values given a pre-configured input. Tests are run
+by executing the [test command](/sentinel/commands/test).
+
+Sentinel is opinionated about the folder structure required for tests.
+This opinionated structure allows testing to be as simple as running
+`sentinel test` with no arguments. Additionally, it becomes simple to test
+in a CI or add new policies.
+
+The structure Sentinel expects is `test//*.[hcl|json]` where ``
+is the name of your policy file without the file extension. Within that folder
+is a list of HCL or JSON files. Each file represents a single test case.
+Therefore, each policy can have multiple tests associated with it.
+
+-> Note that the primary configuration file format for Sentinel is HCL. While
+you can write configuration files in a HCL-equivalent JSON, we only discuss the
+use of HCL on this page.
+
+## Test Case Format
+
+Each HCL file within the test folder for a policy is a single test case.
+
+The file is the same configuration format as the [CLI configuration
+file](/sentinel/configuration). The format lets you define mock data, imports to
+use, and more. This mock data is the key piece in being able to test policies:
+you craft a specific scenario and assert your policy behaves as you expect.
+
+Test cases also use the `test` block within the configuration file to assert the
+value of [rules](/sentinel/writing/rules). If the `test` key is omitted, the
+policy is expected to pass. If the test key is specified, only the rules
+specified in the map will be asserted. This means if you omit `main`, then the
+final policy result is not asserted.
+
+Example with assertions:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+
+test {
+ rules = {
+ main = false
+ is_open_hours = false
+ is_weekday = true
+ }
+}
+```
+
+The configuration above specifies some parameter data, and asserts the result of
+some rules. This is the same configuration used in the example section below.
+
+## Example
+
+Lets use the following file as an example. Save this file to a directory
+and name it `policy.sentinel`. It can be named anything with the `sentinel`
+extension, but by naming it `policy.sentinel` your output should match
+the example output on this page.
+
+```sentinel
+// The day of the week.
+param day
+
+// The hour of the day.
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+### A Passing Test
+
+Next, let's define a single test case. Relative to where you saved
+the policy, create a file at the path `test/policy/good.hcl`.
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 14
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/good.hcl
+```
+
+This passed because the policy passed. We didn't assert any specific
+rules. By not specifying any assertions, `test` expects the policy itself
+to fully pass.
+
+### A Failing Test
+
+Define another test case to fail. We want to verify our policy fails when expected, too.
+
+Save the following as `test/policy/7-am.hcl`:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+FAIL - policy.sentinel
+ FAIL - test/policy/7-am.hcl
+ expected "main" to be true, got: false
+
+ trace:
+ policy.sentinel:9:1 - Rule "main"
+ bool: false
+
+ policy.sentinel:8:1 - Rule "is_open_hours"
+ bool: false
+
+ PASS - test/policy/good.hcl
+```
+
+As you can see, the test fails because "main" is false. This is good
+because the policy _should_ have failed since we specified an invalid
+hour. But, we expect main to be false and don't want our test to fail!
+Update `7-am.hcl` to add test assertions:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+
+test {
+ rules = {
+ main = false
+ is_open_hours = false
+ }
+}
+```
+
+And when we run the tests:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/7-am.hcl
+ PASS - test/policy/good.hcl
+```
+
+The test passes. We asserted that we expect the `main` rule to be false,
+the `is_open_hours` rule to be false, and the `is_weekday` rule to be
+true. By asserting some rules are true, we can verify that our policy
+is failing for reasons we expect.
+
+## Mocking
+
+The above example demonstrates how to test by supplying different
+[parameters](/sentinel/language/parameters). Parameters in a policy can be
+specifically useful when you want to control user-defined input values to a
+policy.
+
+However, generally, when testing, you will need mimic the conditions you will
+see in production. Production implementations of Sentinel will supply data using
+one of two methods:
+
+- **Global data:** Data is injected directly into the policy's scope and is
+ accessible using normal identifiers, similar to variables.
+- **Imports:** Data is stored behind an [import](/sentinel/language/imports)
+ and loaded on demand as needed by the policy author.
+
+Proper testing of a policy requires that these values be able to be _mocked_ -
+or, in other words, simulated in a way that allows the accurate testing of the
+scenarios that a policy could reasonably pass or fail under.
+
+Mocking both globals and imports can be done by setting various parts of the
+configuration file.
+
+### Mocking Globals
+
+Demonstrating the mocking of globals can be seen by making a few modifications to
+our example policy, removing the `param` declarations:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Then, change the `param` section in the configuration file to
+[`global`](/sentinel/configuration#global).
+
+```json
+global "day" {
+ value = "monday"
+}
+
+global "hour" {
+ value = 14
+}
+```
+
+This test should still pass, as if nothing had happened, although what we've
+done is shifted our parameters to globals, simulating an environment where `day`
+and `hour` are already defined for us.
+
+### Mocking Imports
+
+To mock imports, we need to use the
+[`mock`](/sentinel/configuration#mock-imports) section of the
+configuration file.
+
+Let's say the above example is behind an import named `time`.
+
+-> **NOTE:** [`time`](/sentinel/imports/time) is a valid standard import.
+This example may not be accurate to the
+import's syntax.
+
+The code now looks like this:
+
+```sentinel
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To mock this import, we can [mock it as static
+data](/sentinel/configuration#mocking-static-data). The configuration
+file now looks like, without assertions:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ weekday_name = "Monday"
+ hour = 14
+ }
+ }
+}
+```
+
+The policy will now pass, with the `time` import mocked.
+
+Data can also be [mocked as Sentinel
+code](/sentinel/configuration#mocking-with-sentinel-code). In this case,
+the above configuration file would look like:
+
+```hcl
+mock "time" {
+ module {
+ source = "mock-time.sentinel"
+ }
+}
+```
+
+And a file named `mock-time.sentinel` would now hold your mock values:
+
+```sentinel
+day = "monday"
+hour = 14
+```
+
+Mocking as Sentinel code allows more complex details to be mocked as well, such
+as functions. Say we wanted to mock the `time.load()` function. To mock this,
+just add it to the `mock-time.sentinel` file:
+
+```sentinel
+load = func(_) {
+ return {
+ "weekday_name": "Monday",
+ "hour": 14,
+ }
+}
+```
+
+Your code can now be written as:
+
+```sentinel
+import "time"
+
+t = time.load("a_mock_timestamp")
+
+is_weekday = rule { t.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { t.hour > 8 and t.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To see more details, see the [Mock
+Imports](/sentinel/configuration#mock-imports) section in the
+configuration file.
+
+## Asserting Non-Boolean Rules
+
+Non-boolean rules can also be asserted by `sentinel test`.
+
+To assert non-boolean values, simply enter the expected value into the rule
+contents:
+
+```hcl
+param "maintenance_days" {
+ value = [
+ {
+ day = "wednesday"
+ hour = 9
+ },
+ {
+ day = "friday"
+ hour = 1
+ },
+ {
+ day = "sunday"
+ hour = 1
+ },
+ ]
+}
+
+test {
+ rules = {
+ main = [
+ {
+ day = "wednesday"
+ hour = 9
+ },
+ ]
+ }
+}
+```
+
+This would assert a policy checking for violations of a maintenance policy,
+saying that maintenance hours should happen before 6AM on any given day:
+
+```sentinel
+param maintenance_days
+
+main = rule {
+ filter maintenance_days as d {
+ d.hour >= 6
+ }
+}
+```
+
+## Test JSON Output
+
+Running `sentinel test` with the `-json` flag will give you the test results in
+a JSON output format, suitable for parsing by reporting software.
+
+-> **Note:** The JSON output format is currently under development and the
+format may change at a later time. Additionally, support for other well-known
+formats, such as JUnit, may become available in the future.
+
+The current format is an object with the top-level keys being `policies` and
+`duration`, with each test grouped up by policy being run. `duration` represents
+time taken in milliseconds for all policies to run.
+
+The policy result fields are:
+
+- `path`: The path of the policy and the index of the test result in the
+ `policies` field in the root object.
+- `status`: A string representation of the policy's test status as a whole. Can
+ be one of `PASS`, `FAIL`, `ERROR`, or `?`. The final status, `?`, represents a
+ policy that has no tests to process, and acts like a passing test.
+- `errors`: An array of any error messages encountered during processing the
+ policy for testing. Usually reserved for policy file or parser-related errors.
+ For case-specific errors, see the error field for the particular case.
+- `cases`: A map of case results, indexed by case path.
+- `duration`: Time taken in milliseconds for the policy to run.
+
+The case result fields are:
+
+- `path`: The path of the test case and the result's index in the `cases` field
+ in the policy object.
+- `status`: A string representation of the case's test status. Can be one of
+ `PASS`, `FAIL` or `ERROR`.
+- `errors`: An array of any error messages encountered during running this test
+ case.
+- `trace`: The trace for this policy in JSON format. See the
+ [tracing](/sentinel/writing/tracing) page for more details.
+- `rule_detail`: When the `status` of this test case is `FAIL`, contains an
+ object of assertion failure detail, indexed by rule. Only failures are counted
+ here; any rules not found here can be assumed to have passed or not asserted.
+- `config_warnings`: An array of strings denoting any configuration warnings
+ found while processing the configuration for this test case.
+- `config_legacy`: Denotes whether or not the configuration is a legacy JSON
+ configuration and needs to be modernized. This field may be removed in future
+ releases.
+
+A passing example is shown below:
+
+```json
+{
+ "policies": {
+ "policy.sentinel": {
+ "path": "policy.sentinel",
+ "status": "PASS",
+ "errors": null,
+ "duration": 5,
+ "cases": {
+ "test/policy/pass.hcl": {
+ "path": "test/policy/pass.hcl",
+ "status": "PASS",
+ "errors": null,
+ "trace": {
+ "description": "A very basic policy to determine if a run is within working hours.",
+ "error": null,
+ "print": "",
+ "result": true,
+ "rules": {
+ "is_open_hours": {
+ "desc": "Passes if run during business hours.",
+ "ident": "is_open_hours",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 290,
+ "line": 13,
+ "column": 1
+ },
+ "value": true
+ },
+ "is_weekday": {
+ "desc": "Passes if the day does not fall on the weekend.",
+ "ident": "is_weekday",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 193,
+ "line": 10,
+ "column": 1
+ },
+ "value": true
+ },
+ "main": {
+ "desc": "",
+ "ident": "main",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 338,
+ "line": 14,
+ "column": 1
+ },
+ "value": true
+ }
+ }
+ },
+ "rule_detail": {},
+ "config_warnings": null,
+ "config_legacy": false
+ }
+ }
+ }
+ },
+ "duration": 10
+}
+```
diff --git a/content/sentinel/v0.30.x/content/sentinel/docs/writing/tracing.mdx b/content/sentinel/v0.30.x/content/sentinel/docs/writing/tracing.mdx
new file mode 100644
index 0000000000..e98767da00
--- /dev/null
+++ b/content/sentinel/v0.30.x/content/sentinel/docs/writing/tracing.mdx
@@ -0,0 +1,403 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-tracing
+description: >-
+ Sentinel provides execution traces to better understand the execution of a
+ policy.
+layout: docs
+---
+
+# Traces
+
+Sentinel provides execution traces to better understand the execution of a policy.
+
+When using the Sentinel [CLI](/sentinel/commands), traces are shown
+on policy failure during `apply` or `test`, or when the `-trace` flag is
+explicitly specified. For Sentinel-enabled applications, traces are optional
+and may require special configuration to enable.
+
+The availability of the trace may vary from application to application. While
+some applications may only log traces on failure, others, such as [Terraform
+Cloud](https://www.terraform.io/cloud), log the trace on every execution. For
+more details, consult your implementation's documentation.
+
+-> **Note:** We're actively improving Sentinel tracing in each release
+to provide better data and improve readability. The exact format of a
+trace may not match the Sentinel version embedded in the application you're
+using, but the data should be similar. The canonical format for a trace should
+get more stable as we approach a 1.0 release.
+
+## Analyzing a Trace
+
+To show traces, let's use the example policy below with the CLI:
+
+```sentinel
+param day
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+
+main = rule { is_open_hours and is_weekday }
+```
+
+Let's cause the policy to fail so the trace is more interesting.
+If you're on a terminal that supports it, the trace output will be colored
+so you can more clearly see passes, failures, and rules.
+
+
+
+ $ sentinel apply main.sentinel{'\n'}
+
+ main.sentinel:1:7: requires value for parameter day
+
+ {'\n'}
+ {'\n'}
+ {'\n'}
+ {' '}Values can be strings, floats, or JSON array or object values. To force
+ {'\n'}
+ {' '}strings, use quotes.{'\n'}
+ {'\n'}
+ {' '}Enter a value: sunday{'\n'}
+ {'\n'}
+
+ main.sentinel:2:7: requires value for parameter hour
+
+ {'\n'}
+ {'\n'}
+ {'\n'}
+ {' '}Values can be strings, floats, or JSON array or object values. To force
+ {'\n'}
+ {' '}strings, use quotes.{'\n'}
+ {'\n'}
+ {' '}Enter a value: 14{'\n'}
+ {'\n'}
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+
+ Fail - main.sentinel
+
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:7:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}
+ false
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:5:1 - Rule "is_open_hours"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}true{'\n'}
+ {'\n'}
+
+ main.sentinel:4:1 - Rule "is_weekday"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}false{'\n'}
+
+
+
+We can see all of our rules neatly and clearly displayed along with the result
+of the policy. If you are getting the trace due to a failed policy and not
+because you explicitly used `-trace`, an informational message is displayed.
+
+As we can see from our basic example, the `main` rule has failed and its result
+is highlighted in red. All peripheral rules that were also evaluated as part of
+the policy are also displayed below the main rule. Source positions are also
+displayed so that you can find the references to the rules in your code.
+
+Via the trace, we can clearly see that while `is_open_hours` returned a true
+result, `is_weekday` did not, and per the logic in our code, the policy is
+rejected.
+
+## Non-Boolean Rules and the Trace
+
+The trace is particularly important when working with rules that return
+non-boolean data, such as rules where you want to see violations instead of a
+simple opaque boolean value. Let's take our example from the [rules](./rules)
+section, and write a small static policy for it:
+
+```sentinel playground
+days = [
+ {"weekday": "Sunday", "weather": "clouds"},
+ {"weekday": "Monday", "weather": "rain"},
+ {"weekday": "Tuesday", "weather": "sunny"},
+ {"weekday": "Wednesday", "weather": "sunny"},
+ {"weekday": "Thursday", "weather": "clouds"},
+ {"weekday": "Friday", "weather": "rain"},
+ {"weekday": "Saturday", "weather": "sunny"},
+]
+
+main = rule {
+ filter days as d {
+ d.weather is not "sunny"
+ }
+}
+```
+
+Here is the output of our policy:
+
+
+
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+ {'\n'}
+
+ Fail - weather.sentinel
+
+ {'\n'}
+ {'\n'}
+
+ weather.sentinel:11:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+
+ {' [\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Sunday"'}
+ {'\n'}
+ {' "weather": "clouds"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Monday"'}
+ {'\n'}
+ {' "weather": "rain"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Thursday"'}
+ {'\n'}
+ {' "weather": "clouds"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Friday"'}
+ {'\n'}
+ {' "weather": "rain"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' ]'}
+
+
+
+
+We can now see all of the weekdays with non-sunny weather.
+
+Note that to prevent formatting issues and excessive output, the human-readable
+trace is limited in the volume of output it will display for collections.
+
+## JSON Output
+
+The trace can also be displayed in JSON by adding `-json` to `sentinel apply`
+and `sentinel test`, and will display the trace in its entirety, unlike the
+standard CLI output.
+
+Here is the result of running `sentinel apply -json` against our weather policy:
+
+```json
+{
+ "can_override": false,
+ "error": null,
+ "duration": 12,
+ "policies": [
+ {
+ "allowed_failure": false,
+ "error": null,
+ "duration": 12,
+ "policy": "weather.sentinel",
+ "result": false,
+ "trace": {
+ "description": "",
+ "error": null,
+ "print": "",
+ "result": false,
+ "rules": {
+ "main": {
+ "desc": "",
+ "ident": "main",
+ "position": {
+ "filename": "weather.sentinel",
+ "offset": 328,
+ "line": 11,
+ "column": 1
+ },
+ "value": [
+ {
+ "weather": "clouds",
+ "weekday": "Sunday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Monday"
+ },
+ {
+ "weather": "clouds",
+ "weekday": "Thursday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Friday"
+ }
+ ]
+ }
+ }
+ }
+ }
+ ],
+ "result": false
+}
+```
+
+Note that the trace is always included with this output, regardless of whether
+or not the policy passes or fails; using `-trace` is unnecessary.
+
+Additionally, with `sentinel apply`, you can display the value of a single rule
+in JSON, instead of the entire trace. This is done with the `-json-rule` flag.
+
+-> **Note:** `sentinel apply -json-rule` needs to be run either against a single
+on-disk policy, or against a single policy within a policy set. It is ignored
+in full policy set executions and functions exactly like `-json` in these
+scenarios.
+
+Here is the result of executing `sentinel apply -json-rule main weather.sentinel`:
+
+```json
+[
+ {
+ "weather": "clouds",
+ "weekday": "Sunday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Monday"
+ },
+ {
+ "weather": "clouds",
+ "weekday": "Thursday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Friday"
+ }
+]
+```
+
+## Other Trace Details
+
+There are some other details that are good to know when working with the trace:
+
+- Only rules that get executed are added to the trace. Considering our first
+ example where the `main` rule is the expression `is_open_hours and is_weekday`,
+ if our expression was using an `or` operator instead of `and`, in addition to
+ the policy passing, `is_open_hours` would be present in the trace, but
+ `is_weekday` would not be, as the policy would have never evaluated that rule.
+- Descriptions for both rules and policies will show up in the trace as well if
+ present. Here is the output to our first policy with the appropriate
+ annotations:
+
+
+
+ ...{'\n\n'}
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+
+ Fail - main.sentinel
+
+ {'\n'}
+ {'\n'}
+ Description:{'\n'}
+ {' A very basic policy to determine if a run is within working\n'}
+ {' hours.\n'}
+ {'\n'}
+
+ main.sentinel:7:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}
+ false
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:5:1 - Rule "is_open_hours"
+
+ {'\n'}
+ {' '}
+ Description:
+ {'\n'}
+ {' Passes if run during business hours.\n'}
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}true{'\n'}
+ {'\n'}
+
+ main.sentinel:4:1 - Rule "is_weekday"
+
+ {'\n'}
+ {' '}
+ Description:
+ {'\n'}
+ {' Passes if the day does not fall on the weekend.\n'}
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}false{'\n'}
+
+
diff --git a/content/sentinel/v0.30.x/data/docs-nav-data.json b/content/sentinel/v0.30.x/data/docs-nav-data.json
new file mode 100644
index 0000000000..dcd6d9f835
--- /dev/null
+++ b/content/sentinel/v0.30.x/data/docs-nav-data.json
@@ -0,0 +1,395 @@
+[
+ {
+ "title": "What is Sentinel?",
+ "path": "intro"
+ },
+ {
+ "title": "Why Sentinel?",
+ "path": "why"
+ },
+ {
+ "title": "Release Notes",
+ "path": "changelog"
+ },
+ {
+ "title": "Basic Concepts",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "concepts"
+ },
+ {
+ "title": "Policy as Code",
+ "path": "concepts/policy-as-code"
+ },
+ {
+ "title": "Policy Language",
+ "path": "concepts/language"
+ },
+ {
+ "title": "Imports",
+ "path": "concepts/imports"
+ },
+ {
+ "title": "Enforcement Levels",
+ "path": "concepts/enforcement-levels"
+ }
+ ]
+ },
+ {
+ "title": "Configuration File Syntax",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "configuration"
+ },
+ {
+ "title": "Override Files",
+ "path": "configuration/overrides"
+ },
+ {
+ "title": "Remote Sources",
+ "path": "configuration/remote-sources"
+ }
+ ]
+ },
+ {
+ "title": "Commands (CLI)",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "commands"
+ },
+ {
+ "title": "apply",
+ "path": "commands/apply"
+ },
+ {
+ "title": "fmt",
+ "path": "commands/fmt"
+ },
+ {
+ "title": "test",
+ "path": "commands/test"
+ }
+ ]
+ },
+ {
+ "title": "Writing Policy",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "writing"
+ },
+ {
+ "title": "Basics",
+ "path": "writing/basic"
+ },
+ {
+ "title": "Rules",
+ "path": "writing/rules"
+ },
+ {
+ "title": "Traces",
+ "path": "writing/tracing"
+ },
+ {
+ "title": "Testing",
+ "path": "writing/testing"
+ },
+ {
+ "title": "Imports",
+ "path": "writing/imports"
+ },
+ {
+ "title": "Debugging",
+ "path": "writing/debugging"
+ }
+ ]
+ },
+ {
+ "title": "Extending Sentinel",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "extending"
+ },
+ {
+ "title": "Modules",
+ "path": "extending/modules"
+ },
+ {
+ "title": "Plugins",
+ "path": "extending/plugins"
+ },
+ {
+ "title": "Static Imports",
+ "path": "extending/static-imports"
+ },
+ {
+ "title": "Internals",
+ "path": "extending/internals"
+ }
+ ]
+ },
+ {
+ "title": "Features",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "features"
+ },
+ {
+ "title": "terraform",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "features/terraform"
+ },
+ {
+ "title": "tfplan/v1",
+ "path": "features/terraform/tfplan-v1"
+ },
+ {
+ "title": "tfplan/v2",
+ "path": "features/terraform/tfplan-v2"
+ },
+ {
+ "title": "tfconfig/v1",
+ "path": "features/terraform/tfconfig-v1"
+ },
+ {
+ "title": "tfconfig/v2",
+ "path": "features/terraform/tfconfig-v2"
+ },
+ {
+ "title": "tfstate/v1",
+ "path": "features/terraform/tfstate-v1"
+ },
+ {
+ "title": "tfstate/v2",
+ "path": "features/terraform/tfstate-v2"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Language",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "language"
+ },
+ {
+ "title": "Variables",
+ "path": "language/variables"
+ },
+ {
+ "title": "Values",
+ "path": "language/values"
+ },
+ {
+ "title": "Lists",
+ "path": "language/lists"
+ },
+ {
+ "title": "Maps",
+ "path": "language/maps"
+ },
+ {
+ "title": "Rules",
+ "path": "language/rules"
+ },
+ {
+ "title": "Imports",
+ "path": "language/imports"
+ },
+ {
+ "title": "Parameters",
+ "path": "language/parameters"
+ },
+ {
+ "title": "Boolean Expressions",
+ "path": "language/boolexpr"
+ },
+ {
+ "title": "Arithmetic",
+ "path": "language/arithmetic"
+ },
+ {
+ "title": "Slices",
+ "path": "language/slices"
+ },
+ {
+ "title": "Conditionals",
+ "path": "language/conditionals"
+ },
+ {
+ "title": "Loops",
+ "path": "language/loops"
+ },
+ {
+ "title": "Collection Operations",
+ "path": "language/collection-operations"
+ },
+ {
+ "title": "Functions",
+ "path": "language/functions"
+ },
+ {
+ "title": "Scope",
+ "path": "language/scope"
+ },
+ {
+ "title": "Undefined",
+ "path": "language/undefined"
+ },
+ {
+ "title": "Logging and Errors",
+ "path": "language/logging-errors"
+ },
+ {
+ "title": "Specification",
+ "path": "language/spec"
+ }
+ ]
+ },
+ {
+ "title": "Builtin Functions",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "functions"
+ },
+ {
+ "title": "append",
+ "path": "functions/append"
+ },
+ {
+ "title": "compare",
+ "path": "functions/compare"
+ },
+ {
+ "title": "delete",
+ "path": "functions/delete"
+ },
+ {
+ "title": "error",
+ "path": "functions/error"
+ },
+ {
+ "title": "keys",
+ "path": "functions/keys"
+ },
+ {
+ "title": "length",
+ "path": "functions/length"
+ },
+ {
+ "title": "print",
+ "path": "functions/print"
+ },
+ {
+ "title": "range",
+ "path": "functions/range"
+ },
+ {
+ "title": "values",
+ "path": "functions/values"
+ }
+ ]
+ },
+ {
+ "title": "Standard Imports",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "imports"
+ },
+ {
+ "title": "collection",
+ "routes": [
+ {
+ "title": "Reference",
+ "path": "imports/collection"
+ },
+ {
+ "title": "collection/maps",
+ "path": "imports/collection/maps"
+ },
+ {
+ "title": "collection/lists",
+ "path": "imports/collection/lists"
+ }
+ ]
+ },
+ {
+ "title": "base64",
+ "path": "imports/base64"
+ },
+ {
+ "title": "decimal",
+ "path": "imports/decimal"
+ },
+ {
+ "title": "http",
+ "path": "imports/http"
+ },
+ {
+ "title": "json",
+ "path": "imports/json"
+ },
+ {
+ "title": "runtime",
+ "path": "imports/runtime"
+ },
+ {
+ "title": "sockaddr",
+ "path": "imports/sockaddr"
+ },
+ {
+ "title": "strings",
+ "path": "imports/strings"
+ },
+ {
+ "title": "time",
+ "path": "imports/time"
+ },
+ {
+ "title": "types",
+ "path": "imports/types"
+ },
+ {
+ "title": "units",
+ "path": "imports/units"
+ },
+ {
+ "title": "version",
+ "path": "imports/version"
+ }
+ ]
+ },
+ {
+ "divider": true
+ },
+ {
+ "title": "Consul",
+ "path": "consul"
+ },
+ {
+ "title": "Nomad",
+ "path": "nomad"
+ },
+ {
+ "title": "Terraform",
+ "path": "terraform"
+ },
+ {
+ "title": "Vault",
+ "path": "vault"
+ }
+]
diff --git a/content/sentinel/v0.30.x/img/sentinel-import-topology.svg b/content/sentinel/v0.30.x/img/sentinel-import-topology.svg
new file mode 100644
index 0000000000..36f7c79bc9
--- /dev/null
+++ b/content/sentinel/v0.30.x/img/sentinel-import-topology.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/content/sentinel/v0.30.x/redirects.jsonc b/content/sentinel/v0.30.x/redirects.jsonc
new file mode 100644
index 0000000000..1c7c77f138
--- /dev/null
+++ b/content/sentinel/v0.30.x/redirects.jsonc
@@ -0,0 +1,7 @@
+[
+ {
+ "source": "/sentinel/commands/config",
+ "destination": "/sentinel/configuration",
+ "permanent": true,
+ },
+]
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/changelog.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/changelog.mdx
new file mode 100644
index 0000000000..6d0a968ec4
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/changelog.mdx
@@ -0,0 +1,954 @@
+---
+page_title: Sentinel Runtime Release Notes
+sidebar_current: docs-changelog
+description: >-
+ These are the public release notes for the Sentinel runtime. See this document
+ for the latest updates.
+layout: docs
+---
+
+# Sentinel Runtime Release Notes
+
+These are the release notes for the Sentinel runtime.
+
+Each version of the runtime is released with a corresponding version of
+[Sentinel CLI](/sentinel/commands). To download the CLI, see the [install
+page](/sentinel/install).
+
+Sentinel integrations and embedded runtimes may not always have the latest
+version installed, depending on the product's individual release cycle. For more
+information, contact the support team for your specific integration.
+
+
+## 0.40.0 (May 12, 2025)
+
+ENHANCEMENTS:
+
+- Static imports can now be traversed at time of import, allowing for quicker
+data selection within consistent data structures.
+
+BUG FIXES:
+
+- `imports/version`: Added missing `version` selector for version namespace.
+
+## 0.30.0 (February 17, 2025)
+
+ENHANCEMENTS:
+
+- `imports/json`: Added the ability to check if a value is valid JSON through
+the `valid` method.
+
+## 0.29.0 (November 26, 2024)
+
+ENHANCEMENTS:
+
+- `imports/collection/lists`: Added `difference`, `union` and
+`intersection` helpers to the `collection/lists` import.
+
+BUG FIXES:
+
+- `imports/json`: Any list element or map key/value that is `undefined`
+will now be ignored within the `marshal` method instead of returning `{}`.
+Additionally, directly passing `undefined` will return `undefined` as
+documented.
+
+## 0.28.0 (September 26, 2024)
+
+BUG FIXES:
+
+- `imports/collection`: The `matches` helper now returns no results if there
+are no matches found in the collection.
+
+ENHANCEMENTS:
+
+- `cmd/apply`: A new argument, `-timeout`, has been added to the apply command.
+This argument will allow the apply command to exit after a certain time has been
+reached.
+
+NOTES:
+
+- This release introduces a EULA and Terms Of Evaluation to the release artifacts,
+which will be included in all releases going forward.
+
+## 0.27.0 (August 7, 2024)
+
+ENHANCEMENTS:
+- `imports/collection/maps`: Added an `unset` helper to the `collection/maps`
+import, providing the ability to remove keys using a provided path.
+- `imports/collection/maps`: Added an `omit` helper to the `collection/maps`
+import, which returns a map with all provided paths removed from the source
+map.
+- `imports/strings`: Added the `replace`, `trim`, `trim_left`, `trim_right` and
+`trim_space` helpers to the `strings` import.
+
+## 0.26.3 (July 19, 2024)
+
+ENHANCEMENTS:
+
+- `imports/collection/maps`: Added a `set` helper to the `collection/maps`
+import, providing the ability to set values deeply within a map.
+- `imports/collection/maps`: Added a `pick` helper to the `collection/maps`
+import, providing the ability to select a series of values from a map.
+
+## 0.26.2 (June 19, 2024)
+
+ENHANCEMENTS:
+
+- `imports/collection`: Added a `filter` helper to the `collection` import,
+providing a functional approach to filtering collections.
+
+## 0.26.1 (May 22, 2024)
+
+NOTES:
+
+- This release changes lower-level components and there are no user-facing changes.
+
+## 0.26.0 (May 15, 2024)
+
+FEATURES:
+
+- `imports/collection`, `imports/collection/lists` and `import/collection/maps`:
+Adding new helper imports for dealing with collections (lists, maps), helping
+avoid complexities within policy code.
+- `sentinel/eval`: Fixed an issue with per-policy parameters where parameters
+may leak from one policy into the following policy.
+
+## 0.25.1 (Apr 18, 2024)
+
+BREAKING CHANGES:
+
+- `imports/http`: The default `Accept` header has been changed to `*/*`, removing a redundant
+extension that some servers may not accept by default.
+
+## 0.25.0 (Apr 8, 2024)
+
+ENHANCEMENTS:
+
+- `cmd/apply`: JSON results will return an additional `duration` field for individual policies as well as the evaluation as a whole.
+- `cmd/test`: `sentinel test` will return an additional `duration` field in the JSON output.
+
+BUG FIXES:
+
+- `cmd`: Removed redundant debug logging for custom plugins.
+
+## 0.24.4 (Mar 21, 2024)
+
+ENHANCEMENTS:
+
+- `runtime/format`: Null values will now print correctly for rule value outputs.
+- `config`: Some internal changes to configuration parsing workflow.
+
+## 0.24.3 (Feb 9, 2024)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.24.2 (Jan 31, 2024)
+
+BUG FIXES:
+
+- `imports/static`: Fixed an issue where `nil` values provided to the built-in
+static import were being treated as `undefined` in policy.
+
+## 0.24.1 (Jan 19, 2024)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where the Sentinel cache was unstable due to concurrent
+tests.
+
+## 0.24.0 (Dec 7, 2023)
+
+ENHANCEMENTS:
+
+- `cmd/apply`, `cmd/test`: Custom import plugins can now be fetched from remote
+sources.
+- `cmd/apply`, `cmd/test`: Static imports can now be fetched from remote
+sources.
+- `features/terraform`: Added support for `resource_drift` in the `tfplan/v2` import.
+
+## 0.23.1 (Oct 19, 2023)
+
+BUG FIXES:
+
+- `cmd/apply`: Warnings and errors are included in the JSON output if the json flag is enabled.
+
+## 0.23.0 (Sept 5, 2023)
+
+FEATURES:
+
+- `features/apply_all`: Adds the `apply-all` feature, allowing for all policies to evaluate regardless of result, instead of exiting on first failure.
+
+BUG FIXES:
+
+- `features/terraform`: The `tfplan/v1` import correctly handles nested attribute schemas.
+
+## 0.22.1 (June 22, 2023)
+
+BUG FIXES:
+
+- `sentinel/eval`: Under certain conditions, per-policy parameters would cause
+ Sentinel to panic. This has been resolved.
+
+## 0.22.0 (May 31, 2023)
+
+- `config`: Configuration now supports a `sentinel` block to manage the
+ Sentinel runtime.
+
+## 0.21.1 (May 8, 2023)
+
+BUG FIXES:
+
+- `config`: An issue with certain identifiers being treated as incorrectly invalid
+ has been resolved.
+
+## 0.21.0 (March 8, 2023)
+
+BREAKING CHANGES:
+
+- `lang/ast`: `is empty` and `is not empty` are now treated as `ast.UnaryExpr`
+ expressions, with `ast.IsEmptyExpr` being removed.
+- `lang/ast`: You can now assert if a value is defined or not using the `is defined`
+ and `is not defined` syntax.
+
+FEATURES:
+
+- `config`: Parameter values can now be provided for individual policies within
+ a policy block.
+
+## 0.20.0 (February 16, 2023)
+
+ENHANCEMENTS:
+
+- `cmd/testcmd`: Allows sentinel tests to run concurrently by default. Sequential style testing can
+ be enabled by running with the `-maxConcurrency=1` option.
+- `cmd/testcmd`: Allows sentinel test command to timeout after a certain duration. This can be provided
+ by the user or will default to 5 minutes.
+- `cmd/apply`: The policy enforcement level is now included in the JSON output.
+- `lang/ast`: Functions can now be declared as named statements, providing
+ a safer function declaration.
+
+BREAKING CHANGES:
+
+- `cmd/apply`: Policies provided directly to the apply command will now default their enforcement
+ level to `advisory`, aligning with the `policy` configuration block.
+- `sentinel`: JSON results will no longer return `allowed_failure` or `can_override` fields.
+- `sentinel/result`: A new package has been added which provides additional methods to return
+ supplemental data about the evaluation result.
+
+## 0.19.5 (February 9, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `SelectorExpr` will correctly rewrite `X`.
+
+## 0.19.4 (February 8, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `Compile` function will correctly set the `Original`
+ field when rewriting a `CallExpr` as an `ImportExpr`.
+
+## 0.19.3 (February 3, 2023)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.19.2 (January 17, 2023)
+
+BUG FIXES:
+
+- `runtime/localast`: The `Compile` function will no longer modify `ast.CallExpr`
+ arguments in place.
+
+## 0.19.1 (December 14, 2022)
+
+BUG FIXES:
+
+- `lang/ast`: The `Rewrite` function will no longer modify the provided Node
+ in place.
+
+ENHANCEMENTS:
+
+- `lang/semantic`: The Break semantic check uses the AST walker.
+
+## 0.19.0 (December 6, 2022)
+
+BREAKING CHANGES:
+
+- `config`: Attempts to override a standard import with a custom import will
+ now be met with an error.
+- `imports`: All plugin imports now return `sdk.Plugin` instead of `sdk.Import`,
+ as per the SDK update to v0.4.0.
+
+ENHANCEMENTS:
+
+- `runtime/importer`: An `Import` and `ImportInstance` interface have been
+ added to improve the import capabilities.
+- `runtime/importer`: The `Importer` interface now returns a `Import` interface
+ instead of a `sdk.Import`.
+- `lang/semantic`: The Import Assignment semantic check uses the AST walker.
+- `lang/ast`: Added an AST Walker so the AST rewriter does not need to be used for basic AST tasks.
+
+FEATURES:
+
+- `config`: Configuration syntax now allows for configuration of imports within
+ the standard library.
+- `config`: A new configuration syntax for defining modules and plugins is now
+ available.
+- Static data can now be added via `import` configuration.
+- `cmd/apply`: The `apply` command now supports multiple primary configuration
+ files by default, loading all configuration files within the working
+ directory.
+- `cmd/apply`: The `apply` command now accepts applying configuration overrides
+ to primary configuration.
+
+## 0.18.13 (October 31, 2022)
+
+BUG FIXES:
+
+- `cmd/apply`: `sentinel apply` no longer aborts on advisory policy failure.
+
+## 0.18.12 (September 15, 2022)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where nested `CallExpr` were not rewritten.
+
+## 0.18.11 (June 8, 2022)
+
+NOTES:
+
+- `config`: The `enforcement_level` key for policies is now optional, defaulting to `"advisory"` when not supplied.
+
+## 0.18.10 (May 20, 2022)
+
+NOTES:
+
+- `runtime/initwd`: Improvements to the installer, including timeouts and filesize limits, as well as disabling support
+ for symlinks within `git` or `hg` repositories.
+
+## 0.18.9 (March 23, 2022)
+
+NOTES:
+
+- This release fixes an issue with the Docker container build pipeline. There are no changes to the
+Sentinel runtime or CLI.
+
+## 0.18.8 (March 22, 2022)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue in the installer for Windows paths.
+
+## 0.18.7 (February 24, 2022)
+
+NOTES:
+
+- **Darwin arm64**. This release will be our first to include Darwin arm64 architecture.
+
+BUG FIXES:
+
+- `lang/ast`: Fixed issues where `IndexSpec` and `RuleLit` nodes where returning incorrect end positions.
+- `lang/ast`: Fixed an issue where `ParamSpec` nodes where returning incorrect end positions.
+
+## 0.18.6 (February 2, 2022)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.18.5 (January 14, 2022)
+
+IMPROVEMENTS:
+
+- `runtime/initwd`: Changed the sum algorithm used during remote installation from `md5` to `sha256` to reduce chance of collision.
+
+## 0.18.4 (July 20, 2021)
+
+FEATURES:
+
+- `imports/static`: Improved handling of struct tags when performing encoding, allowing `-` to mark as ignored.
+
+## 0.18.3 (June 1, 2021)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue for remote source installation where duplicate sub-directories resulted in incorrect installation.
+- `imports/static`: Fixed an issue where global configuration that contained an empty map could not be accessed without raising selector issues.
+
+## 0.18.2 (May 18, 2021)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where import expressions inside lists and maps were not being evaluated correctly.
+
+## 0.18.1 (May 11, 2021)
+
+BUG FIXES:
+
+- `imports/json`: Fixed an issue where empty maps where failing when passed at any level to `marshal`.
+
+## 0.18.0 (March 25, 2021)
+
+FEATURES:
+
+- `imports/http`: Added support for POST actions within the `http` import
+ through addition of the `post` function. Additionally, support for adding
+ a body to the `request` object has been added via `with_body`.
+
+## 0.17.4 (February 2, 2021)
+
+BUG FIXES:
+
+- `runtime/format`: Fixed a nesting issue with the rule printer where nesting
+ state leaks were leading to all collection values eventually being truncated,
+ regardless of their nesting level.
+
+## 0.17.3 (January 15, 2021)
+
+BUG FIXES:
+
+- `runtime/initwd`: Fixed an issue where local file installation failed on Windows
+ when using `sentinel test`.
+
+## 0.17.2 (January 13, 2021)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where duplicate log messages were being printed with
+ `sentinel test`.
+
+## 0.17.1 (January 7, 2021)
+
+BUG FIXES:
+
+- `cmd/test`: Fixed an issue where hard-coded path separator caused `sentinel
+ test` issues in Windows.
+
+## 0.17.0 (January 5, 2021)
+
+LANGUAGE CHANGES:
+
+- **Map Expression** `map`. A new quantifier expression, `map` has been added.
+ `map` takes a collection and applies an operation to each element in the
+ collection, returning a list of results with the resulting values.
+- **Emptiness checks** `is empty` and `is not empty`. Two new expressions,
+ `is empty` and `is not empty` have been added. As they are aliases to the
+ `length` built-in, only `string`, `map`, `list` or `undefined` is
+ supported.
+- **Comparable maps** Maps (the data type) are now comparable. Maps are equal if
+ they are of equal length and both their corresponding keys and values are
+ comparable and equal.
+- **Rich Return Types** Rules can now process and return non-boolean data.
+ Scalar, list, and map types are supported. When non-boolean values are used,
+ the presence of a non-zero or non-zero-length `main` rule determines policy
+ failure. More details can be found on
+ https://docs.hashicorp.com/sentinel/language#main.
+
+FEATURES:
+
+- **CLI**: `sentinel apply` and `sentinel test` now have options for JSON
+ output. For more details, see the CLI documentation.
+- `imports/base64`: Added a new import, `base64`, to assist with encoding and
+ decoding base64 strings.
+
+## 0.16.1 (October 21, 2020)
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where `contains` to ensure any instance of `undefined` would correctly result in `undefined`.
+- `runtime/eval`: Fixed an issue in `any` and `all` expressions to ensure correct boolean logic when dealing with a collection containing `undefined`.
+- `imports`: Fixed an issue where import processes were not killed, which caused non-graceful closures for the clients.
+- `lang/scanner`: Fixed an issue where inline `#` style comments were not being consumed.
+
+## 0.16.0 (October 14, 2020)
+
+BREAKING CHANGES:
+
+- `cmd/doc`: Removal of the deprecated `sentinel doc` command.
+- `sentinel`: Replace `whitelist` and `blacklist` with `allowlist` and `denylist`, as per inclusive language changes.
+
+FEATURES:
+
+- `imports/version`: Added a new import, `version`, which supplies helpers for dealing with semantic versioning.
+- `cmd/apply`: Added an `HCL` based configuration file, which will eventually deprecate the legacy `JSON` configuration.
+- `cmd/apply`: Added support for download remote policies and modules.
+- `cmd/test`: Added support for downloading remote test modules.
+- `cmd/apply`: Added support for evaluating a policy based on it's key within the configuration.
+- `cmd/apply`: Added support for running all policies in a configuration by default.
+
+## 0.15.6 (July 7, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel Integrations. There are no user-facing changes.
+
+## 0.15.5 (May 20, 2020)
+
+NOTES:
+
+- This release changes lower-level components that are used to manage policies within HashiCorp's Sentinel integrations. There are no user-facing changes.
+
+## 0.15.4 (May 13, 2020)
+
+BUG FIXES:
+
+- `imports`: Fixed an issue with the standard imports that was affecting the handling of null data within lists.
+
+## 0.15.3 (April 16, 2020)
+
+BUG FIXES:
+
+- `runtime/localast`: Fixed an issue where `IndexExpr` were not rewritten, as well as ensuring `SelectorExpr` uses any nested rewrites.
+- `lang/printer`: Fixed issue printing deeply nested structures and/or loops.
+
+IMPROVEMENTS:
+
+- `imports/decimal`: Added alias methods to improve consistency of method names within the `decimal` import. The alias methods are `is_nan` for `isnan`, as well as both `is_inf` and `is_infinite` for `isinfinite`.
+- `command/apply`: `sentinel apply` will now output the policy description when a trace is forced via `-trace`.
+- `sentinel`: Improved `String` output formatting of Policy docstring for `EvalPolicyResult`.
+
+## 0.15.2 (April 2, 2020)
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue with the `printer` that was causing a panic
+ when a `#` line comment was used with no text following it.
+- `imports/types`: Fixed an issue with the `types` import where calls to
+ `type_of` for undefined types were incorrectly returning as map types. These
+ will now be correctly be identified as undefined as expected.
+
+## 0.15.1 (March 6, 2020)
+
+BUG FIXES:
+
+- `sentinel`: Fixed how modules are managed internally in the runtime to correct
+ race conditions in concurrent scenarios and to allow for per-evaluation module
+ restrictions. This functionality is only of interest to embedded applications
+ with long-lived runtimes and is currently not implemented in any HashiCorp
+ integration.
+
+## 0.15.0 (March 5, 2020)
+
+NOTES:
+
+- **Windows Digital Signature**. Our windows releases for this version and up will be signed
+ and verified according to Microsoft's requirements.
+
+FEATURES:
+
+- `cmd/config`: Modules can now be loaded off of local disk using the Sentinel
+ CLI. For information on how to do so, see the Sentinel documentation.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: Changed modules so that they now have singleton scope when
+ loaded. Importing a module under two different aliases, or within a nested
+ module, will now share scopes - modification of state in one will alter the
+ other.
+- `lang/object`: Introduced an interface to allow the duplication of various
+ types, like collections.
+- `runtime/eval`: Changed how collection data is returned from modules. This
+ data is now duplicated to prevent accidental or deliberate circumvention of
+ the prohibition on assignment to import data, and brings behavior to parity
+ with binary imports.
+
+## 0.14.4 (February 6, 2020)
+
+IMPROVEMENTS:
+
+- `imports/time`: Changed the `add` timespace function in the `time` import to
+ allow it to take float types as durations. These values will be truncated to
+ the appropriate integer-value duration.
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where out-of-place right-hand braces were being
+ parsed as empty statements, instead of raising syntax errors.
+
+## 0.14.3 (January 22, 2020)
+
+IMPROVEMENTS:
+
+- `cmd/test`: Fail when an assertion is not found in a trace.
+- `cmd/test`: Added a message to notify when no rules were evaluated in a test.
+
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue where import aliases (the `as baz` in `import
+ "foo/bar" as baz`) were being removed when formatting with `sentinel fmt`.
+
+- `imports/http`: Fixed an issue with the http import where the client library's
+ debug logs were being printed to standard error.
+
+## 0.14.2 (January 15, 2020)
+
+NOTES:
+
+With this release, we are discontinuing support for MacOS 32-bit. 64-bit builds
+are now the only builds available for MacOS.
+
+IMPROVEMENTS:
+
+- `lang/printer`: A newline is now added to files formatted via `sentinel fmt`.
+
+BUG FIXES:
+
+- `lang/printer`: Fixed an issue in printing where multi-line expressions would
+ indent infinitely. They will now only indent once at the most. This is mostly
+ seen when using `sentinel fmt`.
+- `runtime/encoding`: Fixed an issue where if a plugin returned a deeply
+ embedded undefined value, the value was instead decoded into a map.
+
+## 0.14.1 (January 6, 2020)
+
+BUG FIXES:
+
+- `lang/parser`: Fixed an issue where reserved words could not be used as
+ selectors when the selector expression was the last lexeme on a line.
+
+## 0.14.0 (December 18, 2019)
+
+LANGUAGE CHANGES:
+
+- **Filter Expression** `filter`. A new quantifier expression, `filter` has been added.
+ `filter` returns a subset of the provided collection based on a boolean condition that
+ is asserted for each element.
+
+NOTES:
+
+- **Apple Notarization**. Our darwin releases for this version and up will be signed
+ and notarized according to Apple's requirements.
+
+## 0.13.1 (November 25, 2019)
+
+BUG FIXES:
+
+- `runtime/eval`: Parameters are no longer allowed in mock files. Adding one to
+ a mock will result in a runtime error.
+- `runtime/eval`: Fixed an issue where import calls from within mocks were
+ failing under certain circumstances.
+- `imports/decimal`: `int` should now correctly provide the truncated integer
+ representation of a decimal number, not the rounded one.
+
+## 0.13.0 (November 15, 2019)
+
+LANGUAGE CHANGES:
+
+- **Policy Parameters**. The new [policy
+ parameters](https://docs.hashicorp.com/sentinel/language/parameters) feature
+ allows authors to use a `param` declaration at the top of a policy to supply
+ values that are expected to come from outside of a policy. See the linked
+ documentation for more details.
+
+FEATURES:
+
+- `imports/http`: The [`http`
+ import](https://docs.hashicorp.com/sentinel/imports/http) is a new addition
+ to the standard library enabling the use of HTTP-accessible data from outside
+ the runtime in policy rules.
+
+BUG FIXES:
+
+- `runtime/eval`: Fixed an issue where loading other imports before a mocked
+ import would cause those imports to no longer be visible from within the
+ policy.
+
+## 0.12.0 (October 7, 2019)
+
+LANGUAGE CHANGES:
+
+- **New `case` statement**. This statement is a selection control mechanism to
+ conditionally execute a branch based on expression equality, allowing
+ simplification of complex conditional chains that may otherwise need to be
+ written with `else if`. See the [Case
+ Statements](https://docs.hashicorp.com/sentinel/language/spec#case-statements)
+ section of the Sentinel language specification for more details.
+
+IMPROVEMENTS:
+
+- `lang/semantic`: Added a semantic check to ensure usage of append is not using
+ a return value.
+
+BUG FIXES:
+
+- `runtime/eval`: Corrected an issue where calling a method on an import object
+ value that was the result of a method call on another import object value
+ would have erroneously tried to call an import of the name of the "parent"
+ import object value. Example: in `a = subject.new(); b = a.call(); c =
+ b.call()`, `b.call()` would attempt to call a method named `call` on the root
+ namespace for an import named `a`. This has now been corrected so that
+ `b.call()` will now correctly call the `call` method for the respective object
+ namespace residing in the import named `subject`.
+- `imports`: Some standard imports may have been returning null for some unknown
+ keys in objects when they should have been returning undefined. This was due
+ to an SDK issue which was corrected in SDK version 0.3.2, which has now been
+ corrected.
+- `cmd/test`: `sentinel test` will now correctly fail a policy if it encounters
+ an error.
+- `cmd/test`: `sentinel test` will now correctly display errors and other output
+ that were missing due to a formatting issue.
+- `runtime/eval`: The `append` builtin now correctly returns undefined for all
+ calls, as called for by the Specification. Note that in most cases, the
+ semantic check outlined above will trigger an error if the return value is
+ used.
+
+## 0.11.0 (September 5, 2019)
+
+LANGUAGE CHANGES:
+
+- **New builtin function:** `range()`. This function existed in the spec in
+ earlier versions but was removed as it lacked an implementation. This has now
+ been implemented and re-added to the spec. See the
+ [Range](https://docs.hashicorp.com/sentinel/language/spec#range) section of
+ the Sentinel Specification for more details.
+- Lists are now comparable. Lists are equal if their corresponding elements are
+ comparable and equal.
+- Method calls on values returned by imports are now supported. See the
+ [Imports](https://docs.hashicorp.com/sentinel/language/spec#imports) section
+ of the Sentinel Specification for more details.
+
+FEATURES:
+
+- `imports/decimal`: This is a new import designed to do exact precision
+ mathematical calculations.
+- `runtime/eval`: Compound call expressions that refer to imports (example:
+ foo.bar().baz() when `foo` is a loaded import) will now function as expected.
+ Previously, this was only supported up to the first call (example:
+ `foo.bar()`).
+- `imports/time`: Timespaces returned by calls such as `time.now` are now
+ callable. Example: `t = time.now; t.after(some_previous_time)` will now
+ function.
+
+IMPROVEMENTS:
+
+- `runtime/eval`: The implementation of comparison of non-comparable types has
+ now changed. Rather than triggering a runtime error, non-comparable types will
+ now return false when attempting to compare.
+
+## 0.10.4 (August 15, 2019)
+
+BUG FIXES:
+
+- `runtime/encoding`: Fixed an issue with conversion of null values that could
+ lead to crashes in imports.
+
+## 0.10.3 (July 15, 2019)
+
+BUG FIXES:
+
+- `command/config`: Parsing a configuration file where malformed data is
+ encountered after apparently properly-formed data will now report an error. An
+ example would be a situation where misplaced braces would cause a JSON object
+ to only parse part of the configuration file.
+
+## 0.10.2 (June 25, 2019)
+
+BUG FIXES:
+
+- `lang/parser`: Corrected an issue where parsing certain compound binary
+ expressions where the negation predicate (`not`) was in use would cause the
+ negation to have no effect. Example: `foo else "bar" not in "baz"` would have
+ been parsed and evaluated as `foo else "bar" in "baz"`, effectively producing
+ the opposite result.
+
+## 0.10.1 (May 9, 2019)
+
+BUG FIXES:
+
+- `command/test`: `sentinel test` now displays policies without
+tests as an unknown result with [no test files], instead of the somewhat
+erroneous behavior of displaying it as a PASS. A full test run with a mixture
+of passing tests and no tests still results in an overall successful result.
+
+## 0.10.0 (April 18, 2019)
+
+LANGUAGE CHANGES:
+
+- Mixed-number arithmetic operations are now allowed. Addition (`+`),
+ subtraction (`-`), multiplication (`*`), division (`/`), and remainder
+ operations (`%`) are no longer restricted to number values of the same
+ type, and can mix integer and floating point. The result of these operations
+ is always a floating-point number.
+- Remainder (modulo, `%`) operations are now allowed on floating-point numbers.
+
+
+BUG FIXES:
+
+- `lang/parser`: Fixed a bug that affected the use of `not` with `contains`,
+ `matches`, or `in` in a compound expression.
+- `runtime/eval`: Fixed error messages on evaluation errors with `contains` and
+ `in` to indicate that strings are an allowed type along with lists and maps.
+
+
+## 0.9.2 (March 15, 2019)
+
+This is a dependency update related to the changes mentioned in 0.9.1. No other
+changes have been made.
+
+## 0.9.1 (March 14, 2019)
+
+This is a patch release that is required to integrate with the latest versions
+of the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). No other
+changes have been made.
+
+## 0.9.0 (January 28, 2019)
+
+FEATURES:
+
+- `imports/strings`: Added a `join` function to the `strings` import. This can
+ be used to join a list into a string with a specific separator.
+ Multi-dimensional lists and all Sentinel primitives are supported.
+
+## 0.8.1 (January 17, 2019)
+
+BUG FIXES:
+
+- `lang/eval`: Fixed a bug that prevents the effective use of `return`
+ statements in `for` loops.
+
+## 0.8.0 (January 14, 2019)
+
+FEATURES:
+
+- Mocks can now be represented by Sentinel code. This allows for the mocking of
+ functions and other complex data structures that cannot be represented in
+ JSON. For more information on using this feature via mocks, click
+ [here](https://docs.hashicorp.com/sentinel/commands/config#mocking-with-sentinel-code).
+
+
+IMPROVEMENTS:
+
+- Import validation has now been moved to the semantic checking phase. This
+ should result in better reporting of validation errors. In addition, import
+ validation will now enforce the use of an `as` identifier when an import path
+ is not a valid identifier on its own (example: `import "foo/bar"`).
+
+## 0.7.0 (December 12, 2018)
+
+BUG FIXES:
+
+- There have been changes to the runtime in how scope is handled over multiple
+ policy executions. Scope is now correctly unique per single policy execution,
+ and values set or builtins that are overridden in one policy will no longer
+ affect those values within another.
+
+## 0.6.0 (November 30, 2018)
+
+FEATURES:
+
+- `imports/runtime`: This new import allows for one to check various aspects of
+ the Sentinel runtime as it may be embedded in the simulator or a specific
+ implementation. For now, it allows the version to be checked.
+
+## 0.5.1 (November 28, 2018)
+
+IMPROVEMENTS:
+
+- `imports/time`: Added the `zone` and `zone_string` attributes to assist with
+ validation of a timespace's zone.
+- `command/fmt`: Added a new -check flag. This option does not commit changes,
+ but instead checks to see what files need formatting and outputs them on
+ stdout.
+
+BUG FIXES:
+
+- `command/test`: Ensure that passing test results are correctly output one per
+ line. Tests are also now run in a deterministic fashion based on
+ lexicographical (alphabetical) order.
+- `imports/time`: `month_name` and `weekday_name` will now show up correctly in
+ a returned timespace result.
+
+
+## 0.5.0 (November 5, 2018)
+
+IMPROVEMENTS:
+
+- `spec`: Selectors can now contain any reserved word (example: `rule`) or
+ keyword operator (example: `any`, `all`, `is`, `not`). This only works for the
+ _selector_ part of the expression (after the first period) - the first primary
+ expression (before the first period) still needs to be an identifier that does
+ not conflict with reserved words.
+
+BUG FIXES:
+
+- The simulator should now display import function call names correctly in
+ import errors.
+
+## 0.4.0 (October 1, 2018)
+
+FEATURES:
+
+- `builtin`: Added the `bool` built-in type conversion function. Booleans will
+ also now accepted as conversion into other values as well, with the full list
+ of behaviors available in the spec.
+
+## 0.3.2 (September 27, 2018)
+
+FEATURES:
+
+- `command/apply`: `sentinel apply` now prints out messages output by the
+ `print()` function when a trace is output on policy failure, or when a trace
+ is forced with `-trace`.
+- `imports/time`: Added the `month_name` and `weekday_name` keys to the
+ timespace, which return full-English names for the month and day of the week.
+
+
+BUG FIXES:
+
+- `command/fmt`: `sentinel fmt -` Will no longer print out the filter status
+ message on the output stream when `-write=false` Is not explicitly stated.
+ This brings the behavior of the command in line with the help text.
+- `runtime`: Index operations on the right-hand-side that have negative indexes
+ that go out of range (example: `length(list) * -1 - 1`) now correctly return
+ `undefined`. left-hand-side index assignments with a out-of-range negative
+ index still return runtime errors.
+
+## 0.3.1 (August 3, 2018)
+
+BUG FIXES:
+
+- `runtime`: Basic index assignment has been implemented as per the spec.
+- `runtime`: Index expressions for lists with negative indexes will no longer panic if the
+ list index is less than `length(list) * -1`.
+
+## 0.3.0 (July 20, 2018)
+
+FEATURES:
+
+- **New standard import: `types`.** This can be used to dynamicaly detect the
+ type of some value.
+
+## 0.2.0 (April 11, 2018)
+
+FEATURES:
+
+- **New standard import: `json`.** Marshal and unmarshal JSON documents and
+ access their contents as native Sentinel values.
+- **`break` and `continue`.** These are now both specified and implemented.
+ `break` allows loop exiting and `continue` allows immediate execution of the
+ next iteration.
+
+IMPROVEMENTS:
+
+- runtime: `print()` map values are now ordered alphabetically by keys.
+
+BUG FIXES:
+
+- command/test: If no `test` block exists, test behaves like it is asserting
+ `main: true`.
+- runtime: default maximum stack depth to 500
+- runtime: `print()` map values now appear like more typical maps.
+- runtime: division by zero is an error, not a crash
+- runtime: plugins that send map values with `null` values now decode properly
+ into native Sentinel values.
+
+## 0.1.0 (September 19, 2017)
+
+Initial release.
+
+
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/commands/apply.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/commands/apply.mdx
new file mode 100644
index 0000000000..74b74d41fa
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/commands/apply.mdx
@@ -0,0 +1,66 @@
+---
+page_title: 'Command: apply'
+sidebar_current: docs-commands-apply
+description: >-
+ The `sentinel apply` command is used to execute a policy locally for
+ development purposes.
+layout: docs
+---
+
+# Command: `apply`
+
+The `sentinel apply` command is used to execute a policy locally for development
+purposes.
+
+## Usage
+
+Usage: `sentinel apply [options] POLICY`
+
+This command executes the policy file at the path specified by POLICY. In
+addition to a path to a policy, POLICY can reference a label of a
+[policy](/sentinel/configuration#policies) entry in the configuration.
+
+Use the exit code of this command to determine the exact status of the policy
+evaluation. `0` is pass, `1` is fail, `2` is undefined (fail, but because the
+result was undefined), and `3` is a runtime error. Errors unrelated to the
+policy status itself are returned with an exit status of `9`.
+
+To control the behavior of the `apply` command, create a [configuration
+file](/sentinel/configuration). With this, you can define available
+[import plugins](/sentinel/concepts/imports), mock data, and global values.
+This can help you simulate a policy embedded within an application.
+
+As of the 0.16 release, POLICY is optional. When it is not supplied,
+`sentinel apply` will run **all** policies in the supplied configuration.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-config=path` - Path to a configuration file specifying available imports,
+ mock data, globals, etc. The default is `sentinel.[hcl|json]`.
+
+- `-json` Enable JSON output. Using this mode, all other output will be
+ suppressed and the full detail of the apply will be output in a
+ machine-readable format. Enabling this implies `-trace`. See the section on
+ [tracing JSON output](/sentinel/writing/tracing#json-output) for more details.
+
+- `-json-rule=RULE` Enable JSON output, outputting only the value of the
+ specified rule. When running against a policy set, the policy must be supplied
+ to enable per-rule filtering. Implies `-json`.
+
+- `-global key=value` - Set global values. This is the same as setting `global`
+ in the configuration file, and will override any of these respective values
+ set in the configuration. The value is either a string, or a JSON number,
+ array, or object. To force strings, use quotes.
+
+- `-param key=value` - Set parameters, the same as setting `param` in the
+ configuration file. Values are handled in the same way they are with the
+ `-global` flag.
+
+- `-timeout` - Allows users to specify a timeout after which the apply command will stop running.
+ There is no timeout if not provided. The timeout needs to be specified as a Duration type, eg `100ms, 5s etc`
+
+- `-trace` - Always show the execution trace. This shows intermediate
+ boolean expression values. This always shows for failed policies.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/commands/fmt.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/commands/fmt.mdx
new file mode 100644
index 0000000000..8297316d8b
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/commands/fmt.mdx
@@ -0,0 +1,33 @@
+---
+page_title: 'Command: fmt'
+sidebar_current: docs-commands-fmt
+description: The `sentinel fmt` command formats a policy source to a canonical format.
+layout: docs
+---
+
+# Command: `fmt`
+
+The `sentinel fmt` command formats a policy source to a canonical format.
+
+## Usage
+
+Usage: `sentinel fmt [options] FILE ...`
+
+This command formats all the specified policy files to a canonical format.
+
+By default, policy files are overwritten in place. This behavior can be
+changed with the `-write` flag. If a specified FILE is `-` then stdin is
+read and the output is always written to stdout.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-write=true` - Write formatted policy to the named source file. If false,
+ output will go to stdout. If multiple files are specified, the output will
+ be concatenated directly.
+
+- `-check=false` - Don't format, only check if formatting is necessary. Files
+ that require formatting are printed, and a non-zero exit code is returned if
+ changes are required.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/commands/index.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/commands/index.mdx
new file mode 100644
index 0000000000..7177093cec
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/commands/index.mdx
@@ -0,0 +1,23 @@
+---
+page_title: Commands (CLI)
+sidebar_current: docs-commands
+description: >-
+ Sentinel provides a `sentinel` command-line interface (CLI) for developing and
+ testing policies.
+layout: docs
+---
+
+# Sentinel CLI Commands
+
+The Sentinel command-line interface (CLI) allows for the developing and testing
+of policies outside of a particular Sentinel implementation. Having a standard
+workflow to develop policies is critical for our mission of [policy as
+code](/sentinel/concepts/policy-as-code).
+
+The CLI takes a subcommand to execute. The complete list of subcommands is in
+the navigation to the left.
+
+The Sentinel CLI is a well-behaved command line application. In erroneous cases,
+a non-zero exit status will be returned. It also responds to -h and --help as
+you'd expect. To view a list of the available commands at any time, just run
+`sentinel` with no arguments.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/commands/test.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/commands/test.mdx
new file mode 100644
index 0000000000..ed50aacc22
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/commands/test.mdx
@@ -0,0 +1,53 @@
+---
+page_title: 'Command: test'
+sidebar_current: docs-commands-test
+description: The `sentinel test` command runs policies against the Sentinel test framework.
+layout: docs
+---
+
+# Command: `test`
+
+The `sentinel test` command runs policies against the Sentinel test framework.
+
+To learn more about testing, see the [testing
+policies](/sentinel/writing/testing) page.
+
+## Usage
+
+Usage: `sentinel test [options] [PATH] ...`
+
+This command runs the defined test cases against a set of policies.
+
+The test cases are expected to live at the path `test//*.[hcl|json]` relative
+to the policy being tested. `` should be the name of the policy
+excluding the file extension. The files should be in the [configuration
+file structure](/sentinel/configuration). The `test` key is used for
+assertions. Test cases ignore the root level configuration file and must have
+all required configuration provided in each test case.
+
+The PATH arguments specified may be a list of zero or more files or directories.
+When no arguments are given, it defaults to the current directory. When a
+directory is given, all policies ending with the extension `.sentinel` are
+tested.
+
+The command-line flags are all optional. The list of available flags are:
+
+- `-color` - Enable or disable colorized output. Enabled by default if
+ running interactively.
+
+- `-json` - Suppress normal output and output test results as a JSON document.
+ See the [testing JSON output](/sentinel/writing/testing#test-json-output)
+ section for more details.
+
+- `-run=regexp` - Run only tests matching the regular expression pattern.
+ A `/` splits policy name and test case name.
+
+- `-verbose` - Show tracing and log output for tests that succeed in addition
+ to tests that fail. By default, traces and logs are only shown for tests that
+ fail.
+
+- `-maxConcurrency` - Allows users to specify the number of tests to be run concurrently.
+ Defaults to the number of logical CPUs if not provided. To run tests sequentially, use `-maxConcurrency=1`
+
+- `-timeout` - Allows users to specify a timeout after which the test command will stop running.
+Defaults to 5 minutes if not provided. The timeout needs to be specified as a Duration type, eg `100ms, 5s etc`
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/concepts/enforcement-levels.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/concepts/enforcement-levels.mdx
new file mode 100644
index 0000000000..fc4b8062d0
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/concepts/enforcement-levels.mdx
@@ -0,0 +1,43 @@
+---
+page_title: Enforcement Levels
+sidebar_current: docs-concepts-levels
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Enforcement Levels
+
+Enforcement levels are a first class concept in Sentinel allowing pass/fail
+behavior to be associated separately from the policy logic. This enables
+any policy to be a warning, allow overrides, or be absolutely mandatory.
+Because this level is not part of the policy body itself, different uses of
+the same policy can have different enforcement levels.
+
+Sentinel has three enforcement levels:
+
+- **Advisory:** The policy is allowed to fail. However, a warning should be
+ shown to the user or logged. Advisory is the default enforcement level.
+
+- **Soft Mandatory:** The policy must pass unless an override is specified.
+ The semantics of "override" are specific to each Sentinel-enabled application.
+ The purpose of this level is to provide a level of privilege separation
+ for a behavior. Additionally, the override provides non-repudiation since
+ at least the primary actor was explicitly overriding a failed policy.
+
+- **Hard Mandatory:** The policy must pass no matter what. The only way to
+ override a hard mandatory policy is to explicitly remove the policy.
+ It should be used in situations where an override is not possible.
+
+## Configuring Enforcement Levels
+
+Enforcement levels are configured when a policy is deployed to a
+Sentinel-enabled application. The exact mechanism that the level is specified
+is determined by each application. Please reference the documentation for
+your Sentinel-enabled application for more information.
+
+Enforcement levels are not configured and are not known by the policy body
+itself. All policies should be written to describe exactly the behavior
+they're attempting to control. For example, a policy that restricts deploys
+to business hours should be written exactly like so. When that policy is
+configured on an application, the operator may specify that it is advisory,
+soft, or hard mandatory.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/concepts/imports.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/concepts/imports.mdx
new file mode 100644
index 0000000000..1306e1512e
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/concepts/imports.mdx
@@ -0,0 +1,32 @@
+---
+page_title: Imports
+sidebar_current: docs-concepts-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom imports](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only
+local context for making policy decisions.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that the
+import calendar allows access to engineer calendars. This import only exists
+for the purpose of this example and at the time of writing is not a real
+available import.
+
+```json sentinel
+{
+ "policy": "import \"calendar\"\n// Get the calendar for Bob for today\nbob_calendar = calendar.of(\"bob\").today\n\n// Allow this policy to pass if Bob is not on vacation.\nmain = rule { not bob_calendar.has_event(\"vacation\") }",
+ "mocks": {
+ "calendar": "has_event = func(event) {\n\treturn true\n}\nof = func(name) {\n\treturn { \"today\": { \"has_event\": has_event } }\n}"
+ }
+}
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/concepts/index.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/concepts/index.mdx
new file mode 100644
index 0000000000..ce7ac51454
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/concepts/index.mdx
@@ -0,0 +1,15 @@
+---
+page_title: Basic Concepts
+sidebar_current: docs-concepts
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Basic Concepts
+
+This section covers some high level basic concepts that are important
+to understand for day to day Sentinel usage. Every page in
+this section is recommended reading for anyone consuming
+or extending Sentinel.
+
+Please use the navigation to the left to learn more about a topic.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/concepts/language.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/concepts/language.mdx
new file mode 100644
index 0000000000..ce91f3eb8f
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/concepts/language.mdx
@@ -0,0 +1,91 @@
+---
+page_title: Policy Language
+sidebar_current: docs-concepts-language
+description: Basic concepts that are important to understand for Sentinel usage.
+layout: docs
+---
+
+# Policy Language
+
+Sentinel defines and uses its own [policy language](/sentinel/language).
+
+The language was designed to be approachable by non-programmers, since
+there are many use cases where the individual defining policy may not
+be a developer. However, the language includes constructs that
+are familiar to developers to enable powerful policies.
+
+To learn more about the language, please see the
+[writing policy section](/sentinel/writing)
+or the [language reference](/sentinel/language).
+
+An example of the policy language is shown below:
+
+```sentinel playground
+import "time"
+
+# Validate time is between 8 AM and 4 PM
+valid_time = rule { time.now.hour >= 8 and time.now.hour < 16 }
+
+# Validate day is M - Th
+valid_day = rule {
+ time.now.weekday_name in ["Monday", "Tuesday", "Wednesday", "Thursday"]
+}
+
+main = rule { valid_time and valid_day }
+```
+
+## Why?
+
+To explain why Sentinel defines and uses its own language, the question
+can be more easily split into roughly two types of languages: _configuration_ languages
+and _programming_ languages.
+
+### Configuration Languages
+
+Configuration languages are formats such as JSON, YAML, XML, etc. Many applications
+use configuration languages as their ACL format. Configuration languages
+are good for static, declarative information. ACL systems are a good fit
+for this. ACL systems are focused, providing the limiting options necessary
+to restrict access to a system.
+
+Configuration languages are not good for dynamic or logical rules. They
+typically only contain limited conditions, loops, or functions. Further
+configuration is often declarative, which can introduce complexities to
+logical statements that depend on ordering.
+
+The use cases that Sentinel was built for require the ability to perform
+complex behavior that didn't model well into a configuration format or a
+declarative form.
+
+### Programming Languages
+
+The Sentinel language was designed with the following goals:
+
+- **Non-programmer friendly.** Sentinel was built to be used by non-programmers.
+ For the use cases we discovered, non-programmers needed the ability to
+ enforce certain rules within a system. For example, a person responsible for
+ compliance may need to insert rules into a system.
+
+- **Programmer friendly.** At the same time, the language needed to support
+ programmer-friendly constructs such as conditionals, loops, and functions
+ for complex policies that a programmer may be writing.
+
+- **Embeddable.** Sentinel is embedded in existing software. The language
+ itself needs to be easily embeddedable. Further, Sentinel was designed
+ to be embedded in [HashiCorp](https://www.hashicorp.com) software
+ written in Go, so it needed to be easily embeddable in Go.
+
+- **Safe.** The language is used in highly security-sensitive environments.
+ It must not be able to crash its host system or access system resources
+ without explicit approval.
+
+Prior to building our own language, Sentinel evaluated other languages.
+Some languages worked quite well. As we continued to develop Sentinel, we
+found that the flexibility and control over designing our own language
+outweighed the costs.
+
+Because the language itself doesn't need to be general purpose, building
+a language focused on policy proved to be the best route for Sentinel.
+It allows us to build powerful tracing capabilities for debugging, make
+interesting performance choices, and extend the language as needed in the
+future.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/concepts/policy-as-code.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/concepts/policy-as-code.mdx
new file mode 100644
index 0000000000..6dac593e18
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/concepts/policy-as-code.mdx
@@ -0,0 +1,72 @@
+---
+page_title: Policy as Code
+sidebar_current: docs-concepts-pac
+description: >-
+ Policy as code is the idea of writing code in a high-level language to manage
+ and automate policies. By representing policies as code in text files, proven
+ software development best practices can be adopted such as version control,
+ automated testing, and automated deployment.
+layout: docs
+---
+
+# Policy as Code
+
+Policy as code is the idea of writing code in a high-level language to
+manage and automate policies. By representing policies as code in text files,
+proven software development best practices can be adopted such as version
+control, automated testing, and automated deployment.
+
+Many existing policy or ACL systems do not practice policy as code. Many
+policies are set by clicking in a GUI, which isn't easily repeatable nor
+versionable. They usually don't provide any system for testing policies
+other than testing an action that would violate the policy. This makes it
+difficult for automated testing. And the policy language itself varies by
+product.
+
+Sentinel is built around the idea and provides all the benefits of policy as code.
+
+## Benefits
+
+Policy as code provides a number of benefits:
+
+- **Sandboxing.** Policies provide the guardrails for other automated systems.
+ As the number of automated systems grow, there is also a growing need to
+ protect those automated systems from performing dangerous actions. Manual
+ verification is too slow; policies need to be represented as code to
+ keep up with other automated systems.
+
+- **Codification.** By representing policy logic as code, the information
+ and logic about a policy is directly represented in code and can be augmented
+ with comments rather than relying on oral tradition to learn about the
+ reason for policies.
+
+- **Version Control.** Policies are encouraged to be stored as simple text
+ files managed by a version control system. This lets you gain all the
+ benefits of a modern VCS such as history, diffs, pull requests, and more.
+
+- **Testing.** Policies are just code. Their syntax and behavior can be
+ [easily validated with Sentinel](/sentinel/commands/test). This also encourages
+ automated testing such as through a CI. Paired with a VCS system, this
+ allows a pull request workflow to verify that a policy keeps the system
+ behavior as expected before merging.
+
+- **Automation.** With all policies as code in simple text files, various
+ automation tools can be used. For example, it is trivial to create tools
+ to automatically deploy the policies into a system.
+
+## Sentinel and Policy as Code
+
+Sentinel fully embraces policy as code in a number of ways:
+
+- **Language.** All Sentinel policies are written using the
+ [Sentinel language](/sentinel/concepts/language). This language is
+ made to be inputted directly to text files. As an additional benefit,
+ all Sentinel-enabled applications share the same policy language.
+
+- **Development.** Sentinel provides a [CLI](/sentinel/commands)
+ for development and testing. This local CLI can be used to verify policies
+ before deploying them to a system.
+
+- **Testing.** Sentinel provides a [test framework](/sentinel/commands/test)
+ designed specifically for automation. This allows developers and CI systems
+ to further verify policies.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/configuration/index.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/configuration/index.mdx
new file mode 100644
index 0000000000..c7d4a8e3bd
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/configuration/index.mdx
@@ -0,0 +1,422 @@
+---
+page_title: Sentinel CLI Configuration File Syntax
+sidebar_current: docs-configuration
+description: >-
+ The Sentinel CLI's configuration file can be used to control the
+ behavior of the simulator during apply and test operations.
+layout: docs
+---
+
+# Sentinel CLI Configuration File Syntax
+
+The Sentinel CLI's configuration file can be used to control the behavior
+of the simulator during [`apply`](/sentinel/commands/apply) and
+[`test`](/sentinel/commands/test) operations.
+
+## Usage
+
+The configuration file is used in different ways depending on what operation you
+are trying to execute.
+
+### Apply
+
+When using `sentinel apply`, configuration files that have the `.hcl`
+extension are loaded into a single configuration. This allows for configuration
+to be split across multiple files, which is useful for large policy sets. To
+supply a single configuration file, the `-config=FILE` flag can be used, where
+`FILE` is the path to the configuration file. This argument also allows for a
+`.json` configuration file to be supplied. The default is `sentinel.[hcl|json]`.
+
+See the [apply command](/sentinel/commands/apply) reference for more details.
+
+### Test
+
+When using `sentinel test`, each file matching `test//*.[hcl|json]` is a
+configuration file representing a single test case, where `` is the name
+of the policy being tested. Configure assertions for each [test
+case](#test-cases) using the test section.
+
+See the [test command](/sentinel/commands/test) reference for more details.
+
+#### Remote sources
+
+When declaring a `policy` or `module` block within the configuration, a
+`source` attribute must be supplied. This `source` should declare the location
+of the resource, which can be either a local file or a URL pointing to a remote
+file. Examples of local `source` attributes are detailed both in the
+[Policies](#policies) and [Modules](#modules) sections of this page. Below
+are a few examples of remote `source` attributes.
+
+Example:
+
+```hcl
+policy "foo" {
+ source = "git::https://github.com/hashicorp/sentinel-example.git//main.sentinel"
+ enforcement_level = "hard-mandatory"
+}
+```
+
+The above example will fetch the policy from the provided URL and place it into
+the cache ready for use. Be sure to read the
+[Remote Sources](/sentinel/configuration/remote-sources) page for an
+in-depth overview.
+
+## Configuration File Reference
+
+The format of the configuration file is either JSON or HCL. The available
+blocks are:
+
+- [`mock`](#mock-imports) - A mock import specification.
+- [`policy`](#policies) - Configuration for a [policy](/sentinel/writing).
+- [`import`](#imports) - Configuration for a [module](/sentinel/extending/modules), [standard import](/sentinel/imports), [plugin](/sentinel/extending/plugins) or
+ [static import](/sentinel/extending/static-imports).
+- [`global`](#globals) - Data that is inserted into the global scope.
+- [`param`](#parameters) - Values for [parameters](/sentinel/language/parameters) defined in the policy.
+- [`test`](#test-cases) - Test cases for the [test command](/sentinel/commands/test).
+- [`sentinel`](#sentinel) - Configuration to manage the Sentinel runtime
+
+### Mock Imports
+
+Mock imports allow running a policy with an
+[import](/sentinel/concepts/imports) that you may not have access to or is
+too difficult to run. For example, it can be easier to mock data for [Terraform
+Enterprise](https://www.terraform.io/docs/enterprise/sentinel/index.html)
+locally instead of having to run a workspace through a plan/policy check cycle.
+
+Mock imports are specified using the `mock` block, labelled by the import
+name that you want to mock. For example, if you wanted to mock the `time`
+import, you could create an entry with the `time` label pointing to the data
+you want to mock.
+
+The data can take one of two forms:
+
+#### Mocking static data
+
+Static data can be mocked directly via HCl using the `data` attribute on the
+`mock` block.
+
+Example:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ hour = 9
+ minute = 42
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"time\"\n\nmain = rule { time.now.hour is 9 }",
+ "mocks": {
+ "time": "now = { \"hour\": 9, \"minute\": 42 }"
+ }
+}
+```
+
+#### Mocking with Sentinel code
+
+There are some Sentinel types that raw data cannot mock, such as functions,
+and maps that don't have string keys. To mock this data, you can use
+a Sentinel file itself. In this case, you can set the `module` attribute to
+a file with the Sentinel code in it, relative to the current working directory in
+the event of `apply`, or relative to the configuration file location in the
+event of `test`.
+
+Note that `main` is not required in a mock data file.
+
+Example:
+
+```hcl
+mock "foo" {
+ module {
+ source = "mock-foo.sentinel"
+ }
+}
+```
+
+`mock-foo.sentinel` would contain:
+
+```sentinel
+bar = func() {
+ return "baz"
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"foo\"\n\nmain = rule { foo.bar() is \"baz\" }",
+ "mocks": {
+ "foo": "bar = func() { return \"baz\" }"
+ }
+}
+```
+
+### Policies
+
+The `policy` block allows for the the configuration of policies to assist
+with integrating into other commands.
+
+Each `policy` block has a label to identify the policy, with the block
+providing configuration of the policy. The available configuration options for
+policy are `source`, `enforcement_level` and an optional `params` attribute. The
+`source` key provides the location of the policy, while `enforcement_level` is
+currently used by integrations such as [HCP Terraform](/terraform/cloud-docs/policy-enforcement/manage-policy-sets#enforcement-levels).
+For more information on the `source` value, see [Policy and Module Sources](#policy-and-module-sources).
+
+The optional `params` attribute is used to provide values to [parameters](/sentinel/language/parameters)
+defined within the policy file.
+
+```hcl
+policy "foo" {
+ source = "foo.sentinel"
+ enforcement_level = "hard-mandatory"
+ params = {
+ "name" = "Sample"
+ }
+}
+```
+
+If `enforcement_level` is not supplied, it will fallback to the `advisory` level.
+
+### Imports
+
+Import configuration allows you to configure [modules](/sentinel/extending/modules),
+[standard imports](/sentinel/imports) and [import plugins](/sentinel/extending/plugins).
+
+The `import` is a multi-label block, with the first label determining the kind of import
+being configured. Currently the supported values for this label are:
+
+* [`"module"`](#import-modules) for configuring import modules
+* [`"plugin"`](#import-plugins) for configuring standard imports and import plugins
+* [`"static"`](#static-imports) for configuring static data files as imports
+
+#### Import Modules
+
+The `import "module"` block allows you to map a Sentinel import to a
+[module](/sentinel/extending/modules). The Sentinel CLI will parse the module
+source and make it available for evaluation with the policy.
+
+Each block has a label aligning to the name of the import, with
+the block set to the configuration object for the module. Currently, the only
+value within the configuration object is `source`, which points to the
+location of the module. For more information on the `source` value, see
+[Policy and Module Sources](#policy-and-module-sources).
+
+```hcl
+import "module" "foo" {
+ source = "modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "modules/bar.sentinel"
+}
+```
+
+Note when using [`sentinel test`](/sentinel/commands/test), module paths must be
+specified relative to the location of the configuration file.
+
+```hcl
+import "module" "foo" {
+ source = "../../modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "../../modules/bar/sentinel"
+}
+```
+
+See [Writing and Using a
+Module](/sentinel/extending/modules#writing-and-using-a-module) for more details
+on using a module with this configuration.
+
+### Import Plugins
+
+Import `"plugin"` configuration allows you to configure both [standard imports](/sentinel/imports)
+as well as [import plugins](/sentinel/extending/plugins) that can be used within
+a policy. If defining a custom import plugin, the Sentinel CLI will launch the
+plugin, connect to it, configure it, and execute it as needed by the policy.
+
+The available configuration attributes for an `import "plugin"` are:
+
+- `source` (string, required) - Path to the import plugin executable.
+- `args` (list of string, optional) - A list of arguments to pass to the
+ executable when starting it.
+- `env` (map of string to string, optional) - A set of environmental variables
+ to set when launching the plugin.
+- `config` (map, optional) - Configuration for the plugin itself. This is
+ specific to each individual plugin. Please reference the documentation for
+ the plugin for more information.
+
+Standard import example:
+
+```hcl
+import "plugin" "time" {
+ config = { "timezone": "Australia/Brisbane" }
+}
+```
+
+With the above configuration, the following policy would pass:
+
+```json sentinel
+{
+ "policy": "import \"time\"\n\nmain = time.now.zone_string is \"+10:00\"",
+ "mocks": {
+ "time": "now = { \"zone_string\": \"+10:00\" }"
+ }
+}
+```
+
+Custom plugin example:
+
+```hcl
+# flipper receives a boolean and returns the opposite
+import "plugin" "flipper" {
+ source = "/path/to/flipper"
+}
+```
+
+```json sentinel
+{
+ "policy": "import \"flipper\"\n\nmain = flipper.flip(false)",
+ "mocks": {
+ "flipper": "flip = func(input) { return not input }"
+ }
+}
+```
+
+#### Static Imports
+
+Static import configuration allows you to supply a data file and make
+it available to a policy via an import statement.
+
+The available configuration attributes for an `import "static"` are:
+
+- `source` (string, required) - Path to the static data file.
+- `format` (string, required) - The format of the static data. Currently, only
+ the "json" value is supported.
+
+Static import example:
+
+```hcl
+import "static" "people" {
+ source = "./data/people.json"
+ format = "json"
+}
+```
+
+```json sentinel
+{
+ "policy": "import \"people\"\n\nmain = length(people.names) > 0",
+ "mocks": {
+ "people": "names = [\"John Smith\", \"Jane Smith\"]"
+ }
+}
+```
+
+### Globals
+
+Global data is injected directly into the global scope of the policy.
+This can be used to simulate global data that may be injected by a
+Sentinel-enabled application.
+
+Global data is specified by the `global` key. The value is a map of
+variables to inject directly into the running policy.
+
+Example:
+
+```hcl
+global "time" {
+ value = {
+ now = {
+ day = 31
+ }
+ }
+}
+```
+
+With the above configuration, the following policy would pass. Notice
+that we don't have to do any `import`. The values of `global` are
+injected directly into the global scope.
+
+```json sentinel
+{
+ "policy": "main = time.now.day == 31",
+ "globals": {
+ "time": {
+ "now": {
+ "day": 31
+ }
+ }
+ }
+}
+```
+
+### Parameters
+
+The `param` section allows you to supply values for the
+[parameters](/sentinel/language/parameters) found in a policy. Values
+entered here will satisfy required parameters in a policy, in addition to
+override any defaults. Note that any of the manual parameter value methods
+supported by `sentinel apply` will override the values set here.
+
+```hcl
+param "foo" {
+ value = "bar"
+}
+```
+
+### Test Cases
+
+Test cases specify the test cases for the [test command](/sentinel/commands/test).
+
+Tests are specified by the `test` key. The value of this is a map of
+string to boolean. The key is the name of a rule and the value is the
+expected value of that rule. If a rule is not specified, than any value is
+allowed.
+
+Example:
+
+```hcl
+test {
+ rules = {
+ main = true
+ days = []
+ }
+}
+```
+
+For the policy:
+
+```json sentinel
+{
+ "policy": "valid_days = rule {\n\tfilter days as d {\n\td is \"monday\"\n\t}\n}\n\nmain = rule { valid_days is empty }",
+ "globals": {
+ "days": []
+ }
+}
+```
+
+For more information, read about the [test command](/sentinel/commands/test).
+
+### Sentinel
+
+The `sentinel` block provides configuration specific to the Sentinel runtime.
+
+An example of how the `sentinel` block can be used to enable the [terraform](/sentinel/features/terraform)
+feature is as follows:
+
+```hcl
+sentinel {
+ features = {
+ terraform = true
+ }
+}
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/configuration/overrides.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/configuration/overrides.mdx
new file mode 100644
index 0000000000..ca1a6842b1
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/configuration/overrides.mdx
@@ -0,0 +1,80 @@
+---
+page_title: Override Files
+sidebar_current: docs-configuration-overrides
+description: >-
+ The Sentinel CLI's configuration can be overriden using override files.
+ Override files merge additional settings into existing configuration.
+layout: docs
+---
+
+# Override Files
+
+As outlined in the configuration [overview](/sentinel/configuration#Apply),
+the normal behavior of Sentinel is to load all `.hcl` files into a single
+configuration object.
+
+In addition to appending multiple configuration files, Sentinel allows for the
+rare case of overriding configuration through override files. Override files
+are any files that match either `_override.{hcl,json}` or `override.{hcl,json}`.
+
+Sentinel will parse override files after it has loaded the primary
+configuration, and then process each override file in turn (in lexicographical
+order). All top level blocks other than [test](/sentinel/configuration#test-cases)
+require the block to already be defined in the primary configuration. It will
+then attempt to merge the override block into the existing configuration.
+
+## Example
+
+If you had a Sentinel configuration `sentinel.hcl` that contained the following:
+
+```hcl
+policy "main" {
+ source = "./main.sentinel"
+ enforcement_level = "advisory"
+}
+```
+
+And you created a file `override.hcl` that contained the following:
+
+```hcl
+policy "main" {
+ enforcement_level = "soft-mandatory"
+}
+```
+
+Sentinel will merge the contents of the override into the former, providing the
+following configuration:
+
+```hcl
+policy "main" {
+ source = "./main.sentinel"
+ enforcement_level = "soft-mandatory"
+}
+```
+
+## Merging Behavior
+
+The general rule, which applies in most cases, is:
+
+- A top-level block in an override file merges with a block in a normal
+ configuration file that has the same block header. The block header is the
+ block type and any quoted labels that follow it.
+- Within a top-level block, an attribute argument within an override block
+ replaces any argument of the same name in the original block.
+- Within a top-level block, any nested blocks within an override block replace
+ all blocks of the same type in the original block. Any block types that do not
+ appear in the override block remain from the original block.
+- The contents of nested configuration blocks are not merged.
+- The resulting merged block must still comply with any validation rules that
+ apply to the given block type.
+
+If more than one override file defines the same top-level block, the overriding
+effect is compounded, with later blocks taking precedence over earlier blocks.
+Overrides are processed in order first by filename (in lexicographical order)
+and then by position in each file.
+
+## Required Attributes
+
+It is important to note that all attributes inside an override file are
+considered to be optional, meaning that an override file can itself fail
+validation rules for block types.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/configuration/remote-sources.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/configuration/remote-sources.mdx
new file mode 100644
index 0000000000..b64e4d3abc
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/configuration/remote-sources.mdx
@@ -0,0 +1,165 @@
+---
+page_title: Remote Sources
+sidebar_current: docs-configuration-remote-sources
+description: >-
+ There are a number of options for formatting the URL provided to the
+ source attribute on policy and module blocks.
+layout: docs
+---
+
+# Remote Sources
+
+There are a number of options for formatting the URL provided to the
+`source` attribute on `policy` and `import "module"` blocks. This flexibility
+should solve most use cases and allow for reusable policies and modules.
+
+### Supported Protocols
+
+-> **NOTE:** All protocols are supported on the CLI, however each production
+Sentinel integration may not. Be sure to read the appropriate integration
+documentation to check the supported protocols.
+
+- Local filesystem
+- Git
+- Mercurial
+- HTTP
+- Amazon S3
+- Google GCP
+
+### Forced Protocols
+
+Due to the ambiguous nature of URLs, it is possible to force a particular
+protocol to be used during the fetch. To achieve this, simply prefix the url
+with the appropriate protocol as listed below:
+
+- `file` - Local filesystem
+- `git` - Git
+- `hg` - Mercurial
+- `http` or `https` - HTTP
+- `s3` - Amazon S3
+- `gcp` - Google GCP
+
+Example:
+
+```
+git::http://github.com/hashicorp/sentinel.git
+```
+
+The above would download the provided HTTP URL using the Git protocol.
+
+### Protocol Options
+
+In addition to some global options, there are options available to specific
+protocols to perform features such as authentication.
+
+#### Subdirectories / File Pathing
+
+When supplying URLs that point to a directory (eg. `git`), a subdirectory and
+file can be provided by suffixing the URL with `//` and the path. For example,
+if we have a git repository that has a policy, `main.sentinel` in the root, we
+could directly fetch the file by using the following:
+
+```
+git::https://github.com/hashicorp/sentinel-docs-example.git//main.sentinel
+```
+
+Or, if the file was nested in the `policies` folder:
+
+```
+git::https://github.com/hashicorp/sentinel-docs-example.git//policies/main.sentinel
+```
+
+#### Checksumming
+
+For downloads of any protocol, you can automatically verify a checksum. To
+checksum a file, append a `checksum` query parameter to the URL. The parameter
+value can be in the format of type:value or just value, where type is "md5",
+"sha1", "sha256", "sha512" or "file" . The "value" should be the actual
+checksum value or download URL for "file". When type part is omitted, type will
+be guessed based on the length of the checksum string.
+
+```
+./foo.sentinel?checksum=md5:b7d96c89d09d9e204f5fedc4d5d55b21
+
+./foo.sentinel?checksum=b7d96c89d09d9e204f5fedc4d5d55b21
+
+./foo.sentinel?checksum=file:./foo.txt.sha256sum
+```
+
+#### Unarchiving
+
+An `archive` query parameter can be supplied to explicitly state what format
+to attempt to extract, if required. The supported formats are:
+
+- `tar.gz` and `tgz`
+- `tar.bz2` and `tbz2`
+- `tar.xz` and `txz`
+- `zip`
+- `gz`
+- `bz2`
+- `xz`
+
+If a URL has an extension that matches one of the supported formats, it will
+use that format to unarchive.
+
+```
+./file.zip
+```
+
+The above would use the `zip` format to unarchive.
+
+```
+./path/to/file?archive=zip
+```
+
+Would force the `zip` format. If for some reason you required archiving to be
+disabled, this can also be achieved by using the `false` value.
+
+```
+./path/to/file?archive=false
+```
+
+#### Git (`git`)
+
+- `ref` - The Git ref to checkout. This is a ref, so it can point to a commit
+ SHA, a branch name, etc. If it is a named ref such as a branch name, it will
+ be updated to the latest on each get.
+- `sshkey` - An SSH private key to use during clones. The provided key must be
+ a base64-encoded string. For example, to generate a suitable sshkey from a
+ private key file on disk, you would run base64 -w0 \.
+ **Note:** Git 2.3+ is required to use this feature.
+- `depth` - The Git clone depth. The provided number specifies the last `n`
+ revisions to clone from the repository.
+
+The git getter accepts both URL-style SSH addresses like
+`git::ssh://git@example.com/foo/bar`, and "scp-style" addresses like
+`git::git@example.com/foo/bar`. In the latter case, omitting the `git::` forced
+prefix is allowed if the username prefix is exactly git@.
+
+The "scp-style" addresses cannot be used in conjunction with the `ssh://`
+scheme prefix, because in that case the colon is used to mark an optional port
+number to connect on, rather than to delimit the path from the host.
+
+#### Mercurial (`hg`)
+
+- `rev` - The Mercurial revision to checkout.
+
+#### HTTP (`http` or `https`)
+
+Basic Authentication
+
+To use HTTP basic authentication, simply prepend `username:password@` to the
+hostname in the URL such as `https://Aladdin:OpenSesame@www.example.com/index.html`.
+All special characters, including the username and password, must be URL
+encoded.
+
+#### S3 (`s3`)
+
+The following query parameters are available and will take priority when
+authenticating against an S3 bucket.
+
+- `aws_access_key_id` - AWS access key.
+- `aws_access_key_secret` - AWS access key secret.
+- `aws_access_token` - AWS access token if this is being used.
+- `aws_profile` - Use this profile from local ~/.aws/ config. Takes priority
+ over the other three.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/consul.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/consul.mdx
new file mode 100644
index 0000000000..7a3aaaa697
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/consul.mdx
@@ -0,0 +1,47 @@
+---
+page_title: Consul and Sentinel
+sidebar_current: docs-app-consul
+description: >-
+ Consul Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies are applied during writes to
+ the KV Store.
+layout: docs
+---
+
+# Consul
+
+[Consul Enterprise](https://www.hashicorp.com/products/consul/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies are applied during writes to the KV Store.
+
+Sentinel policies have access to the key/value being written. They can be used to
+allow or deny the modification. The information that Sentinel policies have access
+to will expand over time.
+
+The Consul integration with Sentinel is documented in depth in the
+[Consul Enterprise documentation](https://www.consul.io/docs/guides/sentinel.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Input validation depending on the name of the key.**
+
+```sentinel
+main = rule { valid_key() }
+
+required = [
+ ["port", "\\d+"], # ports must be integers
+ ["name", "\\w+"], # name must be a word
+]
+
+valid_key = func() {
+ for required as v {
+ if key is v[0] {
+ return value matches v[1]
+ }
+ }
+
+ return false
+}
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/extending/index.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/extending/index.mdx
new file mode 100644
index 0000000000..a39cb0384d
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/extending/index.mdx
@@ -0,0 +1,52 @@
+---
+page_title: Extending Sentinel
+sidebar_current: docs-extending
+description: Learn how to create your own Sentinel imports to extend Sentinel's functionality.
+layout: docs
+---
+
+# Extending Sentinel
+
+-> **NOTE:** Sentinel's extensibility is currently undergoing large
+improvements - watch this space for future updates!
+
+Sentinel can be extended by writing additional
+[imports](/sentinel/language/imports). These imports can bring new functionality
+to Sentinel by allowing access to new data or by the addition of new functions.
+This section is designed to show you how to accomplish this extensibility and
+describe how it works.
+
+Currently, the only usable way to add additional imports is via
+[modules](#modules). While [plugins](#plugins) currently work under the Sentinel
+CLI, no Sentinel integration currently supports their use.
+
+-> You may also be interested in advanced details on [import
+internals](/sentinel/extending/internals).
+
+## Modules
+
+Modules allow you to re-use Sentinel code as an import. Modules are loaded
+external of policies and mapped to imports. This is usually configured via a
+file such as the [CLI configuration file](/sentinel/configuration).
+
+See the [modules](/sentinel/extending/modules) section for more details.
+
+## Plugins
+
+-> **NOTE:** Plugins are currently only supported on the CLI and not in any
+production Sentinel integration, with the exception of existing configuration
+in [Nomad](https://www.nomadproject.io/docs/configuration/sentinel).
+
+Imports can also be implemented as plugins when Sentinel code itself is too
+restrictive to represent the import, or if you need access to features not
+normally available to the Sentinel runtime.
+
+See the [plugins](/sentinel/extending/plugins) section for more details.
+
+## Static Imports
+
+Static imports provide support for loading data files into the Sentinel runtime,
+making them available as an import.
+
+See the [static imports](/sentinel/extending/static-imports) section for more
+details.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/extending/internals.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/extending/internals.mdx
new file mode 100644
index 0000000000..7133e6df1b
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/extending/internals.mdx
@@ -0,0 +1,252 @@
+---
+page_title: >-
+ Extending Sentinel: Internals
+sidebar_current: docs-extending-internals
+description: This Section covers some details about Sentinel's import internals.
+layout: docs
+---
+
+# Extending Sentinel: Internals
+
+-> **Advanced Topic!** This page covers low-level technical details of Sentinel.
+You don't need to understand these details to effectively use Sentinel. The
+details are documented here for those who wish to learn about them.
+
+This section covers some of the internal details of Sentinel's import system.
+The goal of this section is to help remove notion of "magic" from Sentinel, to
+allow you to trust and understand what Sentinel is doing with imports, and also
+to help practitioners and developers alike understand the differences between
+the ways that imports can be loaded into Sentinel.
+
+## Language and Runtime
+
+Understanding the import system in Sentinel generally requires understanding of
+two key concepts within Sentinel: the _language_, and the _runtime
+implementation_ of the language.
+
+The Sentinel language and the rules governing it are detailed in the [Sentinel
+Language Specification](/sentinel/language/spec). A runtime implementation must
+conform to the rules laid out in the specification at a minimum. However, to
+meet the design goals of a particular implementation, the runtime may implement
+features on top of the language. The subtle implementation details within the
+runtime may also vary while still being compliant with the specification.
+
+The core runtime included within the [Sentinel CLI](/sentinel/commands) is
+sometimes referred to as the _reference implementation_ of the Sentinel
+language. This runtime is also the main runtime embedded within each
+Sentinel-enabled HashiCorp product such as HCP Terraform, Terraform Enterprise, Vault
+Enterprise, Nomad Enterprise, and Consul Enterprise. The runtime includes an API
+to allow these integrations implement Sentinel in as robust or as restrictive of
+a way as possible, so depending on the implementation, your experience may vary.
+
+## Sentinel's Import Model
+
+The concept of Imports within Sentinel is a _language_ feature. You can see the
+exact rules governing imports within the
+[Imports](/sentinel/language/spec#imports) section of the specification.
+
+Within the runtime, there are currently two major classifications of imports:
+
+- **Modules:** The mapping of Sentinel code to an import, essentially mapping a
+ scope of values to a particular package.
+- **Binary Imports:** A grouping of embedded and external plugins written using
+ the Sentinel SDK. These are comprised of internal or embedded imports (usually
+ the [standard imports](/sentinel/imports) and imports included by an
+ integration) and import plugins.
+
+Below is a diagram explaining in brief the relationship between the langauge
+and runtime features.
+
+
+
+This kind of topology ultimately minimizes the visibility of implementation
+internals to the actual policy language. This allows us to keep the Sentinel
+language itself simple and keep concerns such as module or plugin management,
+validation, and configuration out of the language. These kinds of things would
+impact the readability of a policy, possibly present security challenges, and
+would ultimately be of no use to more minimal embedded applications.
+
+### Implementation Differences Between Import Types
+
+As one would imagine, both modules and binary imports are loaded in much
+different ways, and the methods that they expose data to Sentinel differ as
+well. The effect is generally the same however across both, and we take care to
+ensure that both modules and binary imports behave as close as possible to each
+other, however there are some subtle differences:
+
+- Modules currently support the exporting of rules, but do not support function
+ calls with object receiver data (as seen in some standard imports such as
+ [`decimal`](/sentinel/imports/decimal), [`http`](/sentinel/imports/http), and
+ [`time`](/sentinel/imports/time)). This will be coming in a later release.
+- Binary imports cannot export rules. However, they do support object receiver
+ data (see [`framework.New`](/sentinel/extending/plugins#framework-new) in
+ [Extending Sentinel: Plugins](/sentinel/extending/plugins)).
+
+## Modules
+
+_Modules_ are essentially Sentinel code that is intended to be re-used in multiple
+policies as an import.
+
+The mechanism of module loading is fairly straightforward: during initialization
+of the Sentinel runtime, modules are parsed along with policies. The parsed
+_abstract syntax tree_ (AST) for each module is loaded into the runtime under
+the specified _import path_. These are then passed to the lower-level
+interpreter during the evaluation of each policy.
+
+As with policy code, during evaluation, a module's AST is walked to execute the
+code within that specific module. The module data is then stored within an
+specific scope mapped to the import name. The module data can then be accessed
+through the import via selector expressions and function calls (e.g.,
+`foo.some_map` or `foo.some_func()` for a module loaded via `import "foo"`).
+
+The AST for a module is only walked if a policy imports it, and it is _only
+walked once_. That means if a policy and a module import the same module, or a
+policy imports the same module twice (using [import
+aliases](/sentinel/language/imports#aliases)), those instances will share state.
+If you call a function that alters a singleton variable within the module, that
+singleton will be modified for all instances of the import.
+
+-> **Fun fact!** When you [mock an import with Sentinel
+code](/sentinel/configuration#mocking-with-sentinel-code), you are actually
+using a module. The module is loaded with a flag that allows it to override an
+existing import, which would normally give a runtime error.
+
+### Map and List Cloning for Module Calls
+
+It's also worthwhile noting that map and list values are cloned for modules.
+
+Consider the case where you have a module with a map singleton.
+
+```sentinel
+// modules/foo.sentinel
+a_map = {"a": "b"}
+```
+
+Normally, assignment to the singleton, even when using an index expression, is
+blocked, so `foo.a_map["c"] = "d"` would not pass semantic checking.
+
+However, even when you try to do assignment via an intermediary, you will still
+be only modifying the copy, and the original will not be affected:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+a_map_copy = foo.a_map
+a_map_copy["c"] = "d" // Only modifies copy, does not modify module singleton
+print(foo.a_map) // Still only prints {"a": "b"}
+```
+
+This is to ensure that modules are consistent with binary imports in how return
+data is handled, and the expectation that most non-object data within an import
+is read-only, with the exception of modification of singleton data via
+functions. As return of complex object and collection data in a binary import is
+always via copy, it's not possible to modify any value elements within the
+import in a similar fashion.
+
+There are also some other implications of this cloning that are of note:
+
+- Rules are not cloned, either within a list or map, or by themselves. This is
+ to ensure that [memoization](/sentinel/language/rules#lazy-and-memoized)
+ functions correctly. As only a module can export rules at this time, there is no
+ parity for this in binary imports.
+- Functions are _not_ cloned. This mainly has implications for scope - for
+ example, if you assign a function to another value, or assign an object to a
+ value that has a method that utilizes a global module variable, those calls will
+ reference and/or modify those values.
+
+Functions, while represented differently in binary imports, generally follow the
+same logic here - as they would be invoked in the same package within the binary
+import, any singleton values they accessed there would be affected in a similar
+fashion. As rules are pseudo-functions, this generally makes sense here too.
+
+## Binary Imports
+
+_Binary imports_ is a grouping referring to all imports that are designed with
+the [Sentinel SDK](https://github.com/hashicorp/sentinel-sdk). These can be
+either internally embedded, or executed via a plugin.
+
+All imports found in the [standard library](/sentinel/imports) are binary
+imports, embedded internally.
+
+The main difference between internally embedded imports and external plugins is
+whether or not the runtime needs to execute a plugin binary as part of
+[lifecycle management](#lifecycle), and whether or not it needs to
+[communicate](#communication) over RPC. Internal binary imports do not require
+these steps, so their execution model is somewhat simpler; however, technically,
+they still are the same as external plugins in terms of design model and value
+conversion. Most standard imports are even tested using the [SDK test
+suite](/sentinel/extending/plugins#testing), which builds the import as an
+external plugin.
+
+The remainder of this section discusses topics mostly relevant to external
+plugins. A large amount of technical detail regarding binary import development
+(also applicable to embedded binary imports) can be seen on the
+[Plugins](/sentinel/extending/plugins) page.
+
+-> **NOTE:** At this time, plugins can only be written for the Sentinel CLI, and
+cannot be used with any of Sentinel's integrations.
+
+### Plugin Basics
+
+Sentinel plugins are built on top of the HashiCorp [go-plugin
+system](https://github.com/hashicorp/go-plugin). This is the same plugin system
+powering all pluggable HashiCorp tools such as
+[Terraform](https://www.terraform.io), [Vault](https://www.vaultproject.io), and
+more. go-plugin is a system that has been used in production for millions of
+users for over 5 years.
+
+Plugins are executable binaries. Sentinel is responsible for launching and
+managing the lifecycle of plugins. Once a plugin is running, Sentinel
+communicates with the plugin via [gRPC](https://grpc.io/). The [protocol is open
+source](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/import.proto)
+to allow anyone to write a Sentinel plugin.
+
+### Lifecycle
+
+Sentinel launches plugins and is responsible for plugin lifecycle.
+
+The runtime optimizes for latency by launching the plugin as soon as it is
+configured, rather than when a policy requires it. This ensures that the plugin
+is ready to be used immediately. A plugin is only closed when Sentinel is
+reconfigured to no longer allow that plugin or if the Sentinel-enabled
+application is closing.
+
+When a plugin is removed from the configuration, Sentinel may wait to shut down
+the plugin until all currently executing policies that are using that plugin
+complete. New policy executions will not be allowed to use the old plugins.
+
+Plugins are automatically restarted if they shut down unexpectedly. Policies
+that were executing while this happens may fail. The Sentinel system is
+improving to more gracefully understand locations where it is safe to retry an
+execution.
+
+### Communication
+
+An [initial
+handshake](https://github.com/hashicorp/go-plugin/blob/master/docs/internals.md)
+is done via stdout to determine the main communication location. Following the
+handshake, communication will either occur on a local Unix domain socket or via
+a local-only TCP socket. This communication is done mostly over the low-level
+[`Get`](https://github.com/hashicorp/sentinel-sdk/blob/2b4db07dd12b55142f0c016f301af80aa4422520/proto/import.proto#L12)
+RPC call.
+
+#### Value Conversion
+
+As can generally be seen in [Developing an Import
+Plugin](/sentinel/extending/plugins#developing-an-import-plugin), the Sentinel
+type system is not exposed to binary imports. While explicit values for null and
+undefined exist, values are mostly converted over the wire from their Go values
+to their Sentinel equivalents.
+
+This has a few implications:
+
+- As discussed in [Map and List Cloning for Module
+ Calls](#map-and-list-cloning-for-module-calls), Map and list data returned from
+ binary import value lookups or function calls is always cloned. These can be
+ freely modified without affecting anything within the binary import.
+- It's currently impossible to represent certain Sentinel types such as
+ [rules](/sentinel/language/rules) within a binary import. This is not a major
+ issue at this point in time as practitioners generally do not look to binary
+ imports for rules; we may examine this as a later time if this situation
+ changes.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/extending/modules.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/extending/modules.mdx
new file mode 100644
index 0000000000..9846d263e5
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/extending/modules.mdx
@@ -0,0 +1,135 @@
+---
+page_title: >-
+ Extending Sentinel: Modules
+sidebar_current: docs-extending-modules
+description: >-
+ Modules allow you to re-use Sentinel code as an import.
+layout: docs
+---
+
+# Extending Sentinel: Modules
+
+Modules allow you to re-use Sentinel code as an import. This allows for the
+export of any general construct that Sentinel supports, including values,
+functions, and even rules. As such, it's a great way to package code that is
+useful across multiple policies, reducing boilerplate and making final policy
+code simpler.
+
+You may want to use modules for:
+
+- **Writing a simple re-usable helper library.** If you mostly know Sentinel or
+ have helpers that you have written in Sentinel, moving these helpers to a module
+ will offer a low-friction way of facilitating their re-use.
+- **Writing re-usable rules.** If you have a set of
+ [rules](/sentinel/language/rules) you know you want to use across multiple
+ policies, bundling them into a module is a great way of abstracting the logic
+ away.
+- **Abstracting existing Sentinel imports.** Using a module is a great way to
+ extend existing Sentinel imports by abstracting the common functionality that
+ you use within an import to your own workflow.
+
+This page describes how you can use modules within the context of the Sentinel
+CLI. For instructions for a particular integration, see the documentation for
+that product.
+
+-> **Not all Sentinel-enabled applications support modules.** Please refer
+to the documentation of the specific Sentinel-enabled application. Some
+applications disable modules for security reasons.
+
+## Configuring a Module
+
+A module is configured within the Sentinel CLI by adding an entry for it in
+within the `imports` section of the [CLI configuration
+file](/sentinel/configuration). The key for each entry specifies the path that
+the module is imported as.
+
+```hcl
+import "module" "foo" {
+ source = "modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "modules/bar.sentinel"
+}
+```
+
+In this example, we have two modules:
+
+- Import path `foo`, being loaded from the code within `modules/foo.sentinel`.
+- Import path `bar`, being loaded from the code within `modules/bar.sentinel`.
+
+See the [Import Modules](/sentinel/configuration#import-modules) section within
+the CLI configuration file syntax page for more details.
+
+### Remote Modules
+
+In addition to loading from a local source, modules can be loaded from a remote
+location. This can increase reusability and allow for sharing modules across
+multiple configurations.
+
+### Testing Using Modules
+
+As with [mocks](/sentinel/configuration#mocking-with-sentinel-code), modules
+are loaded relative to the configuration file location when using `sentinel test`. As such, you need to account for this in your test configuration files:
+
+```hcl
+import "module" "foo" {
+ source = "../../modules/foo.sentinel"
+}
+
+import "module" "bar" {
+ source = "../../modules/bar.sentinel"
+}
+```
+
+This could be a test file for a policy (example: `policy.sentinel`), residing
+under the correct test file directory for this policy (example:
+`test/policy/pass.json`).
+
+## Writing and Using a Module
+
+Writing a module is nearly the same as writing a Sentinel policy, except for the
+following two exceptions:
+
+- Modules do not require [`main`](/sentinel/writing/basic#the-simplest-policy)
+ to be present. It can be, but it will not carry any extra significance as it
+ does within a policy.
+- [`param`](/sentinel/language/parameters) declarations cannot be present in a module. If they are encountered,
+ a runtime error is given.
+
+Once you have a complete module ready for use and configured, using the module
+is as simple as importing it.
+
+Building on the above [configuration example](#configuring-a-module), we can
+export a simple test function in the module `foo` by adding this code to
+`modules/foo.sentinel`:
+
+```sentinel
+// modules/foo.sentinel
+hello = func() {
+ print("hello world!")
+ return undefined
+}
+```
+
+We can then import it within our policy and use it:
+
+```sentinel
+// policy.sentinel
+import "foo"
+
+// prints "hello world" in the trace
+foo.hello()
+
+main = true
+```
+
+Note that modules are considered [imports](/sentinel/language/spec#imports) by
+the Sentinel runtime and as such conform to the specification. This means that
+you cannot directly assign values to anything behind a module scope. You can,
+however, use functions to change state.
+
+-> **NOTE:** At this time, modules do not support object receiver data in the
+way that some imports do, like [`time`](/sentinel/imports/time),
+[`decimal`](/sentinel/imports/decimal), and [`http`](/sentinel/imports/http).
+Support for this will be coming in later versions of Sentinel.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/extending/plugins.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/extending/plugins.mdx
new file mode 100644
index 0000000000..e80de5d4b1
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/extending/plugins.mdx
@@ -0,0 +1,521 @@
+---
+page_title: >-
+ Extending Sentinel: Plugins
+sidebar_current: docs-extending-plugins
+description: >-
+ Plugins allow you to write imports as standalone executables, allowing you to
+ extend Sentinel in ways not normally possible with policy code alone.
+layout: docs
+---
+
+# Extending Sentinel: Plugins
+
+-> **NOTE:** Plugins are currently only supported on the CLI and not in any
+production Sentinel integration, with the exception of existing configuration
+in [Nomad](https://www.nomadproject.io/docs/configuration/sentinel).
+
+Plugins allow you to write imports as standalone executables, allowing you to
+extend Sentinel in ways not normally possible with policy code alone.
+
+You might want to write or use a plugin when you need to:
+
+- **Use a provider or API integration for Sentinel.** In this case, you will
+ probably be working with a provider SDK, or other libraries that will be making
+ complex network requests to external services. Sentinel only provides limited
+ capabilities for these kinds of operations in the standard library, so writing
+ this kind of import as a module is not optimal.
+- **Introduce novel functionality not currently supported by Sentinel.**
+ Sentinel's policy language is limited by design to encourage simple policies,
+ reduce its runtime footprint, and to keep it efficient and safe. This will
+ mean that some functionality may be difficult or impossible to express as
+ Sentinel code, and would require a plugin to write.
+- **Extend a complex module.** Additionally, modules are restricted to a single
+ file of Sentinel code, so these will naturally become more and more unwieldy as
+ you continue to add to them. Eventually, there might be a time where a plugin is
+ better suited for the functionality, especially if it's a general-purpose
+ library.
+
+This section details how import plugins are currently configured in the Sentinel
+CLI, and some detail on how they are written. As we continue to build on the
+ability to use import plugins, we will extend this section with more details.
+
+-> **Not all Sentinel-enabled applications will support plugins.** Some
+applications will disable plugins for security reasons. For those that do enable
+plugins, the method to configure plugins will vary.
+
+## Installing Plugins
+
+Sentinel plugins are standalone executables. Sentinel-enabled applications such
+as the CLI launch these plugins and communicate with them via
+[RPC](https://en.wikipedia.org/wiki/Remote_procedure_call). To install a plugin,
+the first step is to download the plugin executable.
+
+After downloading the plugin, you must add it in the CLI configuration file,
+setting its name, path, and any arguments, environment variables, or
+configuration. An example is below:
+
+```hcl
+import "plugin" "custom_time" {
+ source = "/path/to/sentinel-import-time"
+ config = { "fixed_time": 1504155600 }
+}
+```
+
+-> **NOTE:** We are using "custom_time" as standard import paths cannot be
+overriden. See [Configuration File Syntax](/sentinel/configuration#imports) for
+more details.
+
+After configuring the plugin, it will be available on the next `sentinel apply`
+or `sentinel test`.
+
+For more details on configuration, see the
+[plugins](/sentinel/configuration#plugins) section of the [CLI configuration
+file syntax](/sentinel/configuration).
+
+## Debugging Plugins
+
+The Sentinel CLI logs when it launches, configures, and closes a plugin. The
+plugin may also log additional information when it is running.
+
+To debug issues with a plugin, you can usually refer to these logs. Depending on
+the plugin configuration, the logs you see may vary - refer to the plugin
+documentation on how to enable logs for a particular plugin.
+
+## Developing an Import Plugin
+
+Anyone can develop a Sentinel import plugin using the [Sentinel
+SDK](https://github.com/hashicorp/sentinel-sdk). The SDK contains a high-level
+framework for writing plugins in [Go](https://golang.org) including a test
+framework. Additionally, the SDK contains the low-level [gRPC protocol buffers
+definition](https://github.com/hashicorp/sentinel-sdk/blob/master/proto/plugin.proto)
+for writing plugins in other languages.
+
+The rest of this page will document how to write a new Sentinel plugin in Go
+using the Sentinel SDK and the high-level framework provided. You are expected
+to already know the basics of Go and have a properly configured Go installation.
+
+Throughout this page, we'll be developing a simple plugin to access time values.
+
+-> **NOTE:** The example here is not reflective of the actual implementation of
+the [`time`](/sentinel/imports/time) standard import, so make sure to not
+get the two confused.
+
+### Plugin Framework
+
+The Sentinel SDK provides a high-level
+[framework](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework)
+for writing plugins. We recommend using this framework.
+
+#### Basic Concepts
+
+This framework works by implementing the correct Go interfaces.
+
+As a general overview:
+[`framework.Plugin`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Plugin)
+implements the
+[`sdk.Plugin`](https://godoc.org/github.com/hashicorp/sentinel-sdk#Plugin)
+interface and therefore is a valid import plugin. You must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface to configure the plugin. The root may then delegate nested access to
+various
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+implementations. Other interfaces are implemented to provide functionality past
+what is provided by the basic `framework.Namespace` implementation - these are
+documented below.
+
+This all may sound like a lot of interfaces, but each interface typically only
+requires a single function implementation and you're only required to implement
+a single namespace (`framework.Namespace`).
+
+As we move on, we'll use real examples to make this clear.
+
+#### Implementing framework.Root
+
+To begin, you must implement the
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+interface. This is the interface representing the root of your plugin. The root
+itself must be implement
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace),
+which allows values to be accessed. Note that the root can optionally implement
+other interfaces, but they're more advanced and omitted here for simplicity.
+
+For our custom time example, our root may look like this:
+
+```sentinel
+type root struct {
+ time time.Time
+}
+
+// framework.Root impl.
+func (m *root) Configure(raw map[string]interface{}) error {
+ if _, ok := raw["timestamp"]; !ok {
+ raw["timestamp"] = time.Now().Unix()
+ }
+
+ v := raw["timestamp"]
+ timestamp, ok := v.(int)
+ if !ok {
+ return fmt.Errorf("invalid timestamp type %T", v)
+ }
+
+ m.time = time.Unix(timestamp, 0).UTC()
+ return nil
+}
+
+// framework.Namespace impl.
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "minute":
+ return m.time.Minute(), nil
+ }
+
+ return nil, nil
+}
+```
+
+This example implements `framework.Root` and `framework.Namespace` and would
+enable the following within a Sentinel policy once the completed plugin is
+installed.
+
+```sentinel
+import "custom_time"
+
+print(custom_time.minute)
+main = true
+```
+
+#### framework.Namespace
+
+The
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace)
+interface implements a namespace of values. You saw above that
+[`framework.Root`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Root)
+must itself be a namespace. However, a namespace `Get` implementation may
+further return namespaces to access nested values.
+
+This enables behavior such as `custom_time.month.string` vs. `custom_time.month.index`.
+Notice each of these examples accesses a nested value within `month`. This can
+be modeled as namespaces within the framework.
+
+In the example below, we return a new namespace for `month` to do just this:
+
+```sentinel
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "month":
+ return &namespaceMonth{Month: m.time.Month()}, nil
+ }
+
+ // ...
+}
+
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil nil
+}
+```
+
+#### Primitive Types and Structs
+
+A namespace may also return any primitive Go type as well as structs. The
+framework automatically exposes primitive values as you would expect. For
+structs, exported fields are lowercased and exposed to the policies. The
+`sentinel` struct tag can be used to control how Sentinel can access the field.
+
+In the example below, we expose a struct directly from the root namespace:
+
+```sentinel
+type Location struct {
+ Name string
+ TimeZone string `sentinel:"time_zone"`
+}
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "location":
+ return &Location{Name: "somewhere", TimeZone: "some zone"}, nil
+ }
+
+ // ...
+}
+```
+
+This makes the following values work within a Sentinel policy:
+`time.location.name`, `time.location.time_zone`.
+
+If a field has an empty `sentinel` struct tag (example: `sentinel:""`),
+then that field will not be accessible from a policy.
+
+#### Optional Interfaces
+
+The following are optional interfaces that can be implemented on top of
+[`framework.Namespace`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Namespace).
+All of these interfaces can be implemented at any level, except for
+[`framework.New`](#framework-new), which only works at the root level.
+
+##### `framework.Call`
+
+The
+[`framework.Call`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Call)
+interface can be implemented to support function calls.
+
+Implementing this interface is very similar to attribute access, except instead
+of returning the attribute value, you return a function. The framework uses Go
+reflection to determine the argument and result types and calls it. If the
+argument types do not match or the signature is otherwise invalid, an error is
+returned.
+
+For example, let's implement a function to add months to the current month:
+
+```sentinel
+func (m *root) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *root) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.time.AddDate(0, n, 0).Month()}
+}
+```
+
+You can now call the function. If today was in the month of September,
+the policy below would pass:
+
+```sentinel
+import "custom_time"
+
+main = custom_time.add_month(4).string == "January"
+```
+
+##### `framework.Map`
+
+The optional
+[`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+interface allows an alternative method to to present a namespace back to a
+Sentinel policy as a map.
+
+`framework.Map` is best paired with
+[`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys),
+which allows you to quickly fetch the necessary data via `Get` calls on the
+namespace:
+
+```
+type namespaceMonth struct { Month time.Month }
+
+func (m *namespaceMonth) Get(key string) (interface{}, error) {
+ switch key {
+ case "string":
+ return m.Month.String(), nil
+
+ case "index":
+ return int(m.Month), nil
+ }
+
+ return nil, nil
+}
+
+func (m *namespaceMonth) Map() (map[string]interface{}, error) {
+ return framework.MapFromKeys(m, []string{"string", "index"})
+}
+```
+
+##### `framework.New`
+
+The optional
+[`framework.New`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#New)
+interface can be implemented on a root namespace to add _methods_ to your
+namespaces.
+
+Data returned from a namespace is memoized and returned as a map, and is
+normally not callable - to call a function to operate on the data, you would
+need to create a new namespace from the top-level of the plugin, and then make a
+function call on the result within the same expression.
+
+Let's consider the [root example](#implementing-framework-root). Let's say,
+instead of hard-coding the time value at configuration, we wanted to load it
+on-demand using a `time.now` key, and allow `add_month` to be callable on that
+value. Our root and de-coupled time namespace would now look like:
+
+```
+type root struct{}
+
+func (m *root) Configure(raw map[string]interface{}) error { return nil }
+
+func (m *root) Get(key string) (interface{}, error) {
+ switch key {
+ case "now":
+ return &namespaceTime{Time: time.Now()}
+ }
+
+ return nil
+}
+
+func (m *root) New(data map[string]interface{}) (framework.Namespace, error) {
+ if v, ok := data["unix"]; ok {
+ if t, ok := v.(int64); !ok {
+ return &namespaceTime{Time: time.Unix(t, 0)}
+ }
+
+ return nil, fmt.Errorf("expected timestamp to be int64, got %T", v)
+ }
+
+ return nil, nil
+}
+
+type namespaceTime struct {
+ Time time.Time
+}
+
+func (m *namespaceTime) Get(key string) interface{} {
+ switch key {
+ case "unix":
+ return m.Time.Unix()
+
+ // case ...
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) Get(key string) (interface{}, error) {
+ return framework.MapFromKeys(m, []string{"unix", "..."})
+}
+
+func (m *namespaceTime) Func(key string) interface{} {
+ switch key {
+ case "add_month":
+ return m.addMonth
+ }
+
+ return nil
+}
+
+func (m *namespaceTime) addMonth(n int) *namespaceMonth {
+ return &namespaceMonth{Month: m.Time.AddDate(0, n, 0).Month()}
+}
+```
+
+Via this example, we can now create and assign a `namespaceTime` using `t = custom_time.now`, and then call `t.add_month(months_to_add)` to return a
+`namespaceMonth` for the corresponding month.
+
+Methods on a namespace can also _modfiy_ the receiver data. So you can, for
+example, add a method named `increment_month` that takes no arguments, but
+increments the time stored in `namespaceTime.Time` by a month. Subsequent
+statements using the receiver would see the new value.
+
+Note that there are some restrictions and guidelines that you should take into
+account when using `framework.New`:
+
+- Only data that makes it back to a policy can be used as receiver data to
+ instantiate new namespaces. As such it's recommended to make use of
+ [`framework.Map`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#Map)
+ and
+ [`framework.MapFromKeys`](https://godoc.org/github.com/hashicorp/sentinel-sdk/framework#MapFromKeys)
+ to return all of the attribute data you need to construct new objects.
+- `framework.New` is only supported on the root namespace and has no effect on
+ namespaces below the root. Check all possible cases of receiver data within
+ the top-level `New` function and return the appropriate namespace based on the
+ input data.
+- Do not return general errors from your `New` method. When an unknown lookup is
+ made off of memoized data, it will hit your `New` method for possible
+ instantiation and key calls. This will ensure `undefined` is correctly
+ returned for these cases.
+- `framework.New` is designed to add utility to plugins where calling methods on
+ a value is more intuitive than just making a function call, or just returning
+ all of a data set as a memoized map. Use it sparingly and avoid using it on
+ recursively complex data sets.
+
+### Testing
+
+The SDK exposes a testing framework to verify your plugin works as
+expected. This test framework integrates directly into `go test` so it
+is part of a familiar workflow.
+
+The test framework works by dynamically building your plugin and
+running it against the `sentinel` CLI to verify it behaves as expected.
+This ensures that your plugin builds, your plugin communicates via RPC
+correctly, and that the behavior within a policy is also correct.
+
+The example below shows a complete ready table-driven test for our time plugin.
+You can run this with a normal `go test`.
+
+```sentinel
+package main
+
+import (
+ "os"
+ "testing"
+
+ "github.com/hashicorp/sentinel-sdk"
+ plugintesting "github.com/hashicorp/sentinel-sdk/testing"
+)
+
+func TestMain(m *testing.M) {
+ exitCode := m.Run()
+ plugintesting.Clean()
+ os.Exit(exitCode)
+}
+
+func TestPlugin(t *testing.T) {
+ cases := []struct {
+ Name string
+ Data map[string]interface{}
+ Source string
+ }{
+ {
+ "month",
+ map[string]interface{}{"timestamp": 1495483674},
+ `main = subject.month.string == "September"`,
+ },
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.Name, func(t *testing.T) {
+ plugintesting.TestPlugin(t, plugintesting.TestPluginCase{
+ Config: tc.Data,
+ Source: tc.Source,
+ })
+ })
+ }
+}
+```
+
+### Building Your Plugin
+
+To build your plugin, you must first implement the `main` function.
+The main function should just use the `rpc.Serve` method to serve the
+plugin over the Sentinel RPC layer:
+
+```sentinel
+package main
+
+import (
+ "github.com/hashicorp/sentinel-sdk"
+ "github.com/hashicorp/sentinel-sdk/rpc"
+)
+
+func main() {
+ rpc.Serve(&rpc.ServeOpts{
+ PluginFunc: func() sdk.Plugin {
+ return &framework.Plugin{Root: &root{}}
+ },
+ })
+}
+```
+
+You can then build this using `go build`. The output can be named
+anything. Once your plugin is built, [install it](#installing-plugins)
+and try it out!
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/extending/static-imports.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/extending/static-imports.mdx
new file mode 100644
index 0000000000..e908f65ab1
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/extending/static-imports.mdx
@@ -0,0 +1,91 @@
+---
+page_title: >-
+ Extending Sentinel: Static Imports
+sidebar_current: docs-extending-static-imports
+description: >-
+ Static imports allow you to write imports that use static data files.
+layout: docs
+---
+
+# Extending Sentinel: Static Imports
+
+Static imports allow you to write imports that use static data files. This can
+provide methods of consuming data in ways that are not available via
+[modules](./modules) or [plugins](./plugins).
+
+### Configuring a Static Import
+
+The [configuration page](/sentinel/configuration#static-imports) details how to
+configure static imports.
+
+### Usage
+
+Using a static import is not that different to using a plugin or module. The
+import must first be introduced to the policy via an import statement.
+
+```sentinel
+import "people"
+```
+
+From here, the static data can be used throughout the policy as you would any
+normal Sentinel value.
+
+```sentinel
+admins = filter people as person {
+ person.role is "Admin"
+}
+```
+
+One differentiator for static imports is that you can directly reference the
+import without the use of selectors. For example, to print the value;
+
+```sentinel
+print(people)
+```
+
+This is due to the static data being directly loaded and converted into a
+Sentinel value, ready for use. Static imports, like modules and plugins, cannot
+be assigned a new value.
+
+#### Namespaces
+
+One advantage of static imports over other import types, is the ability to
+access nested data directly via a namespaced import. For instance, let us
+consider a static import with the following nested data structure:
+
+```json
+{
+ "dogs": [
+ "Labrador",
+ "Golden Retriever"
+ ],
+ "cats": [
+ "Birman",
+ "Korat"
+ ]
+}
+```
+
+The list of dogs would normally be accessed through a selector expression, such
+as:
+
+```sentinel playground
+import "animals"
+
+main = rule {
+ length(animals.dogs) is 2
+}
+```
+
+However, the data can also be accessed directly at import time:
+
+```sentinel playground
+import "animals/dogs" as dogs
+
+main = rule {
+ length(dogs) is 2
+}
+```
+
+Although the above example is simple in nature, any nested map structure can
+be accessed this way. Note, lists cannot be indexed from the import path.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/features/index.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/features/index.mdx
new file mode 100644
index 0000000000..e035be978c
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/features/index.mdx
@@ -0,0 +1,28 @@
+---
+page_title: Features
+sidebar_current: docs-features
+description: Learn how features can enhance the Sentinel runtime by enabling further capabilities.
+layout: docs
+---
+
+# Features
+
+Features are a set of capabilities that enhance the runtime experience. They
+are enabled through the [`sentinel`](/sentinel/configuration#sentinel) block of the
+configuration, using the features attribute. An example of how to enable features
+is as follows:
+
+```hcl
+sentinel {
+ features = {
+ apply-all = true
+ terraform = true
+ }
+}
+```
+
+The current set of features are:
+
+- [terraform](/sentinel/features/terraform) - Allowing the Sentinel runtime
+ to interact with Terraform data.
+- apply-all - Force Sentinel to evaluate all policies within a policy set without prematurely terminating on failures.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/features/terraform/index.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/features/terraform/index.mdx
new file mode 100644
index 0000000000..a810dbe478
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/features/terraform/index.mdx
@@ -0,0 +1,24 @@
+---
+page_title: terraform
+sidebar_current: docs-features-terraform
+description: Learn about the terraform feature and its capabilities.
+layout: docs
+---
+
+# terraform feature
+
+The `terraform` feature enhances the Sentinel runtime to work with Terraform
+data. It will add the following imports to the standard library:
+
+- [tfplan/v1](/sentinel/features/terraform/tfplan-v1)
+- [tfplan/v2](/sentinel/features/terraform/tfplan-v2)
+- [tfconfig/v1](/sentinel/features/terraform/tfconfig-v1)
+- [tfconfig/v2](/sentinel/features/terraform/tfconfig-v2)
+- [tfstate/v1](/sentinel/features/terraform/tfstate-v1)
+- [tfstate/v2](/sentinel/features/terraform/tfstate-v2)
+
+-> **NOTE:** The above imports will only work with Terraform 0.12 and above,
+as they rely on the output from the `terraform show -json` command.
+
+It is recommended that the `/v2` suffixed imports are used, as they provide
+the best experience when interacting with the underlying data structures.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/features/terraform/tfconfig-v1.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/features/terraform/tfconfig-v1.mdx
new file mode 100644
index 0000000000..eb4092eb74
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/features/terraform/tfconfig-v1.mdx
@@ -0,0 +1,949 @@
+---
+page_title: tfconfig/v1 - terraform - Features
+sidebar_current: docs-features-terraform-config-v1
+description: The tfconfig/v1 import provides access to a Terraform configuration.
+layout: docs
+---
+
+# Import: tfconfig/v1
+
+~> **Warning:** The `tfconfig/v1` import is deprecated and will be permanently removed in August 2025.
+Use the [tfconfig/v2](/sentinel/docs/features/terraform/tfconfig-v2) import as soon as possible to avoid disruptions.
+The `tfconfig/v2` import offers improved functionality and is designed to better support your policy enforcement needs.
+
+The `tfconfig/v1` import provides access to a Terraform configuration.
+
+The Terraform configuration is the set of `*.tf` files that are used to
+describe the desired infrastructure state. Policies using the `tfconfig/v1`
+import can access all aspects of the configuration: providers, resources,
+data sources, modules, and variables.
+
+Some use cases for `tfconfig/v1` include:
+
+* **Organizational naming conventions**: requiring that configuration elements
+ are named in a way that conforms to some organization-wide standard.
+* **Required inputs and outputs**: organizations may require a particular set
+ of input variable names across all workspaces or may require a particular
+ set of outputs for asset management purposes.
+* **Enforcing particular modules**: organizations may provide a number of
+ "building block" modules and require that each workspace be built only from
+ combinations of these modules.
+* **Enforcing particular providers or resources**: an organization may wish to
+ require or prevent the use of providers and/or resources so that configuration
+ authors cannot use alternative approaches to work around policy
+ restrictions.
+
+Note with these use cases that this import is concerned with object _names_
+in the configuration. Since this is the configuration and not an invocation
+of Terraform, you can't see values for variables, the state, or the diff for
+a pending plan. If you want to write policy around expressions used
+within configuration blocks, you likely want to use the
+[`tfplan/v1`](/sentinel/features/terraform/tfplan-v1) import.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfconfig/v1" {
+ config = {
+ "path": "./path/to/plan.json"
+ }
+}
+```
+
+## Namespace Overview
+
+The following is a tree view of the import namespace. For more detail on a
+particular part of the namespace, see below.
+
+-> **Note:** The root-level alias keys shown here (`data`, `modules`,
+`providers`, `resources`, and `variables`) are shortcuts to a [module
+namespace](#namespace-module) scoped to the root module. For more details, see
+the section on [root namespace aliases](#root-namespace-aliases).
+
+```
+tfconfig/v1
+├── module() (function)
+│ └── (module namespace)
+│ ├── data
+│ │ └── TYPE.NAME
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── provisioners
+│ │ └── NUMBER
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── type (string)
+│ ├── modules
+│ │ └── NAME
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ ├── source (string)
+│ │ └── version (string)
+│ ├──outputs
+│ │ └── NAME
+│ │ ├── depends_on (list of strings)
+│ │ ├── description (string)
+│ │ ├── sensitive (boolean)
+│ │ ├── references (list of strings)
+│ │ └── value (value)
+│ ├── providers
+│ │ └── TYPE
+│ │ ├── alias
+│ │ │ └── ALIAS
+│ │ │ ├── config (map of keys)
+│ │ | ├── references (map of keys)
+│ │ │ └── version (string)
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── version (string)
+│ ├── resources
+│ │ └── TYPE.NAME
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── provisioners
+│ │ └── NUMBER
+│ │ ├── config (map of keys)
+│ │ ├── references (map of keys)
+│ │ └── type (string)
+│ └── variables
+│ └── NAME
+│ ├── default (value)
+│ └── description (string)
+├── module_paths ([][]string)
+│
+├── data (root module alias)
+├── modules (root module alias)
+├── outputs (root module alias)
+├── providers (root module alias)
+├── resources (root module alias)
+└── variables (root module alias)
+```
+
+### `references` Overview
+
+As an example, consider the following resource block:
+
+```hcl
+resource "local_file" "accounts" {
+ content = "some text"
+ filename = "${var.subdomain}.${var.domain}/accounts.txt"
+}
+```
+
+In this example, one might want to ensure `domain` and `subdomain` input
+variables are used within `filename` in this configuration.
+
+-> Any non-static values (such as interpolated strings) are not present within the
+configuration value and `references` should be used instead:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+# filename_references is a list of string values containing the references used in the expression
+filename_references = tfconfig.resources.local_file.accounts.references.filename
+
+main = rule {
+ filename_references contains "var.domain" and
+ filename_references contains "var.subdomain"
+}
+```
+
+The `references` value is present in any namespace where non-constant
+configuration values can be expressed. This is essentially every namespace
+which has a `config` value as well as the `outputs` namespace.
+
+-> **Note:** Remember, this import enforces policy around the literal Terraform
+configuration and not the final values as a result of invoking Terraform. If
+you want to write policy around the _result_ of expressions used within
+configuration blocks (for example, if you wanted to ensure the final value of
+`filename` above includes `accounts.txt`), you likely want to use the
+[`tfplan/v1`](/sentinel/features/terraform/tfplan-v1) import.
+
+## Namespace: Root
+
+The root-level namespace consists of the values and functions documented below.
+
+In addition to this, the root-level `data`, `modules`, `providers`, `resources`,
+and `variables` keys all alias to their corresponding namespaces within the
+[module namespace](#namespace-module).
+
+
+
+### Function: `module()`
+
+```
+module = func(ADDR)
+```
+
+* **Return Type:** A [module namespace](#namespace-module).
+
+The `module()` function in the [root namespace](#namespace-root) returns the
+[module namespace](#namespace-module) for a particular module address.
+
+The address must be a list and is the module address, split on the period (`.`),
+excluding the root module.
+
+Hence, a module with an address of simply `foo` (or `root.foo`) would be
+`["foo"]`, and a module within that (so address `foo.bar`) would be read as
+`["foo", "bar"]`.
+
+[`null`][ref-null] is returned if a module address is invalid, or if the module
+is not present in the configuration.
+
+[ref-null]: /sentinel/language/spec#null
+
+As an example, given the following module block:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+If the module contained the following content:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { subject.module(["foo"]).resources.null_resource.foo.config.triggers[0].foo is "bar" }
+```
+
+
+
+### Value: `module_paths`
+
+* **Value Type:** List of a list of strings.
+
+The `module_paths` value within the [root namespace](#namespace-root) is a list
+of all of the modules within the Terraform configuration.
+
+Modules not present in the configuration will not be present here, even if they
+are present in the diff or state.
+
+This data is represented as a list of a list of strings, with the inner list
+being the module address, split on the period (`.`).
+
+The root module is included in this list, represented as an empty inner list.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The value of `module_paths` would be:
+
+```
+[
+ [],
+ ["foo"],
+]
+```
+
+And the following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.module_paths contains ["foo"] }
+```
+
+#### Iterating Through Modules
+
+Iterating through all modules to find particular resources can be useful. This
+[example][iterate-over-modules] shows how to use `module_paths` with the
+[`module()` function](#function-module-) to find all resources of a
+particular type from all modules using the `tfplan` import. By changing `tfplan`
+in this function to `tfconfig`, you could make a similar function find all
+resources of a specific type in the Terraform configuration.
+
+[iterate-over-modules]: https://developer.hashicorp.com/terraform/cloud-docs/policy-enforcement/sentinel#sentinel-imports
+
+## Namespace: Module
+
+The **module namespace** can be loaded by calling [`module()`](#root-function-module)
+for a particular module.
+
+It can be used to load the following child namespaces:
+
+* `data` - Loads the [resource namespace](#namespace-resources-data-sources),
+ filtered against data sources.
+* `modules` - Loads the [module configuration
+ namespace](#namespace-module-configuration).
+* `outputs` - Loads the [output namespace](#namespace-outputs).
+* `providers` - Loads the [provider namespace](#namespace-providers).
+* `resources` - Loads the [resource
+ namespace](#namespace-resources-data-sources), filtered against resources.
+* `variables` - Loads the [variable namespace](#namespace-variables).
+
+### Root Namespace Aliases
+
+The root-level `data`, `modules`, `providers`, `resources`, and `variables` keys
+all alias to their corresponding namespaces within the module namespace, loaded
+for the root module. They are the equivalent of running `module([]).KEY`.
+
+
+
+## Namespace: Resources/Data Sources
+
+The **resource namespace** is a namespace _type_ that applies to both resources
+(accessed by using the `resources` namespace key) and data sources (accessed
+using the `data` namespace key).
+
+Accessing an individual resource or data source within each respective namespace
+can be accomplished by specifying the type and name, in the syntax
+`[resources|data].TYPE.NAME`.
+
+In addition, each of these namespace levels is a map, allowing you to filter
+based on type and name. Some examples of multi-level access are below:
+
+* To fetch all `aws_instance` resources within the root module, you can specify
+ `tfconfig.resources.aws_instance`. This would give you a map of resource
+ namespaces indexed from the names of each resource (`foo`, `bar`, and so
+ on).
+* To fetch all resources within the root module, irrespective of type, use
+ `tfconfig.resources`. This is indexed by type, as shown above with
+ `tfconfig.resources.aws_instance`, with names being the next level down.
+
+As an example, perhaps you wish to deny use of the `local_file` resource
+in your configuration. Consider the following resource block:
+
+```hcl
+resource "local_file" "foo" {
+ content = "foo!"
+ filename = "${path.module}/foo.bar"
+}
+```
+
+The following policy would fail:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.resources not contains "local_file" }
+```
+
+Further explanation of the namespace will be in the context of resources. As
+mentioned, when operating on data sources, use the same syntax, except with
+`data` in place of `resources`.
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+The `config` value within the [resource
+namespace](#namespace-resources-data-sources) is a map of key-value pairs that
+directly map to Terraform config keys and values.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#resources-value-references) should be used instead.
+
+As an example, consider the following resource block:
+
+```hcl
+resource "local_file" "accounts" {
+ content = "some text"
+ filename = "accounts.txt"
+}
+```
+
+In this example, one might want to access `filename` to validate that the correct
+file name is used. Given the above example, the following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule {
+ tfconfig.resources.local_file.accounts.config.filename is "accounts.txt"
+}
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [resource namespace](#namespace-resources-data-sources)
+contains the identifiers within non-constant expressions found in [`config`](#resources-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+
+
+### Value: `provisioners`
+
+* **Value Type:** List of [provisioner namespaces](#namespace-provisioners).
+
+The `provisioners` value within the [resource namespace](#namespace-resources)
+represents the [provisioners][ref-tf-provisioners] within a specific resource.
+
+Provisioners are listed in the order they were provided in the configuration
+file.
+
+While the `provisioners` value will be present within data sources, it will
+always be an empty map `null` (in Terraform 0.12) since data sources cannot
+actually have provisioners.
+
+The data within a provisioner can be inspected via the returned [provisioner
+namespace](#namespace-provisioners).
+
+[ref-tf-provisioners]: https://developer.hashicorp.com/terraform/language/resources/provisioners/syntax
+
+## Namespace: Provisioners
+
+The **provisioner namespace** represents the configuration for a particular
+[provisioner][ref-tf-provisioners] within a specific resource.
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+The `config` value within the [provisioner namespace](#namespace-provisioners)
+represents the values of the keys within the provisioner.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#provisioners-value-references) should be used instead.
+
+As an example, given the following resource block:
+
+```hcl
+resource "null_resource" "foo" {
+ # ...
+
+ provisioner "local-exec" {
+ command = "echo ${self.private_ip} > file.txt"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule {
+ tfconfig.resources.null_resource.foo.provisioners[0].config.command is "echo ${self.private_ip} > file.txt"
+}
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [provisioner namespace](#namespace-provisioners)
+contains the identifiers within non-constant expressions found in [`config`](#provisioners-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+
+
+### Value: `type`
+
+* **Value Type:** String.
+
+The `type` value within the [provisioner namespace](#namespace-provisioners)
+represents the type of the specific provisioner.
+
+As an example, in the following resource block:
+
+```hcl
+resource "null_resource" "foo" {
+ # ...
+
+ provisioner "local-exec" {
+ command = "echo ${self.private_ip} > file.txt"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.resources.null_resource.foo.provisioners[0].type is "local-exec" }
+```
+
+## Namespace: Module Configuration
+
+The **module configuration** namespace displays data on _module configuration_
+as it is given within a `module` block. This means that the namespace concerns
+itself with the contents of the declaration block (example: the `source`
+parameter and variable assignment keys), not the data within the module
+(example: any contained resources or data sources). For the latter, the module
+instance would need to be looked up with the [`module()`
+function](#root-function-module).
+
+
+
+### Value: `source`
+
+* **Value Type:** String.
+
+The `source` value within the [module configuration
+namespace](#namespace-module-configuration) represents the module source path as
+supplied to the module configuration.
+
+As an example, given the module declaration block:
+
+```hcl
+module "foo" {
+ source = "./foo"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.modules.foo.source is "./foo" }
+```
+
+
+
+### Value: `version`
+
+* **Value Type:** String.
+
+The `version` value within the [module configuration
+namespace](#namespace-module-configuration) represents the [version
+constraint][module-version-constraint] for modules that support it, such as
+modules within the [Terraform Module Registry][terraform-module-registry] or the
+[HCP Terraform private module registry][tfe-private-registry].
+
+[module-version-constraint]: https://developer.hashicorp.com/terraform/language/modules#module-versions
+
+[terraform-module-registry]: https://registry.terraform.io/
+
+[tfe-private-registry]: https://developer.hashicorp.com/terraform/cloud-docs/registry
+
+As an example, given the module declaration block:
+
+```hcl
+module "foo" {
+ source = "foo/bar"
+ version = "~> 1.2"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.modules.foo.version is "~> 1.2" }
+```
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#modules-value-references) should be used instead.
+
+The `config` value within the [module configuration
+namespace](#namespace-module-configuration) represents the values of the keys
+within the module configuration. This is every key within a module declaration
+block except [`source`](#modules-value-source) and [`version`](#modules-value-version), which
+have their own values.
+
+As an example, given the module declaration block:
+
+```hcl
+module "foo" {
+ source = "./foo"
+
+ bar = "baz"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.modules.foo.config.bar is "baz" }
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [module configuration namespace](#namespace-module-configuration)
+contains the identifiers within non-constant expressions found in [`config`](#modules-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+## Namespace: Outputs
+
+The **output namespace** represents _declared_ output data within a
+configuration. As such, configuration for the [`value`](#outputs-value-value) attribute
+will be in its raw form, and not yet interpolated. For fully interpolated output
+values, see the [`tfstate` import][ref-tfe-sentinel-tfstate].
+
+[ref-tfe-sentinel-tfstate]: /sentinel/features/terraform/tfstate-v1
+
+This namespace is indexed by output name.
+
+
+
+### Value: `depends_on`
+
+* **Value Type:** A list of strings.
+
+The `depends_on` value within the [output namespace](#namespace-outputs)
+represents any _explicit_ dependencies for this output. For more information,
+see the [depends_on output setting][ref-depends_on] within the general Terraform
+documentation.
+
+[ref-depends_on]: https://developer.hashicorp.com/terraform/language/values/outputs#depends_on
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ depends_on = ["null_resource.bar"]
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.depends_on[0] is "null_resource.bar" }
+```
+
+
+
+### Value: `description`
+
+* **Value Type:** String.
+
+The `description` value within the [output namespace](#namespace-outputs)
+represents the defined description for this output.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ description = "foobar"
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.description is "foobar" }
+```
+
+
+
+### Value: `sensitive`
+
+* **Value Type:** Boolean.
+
+The `sensitive` value within the [output namespace](#namespace-outputs)
+represents if this value has been marked as sensitive or not.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ sensitive = true
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { subject.outputs.id.sensitive }
+```
+
+
+
+### Value: `value`
+
+* **Value Type:** Any primitive type, list or map.
+
+The `value` value within the [output namespace](#namespace-outputs) represents
+the defined value for the output as declared in the configuration. Primitives
+will bear the implicit type of their declaration (string, int, float, or bool),
+and maps and lists will be represented as such.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#outputs-value-references) should be used instead.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ value = "foo"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.value is "foo" }
+```
+
+
+
+### Value: `references`
+
+* **Value Type:**. List of strings.
+
+The `references` value within the [output namespace](#namespace-outputs)
+contains the names of any referenced identifiers when [`value`](#outputs-value-value)
+is a non-constant expression.
+
+As an example, given the following output declaration block:
+
+```hcl
+output "id" {
+ value = "${null_resource.foo.id}"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.outputs.id.references contains "null_resource.foo.id" }
+```
+
+## Namespace: Providers
+
+The **provider namespace** represents data on the declared providers within a
+namespace.
+
+This namespace is indexed by provider type and _only_ contains data about
+providers when actually declared. If you are using a completely implicit
+provider configuration, this namespace will be empty.
+
+This namespace is populated based on the following criteria:
+
+* The top-level namespace [`config`](#providers-value-config) and
+ [`version`](#providers-value-version) values are populated with the configuration and
+ version information from the default provider (the provider declaration that
+ lacks an alias).
+* Any aliased providers are added as namespaces within the
+ [`alias`](#providers-value-alias) value.
+* If a module lacks a default provider configuration, the top-level `config` and
+ `version` values will be empty.
+
+
+
+### Value: `alias`
+
+* **Value Type:** A map of [provider namespaces](#namespace-providers), indexed
+ by alias.
+
+The `alias` value within the [provider namespace](#namespace-providers)
+represents all declared [non-default provider
+instances][ref-tf-provider-instances] for a specific provider type, indexed by
+their specific alias.
+
+[ref-tf-provider-instances]: https://developer.hashicorp.com/terraform/language/providers/configuration#alias-multiple-provider-configurations
+
+The return type is a provider namespace with the data for the instance in
+question loaded. The `alias` key will not be available within this namespace.
+
+As an example, given the following provider declaration block:
+
+```hcl
+provider "aws" {
+ alias = "east"
+ region = "us-east-1"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.providers.aws.alias.east.config.region is "us-east-1" }
+```
+
+
+
+### Value: `config`
+
+* **Value Type:** A string-keyed map of values.
+
+-> Any non-static values (such as interpolated strings) are not present and
+[`references`](#providers-value-references) should be used instead.
+
+The `config` value within the [provider namespace](#namespace-providers)
+represents the values of the keys within the provider's configuration, with the
+exception of the provider version, which is represented by the
+[`version`](#providers-value-version) value. [`alias`](#providers-value-alias) is also not included
+when the provider is aliased.
+
+As an example, given the following provider declaration block:
+
+```hcl
+provider "aws" {
+ region = "us-east-1"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.providers.aws.config.region is "us-east-1" }
+```
+
+
+
+### Value: `references`
+
+* **Value Type:** A string-keyed map of list values containing strings.
+
+The `references` value within the [provider namespace](#namespace-providers)
+contains the identifiers within non-constant expressions found in [`config`](#providers-value-config).
+See the [documentation on `references`](#references-overview) for more information.
+
+
+
+### Value: `version`
+
+* **Value Type:** String.
+
+The `version` value within the [provider namespace](#namespace-providers)
+represents the explicit expected version of the supplied provider. This includes
+the pessimistic operator.
+
+As an example, given the following provider declaration block:
+
+```hcl
+provider "aws" {
+ version = "~> 1.34"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.providers.aws.version is "~> 1.34" }
+```
+
+## Namespace: Variables
+
+The **variable namespace** represents _declared_ variable data within a
+configuration. As such, static data can be extracted, such as defaults, but not
+dynamic data, such as the current value of a variable within a plan (although
+this can be extracted within the [`tfplan` import][ref-tfe-sentinel-tfplan]).
+
+[ref-tfe-sentinel-tfplan]: /sentinel/features/terraform/tfplan-v1
+
+This namespace is indexed by variable name.
+
+
+
+### Value: `default`
+
+* **Value Type:** Any primitive type, list, map, or `null`.
+
+The `default` value within the [variable namespace](#namespace-variables)
+represents the default for the variable as declared in the configuration.
+
+The actual value will be as configured. Primitives will bear the implicit type
+of their declaration (string, int, float, or bool), and maps and lists will be
+represented as such.
+
+If no default is present, the value will be [`null`][ref-sentinel-null] (not to
+be confused with [`undefined`][ref-sentinel-undefined]).
+
+[ref-sentinel-null]: /sentinel/language/spec#null
+
+[ref-sentinel-undefined]: /sentinel/language/undefined
+
+As an example, given the following variable blocks:
+
+```hcl
+variable "foo" {
+ default = "bar"
+}
+
+variable "number" {
+ default = 42
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+default_foo = rule { tfconfig.variables.foo.default is "bar" }
+default_number = rule { tfconfig.variables.number.default is 42 }
+
+main = rule { default_foo and default_number }
+```
+
+
+
+### Value: `description`
+
+* **Value Type:** String.
+
+The `description` value within the [variable namespace](#namespace-variables)
+represents the description of the variable, as provided in configuration.
+
+As an example, given the following variable block:
+
+```hcl
+variable "foo" {
+ description = "foobar"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfconfig/v1" as tfconfig
+
+main = rule { tfconfig.variables.foo.description is "foobar" }
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/features/terraform/tfconfig-v2.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/features/terraform/tfconfig-v2.mdx
new file mode 100644
index 0000000000..ad8163bbee
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/features/terraform/tfconfig-v2.mdx
@@ -0,0 +1,441 @@
+---
+page_title: tfconfig/v2 - terraform - Features
+sidebar_current: docs-features-terraform-tfconfig-v2
+description: The tfconfig/v2 import provides access to a Terraform configuration.
+layout: docs
+---
+
+# Import: tfconfig/v2
+
+The `tfconfig/v2` import provides access to a Terraform configuration.
+
+The Terraform configuration is the set of `*.tf` files that are used to
+describe the desired infrastructure state. Policies using the `tfconfig`
+import can access all aspects of the configuration: providers, resources,
+data sources, modules, and variables.
+
+Some use cases for `tfconfig` include:
+
+* **Organizational naming conventions**: requiring that configuration elements
+ are named in a way that conforms to some organization-wide standard.
+* **Required inputs and outputs**: organizations may require a particular set
+ of input variable names across all workspaces or may require a particular
+ set of outputs for asset management purposes.
+* **Enforcing particular modules**: organizations may provide a number of
+ "building block" modules and require that each workspace be built only from
+ combinations of these modules.
+* **Enforcing particular providers or resources**: an organization may wish to
+ require or prevent the use of providers and/or resources so that configuration
+ authors cannot use alternative approaches to work around policy
+ restrictions.
+
+The data in the `tfconfig/v2` import is sourced from the JSON configuration file
+that is generated by the [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) command. For more information on
+the file format, see the [JSON Output Format](https://developer.hashicorp.com/terraform/internals/json-format)
+page.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfconfig/v2" {
+ config = {
+ "path": "./path/to/plan.json"
+ }
+}
+```
+
+## Import Overview
+
+The `tfconfig/v2` import is structured as a series of _collections_, keyed as a
+specific format, such as resource address, module address, or a
+specifically-formatted provider key.
+
+```
+tfconfig/v2
+├── strip_index() (function)
+├── providers
+│ └── (indexed by [module_address:]provider[.alias])
+│ ├── provider_config_key (string)
+│ ├── name (string)
+│ ├── full_name (string)
+│ ├── alias (string)
+│ ├── module_address (string)
+│ ├── config (block expression representation)
+│ └── version_constraint (string)
+├── resources
+│ └── (indexed by address)
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── provider_config_key (string)
+│ ├── provisioners (list)
+│ │ └── (ordered provisioners for this resource only)
+│ ├── config (block expression representation)
+│ ├── count (expression representation)
+│ ├── for_each (expression representation)
+│ └── depends_on (list of strings)
+├── provisioners
+│ └── (indexed by resource_address:index)
+│ ├── resource_address (string)
+│ ├── type (string)
+│ ├── index (string)
+│ └── config (block expression representation)
+├── variables
+│ └── (indexed by module_address:name)
+│ ├── module_address (string)
+│ ├── name (string)
+│ ├── default (value)
+│ └── description (string)
+├── outputs
+│ └── (indexed by module_address:name)
+│ ├── module_address (string)
+│ ├── name (string)
+│ ├── sensitive (boolean)
+│ ├── value (expression representation)
+│ ├── description (string)
+│ └── depends_on (list of strings)
+└── module_calls
+ └── (indexed by module_address:name)
+ ├── module_address (string)
+ ├── name (string)
+ ├── source (string)
+ ├── config (block expression representation)
+ ├── count (expression representation)
+ ├── depends_on (expression representation)
+ ├── for_each (expression representation)
+ └── version_constraint (string)
+```
+
+The collections are:
+
+* [`providers`](#the-providers-collection) - The configuration for all provider
+ instances across all modules in the configuration.
+* [`resources`](#the-resources-collection) - The configuration of all resources
+ across all modules in the configuration.
+* [`variables`](#the-variables-collection) - The configuration of all variable
+ definitions across all modules in the configuration.
+* [`outputs`](#the-outputs-collection) - The configuration of all output
+ definitions across all modules in the configuration.
+* [`module_calls`](#the-module_calls-collection) - The configuration of all module
+ calls (individual [`module`](/terraform/language/modules) blocks) across
+ all modules in the configuration.
+
+These collections are specifically designed to be used with the
+[`filter`](/sentinel/language/collection-operations#filter-expression)
+quantifier expression in Sentinel, so that one can collect a list of resources
+to perform policy checks on without having to write complex module or
+configuration traversal. As an example, the following code will return all
+`aws_instance` resource types within the configuration, regardless of what
+module they are in:
+
+```
+all_aws_instances = filter tfconfig.resources as _, r {
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+You can add specific attributes to the filter to narrow the search, such as the
+module address. The following code would return resources in a module named
+`foo` only:
+
+```
+all_aws_instances = filter tfconfig.resources as _, r {
+ r.module_address is "module.foo" and
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+### Address Differences Between `tfconfig`, `tfplan`, and `tfstate`
+
+This import deals with configuration before it is expanded into a
+resource graph by Terraform. As such, it is not possible to compute an index as
+the import is building its collections and computing addresses for resources and
+modules.
+
+As such, addresses found here may not always match the expanded addresses found
+in the [`tfplan/v2`](/sentinel/features/terraform/tfplan-v2) and [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2)
+imports, specifically when
+[`count`](https://developer.hashicorp.com/terraform/language/resources#count-multiple-resource-instances-by-count)
+and
+[`for_each`](https://developer.hashicorp.com/terraform/language/resources#for_each-multiple-resource-instances-defined-by-a-map-or-set-of-strings),
+are used.
+
+As an example, consider a resource named `null_resource.foo` with a count of `2`
+located in a module named `bar`. While there will possibly be entries in the
+other imports for `module.bar.null_resource.foo[0]` and
+`module.bar.null_resource.foo[1]`, in `tfconfig/v2`, there will only be a
+`module.bar.null_resource.foo`. As mentioned in the start of this section, this
+is because configuration actually _defines_ this scaling, whereas _expansion_
+actually happens when the resource graph is built, which happens as a natural
+part of the refresh and planning process.
+
+The `strip_index` helper function, found in this import, can assist in
+removing the indexes from addresses found in the `tfplan/v2` and `tfstate/v2`
+imports so that data from those imports can be used to reference data in this
+one.
+
+## The `strip_index` Function
+
+The `strip_index` helper function can be used to remove indexes from addresses
+found in [`tfplan/v2`](/sentinel/features/terraform/tfplan-v2) and [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2),
+by removing the indexes from each resource.
+
+This can be used to help facilitate cross-import lookups for data between plan,
+state, and config.
+
+```
+import "tfconfig/v2" as tfconfig
+import "tfplan/v2" as tfplan
+
+main = rule {
+ all filter tfplan.resource_changes as _, rc {
+ rc.mode is "managed" and
+ rc.type is "aws_instance"
+ } as _, rc {
+ tfconfig.resources[tfconfig.strip_index(rc.address)].config.ami.constant_value is "ami-abcdefgh012345"
+ }
+}
+```
+
+## Expression Representations
+
+Most collections in this import will have one of two kinds of _expression
+representations_. This is a verbose format for expressing a (parsed)
+configuration value independent of the configuration source code, which is not
+100% available to a policy check in HCP Terraform.
+
+```
+(expression representation)
+├── constant_value (value)
+└── references (list of strings)
+```
+
+There are two major parts to an expression representation:
+
+* Any _strictly constant value_ is expressed as an expression with a
+ `constant_value` field.
+* Any expression that requires some degree of evaluation to generate the final
+ value - even if that value is known at plan time - is not expressed in
+ configuration. Instead, any particular references that are made are added to
+ the `references` field. More details on this field can be found in the
+ [expression
+ representation](https://developer.hashicorp.com/terraform/internals/json-format#expression-representation)
+ section of the JSON output format documentation.
+
+For example, to determine if an output is based on a particular
+resource value, one could do:
+
+```
+import "tfconfig/v2" as tfconfig
+
+main = rule {
+ tfconfig.outputs["instance_id"].value.references is ["aws_instance.foo"]
+}
+```
+
+-> **Note:** The representation does not account for
+complex interpolations or other expressions that combine constants with other
+expression data. For example, the partially constant data in `"foo${var.bar}"` would be lost.
+
+### Block Expression Representation
+
+Expanding on the above, a multi-value expression representation (such as the
+kind found in a [`resources`](#the-resources-collection) collection element) is
+similar, but the root value is a keyed map of expression representations. This
+is repeated until a "scalar" expression value is encountered, ie: a field that
+is not a block in the resource's schema.
+
+```
+(block expression representation)
+└── (attribute key)
+ ├── (child block expression representation)
+ │ └── (...)
+ ├── constant_value (value)
+ └── references (list of strings)
+```
+
+As an example, one can validate expressions in an
+[`aws_instance`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance) resource using the
+following:
+
+```
+import "tfconfig/v2" as tfconfig
+
+main = rule {
+ tfconfig.resources["aws_instance.foo"].config.ami.constant_value is "ami-abcdefgh012345"
+}
+```
+
+Note that _nested blocks_, sometimes known as _sub-resources_, will be nested in
+configuration as as list of blocks (reflecting their ultimate nature as a list
+of objects). An example would be the `aws_instance` resource's
+[`ebs_block_device`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#ebs-ephemeral-and-root-block-devices) block:
+
+```
+import "tfconfig/v2" as tfconfig
+
+main = rule {
+ tfconfig.resources["aws_instance.foo"].config.ebs_block_device[0].volume_size < 10
+}
+```
+
+## The `providers` Collection
+
+The `providers` collection is a collection representing the configurations of
+all provider instances across all modules in the configuration.
+
+This collection is indexed by an opaque key. This is currently
+`module_address:provider.alias`, the same value as found in the
+`provider_config_key` field. `module_address` and the colon delimiter are
+omitted for the root module.
+
+The `provider_config_key` field is also found in the `resources` collection and
+can be used to locate a provider that belongs to a configured resource.
+
+The fields in this collection are as follows:
+
+* `provider_config_key` - The opaque configuration key, used as the index key.
+* `name` - The name of the provider, ie: `aws`.
+* `full_name` - The fully-qualified name of the provider, e.g. `registry.terraform.io/hashicorp/aws`.
+* `alias` - The alias of the provider, ie: `east`. Empty for a default provider.
+* `module_address` - The address of the module this provider appears in.
+* `config` - A [block expression
+ representation](#block-expression-representation) with provider configuration
+ values.
+* `version_constraint` - The defined version constraint for this provider.
+
+## The `resources` Collection
+
+The `resources` collection is a collection representing all of the resources
+found in all modules in the configuration.
+
+This collection is indexed by the resource address.
+
+The fields in this collection are as follows:
+
+* `address` - The resource address. This is the index of the collection.
+* `module_address` - The module address that this resource was found in.
+* `mode` - The resource mode, either `managed` (resources) or `data` (data
+ sources).
+* `type` - The type of resource, ie: `null_resource` in `null_resource.foo`.
+* `name` - The name of the resource, ie: `foo` in `null_resource.foo`.
+* `provider_config_key` - The opaque configuration key that serves as the index
+ of the [`providers`](#the-providers-collection) collection.
+* `provisioners` - The ordered list of provisioners for this resource. The
+ syntax of the provisioners matches those found in the
+ [`provisioners`](#the-provisioners-collection) collection, but is a list
+ indexed by the order the provisioners show up in the resource.
+* `config` - The [block expression
+ representation](#block-expression-representation) of the configuration values
+ found in the resource.
+* `count` - The [expression data](#expression-representations) for the `count`
+ value in the resource.
+* `for_each` - The [expression data](#expression-representations) for the
+ `for_each` value in the resource.
+* `depends_on` - The contents of the `depends_on` config directive, which
+ declares explicit dependencies for this resource.
+
+## The `provisioners` Collection
+
+The `provisioners` collection is a collection of all of the provisioners found
+across all resources in the configuration.
+
+While normally bound to a resource in an ordered fashion, this collection allows
+for the filtering of provisioners within a single expression.
+
+This collection is indexed with a key following the format
+`resource_address:index`, with each field matching their respective field in the
+particular element below:
+
+* `resource_address`: The address of the resource that the provisioner was found
+ in. This can be found in the [`resources`](#the-resources-collection)
+ collection.
+* `type`: The provisioner type, ie: `local_exec`.
+* `index`: The provisioner index as it shows up in the resource provisioner
+ order.
+* `config`: The [block expression
+ representation](#block-expression-representation) of the configuration values
+ in the provisioner.
+
+## The `variables` Collection
+
+The `variables` collection is a collection of all variables across all modules
+in the configuration.
+
+Note that this tracks variable definitions, not values. See the [`tfplan/v2`
+`variables` collection](/sentinel/features/terraform/tfplan-v2#the-variables-collection) for variable
+values set within a plan.
+
+This collection is indexed by the key format `module_address:name`, with each
+field matching their respective name below. `module_address` and the colon
+delimiter are omitted for the root module.
+
+* `module_address` - The address of the module the variable was found in.
+* `name` - The name of the variable.
+* `default` - The defined default value of the variable.
+* `description` - The description of the variable.
+
+## The `outputs` Collection
+
+The `outputs` collection is a collection of all outputs across all modules in
+the configuration.
+
+Note that this tracks variable definitions, not values. See the [`tfstate/v2`
+`outputs` collection](/sentinel/features/terraform/tfstate-v2#the-outputs-collection) for the final
+values of outputs set within a state. The [`tfplan/v2` `output_changes`
+collection](/sentinel/features/terraform/tfplan-v2#the-output_changes-collection) also contains a more
+complex collection of planned output changes.
+
+This collection is indexed by the key format `module_address:name`, with each
+field matching their respective name below. `module_address` and the colon
+delimiter are omitted for the root module.
+
+* `module_address` - The address of the module the output was found in.
+* `name` - The name of the output.
+* `sensitive` - Indicates whether or not the output was marked as
+ [`sensitive`](https://developer.hashicorp.com/terraform/language/values/outputs#sensitive-suppressing-values-in-cli-output).
+* `value` - An [expression representation](#expression-representations) for the output.
+* `description` - The description of the output.
+* `depends_on` - A list of resource names that the output depends on. These are
+ the hard-defined output dependencies as defined in the
+ [`depends_on`](https://developer.hashicorp.com/terraform/language/values/outputs#depends_on-explicit-output-dependencies)
+ field in an output declaration, not the dependencies that get derived from
+ natural evaluation of the output expression (these can be found in the
+ `references` field of the expression representation).
+
+## The `module_calls` Collection
+
+The `module_calls` collection is a collection of all module declarations at all
+levels within the configuration.
+
+Note that this is the
+[`module`](https://developer.hashicorp.com/terraform/language/modules#calling-a-child-module) stanza in
+any particular configuration, and not the module itself. Hence, a declaration
+for `module.foo` would actually be declared in the root module, which would be
+represented by a blank field in `module_address`.
+
+This collection is indexed by the key format `module_address:name`, with each
+field matching their respective name below. `module_address` and the colon
+delimiter are omitted for the root module.
+
+* `module_address` - The address of the module the declaration was found in.
+* `name` - The name of the module.
+* `source` - The contents of the `source` field.
+* `config` - A [block expression
+ representation](#block-expression-representation) for all parameter values
+ sent to the module.
+* `count` - An [expression representation](#expression-representations) for the
+ `count` field.
+* `depends_on`: An [expression representation](#expression-representations) for the
+ `depends_on` field.
+* `for_each` - An [expression representation](#expression-representations) for
+ the `for_each` field.
+* `version_constraint` - The string value found in the `version` field of the
+ module declaration.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/features/terraform/tfplan-v1.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/features/terraform/tfplan-v1.mdx
new file mode 100644
index 0000000000..1e9f9f1f9b
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/features/terraform/tfplan-v1.mdx
@@ -0,0 +1,614 @@
+---
+page_title: tfplan/v1 - terraform - Features
+sidebar_current: docs-features-terraform-tfplan-v1
+description: >-
+ The tfplan/v1 import provides access to a Terraform plan. A Terraform plan is the
+ file created as a result of `terraform plan` and is the input to `terraform
+ apply`. The plan represents the changes that Terraform needs to make to
+ infrastructure to reach the desired state represented by the configuration.
+layout: docs
+---
+
+# Import: tfplan/v1
+
+~> **Warning:** The `tfplan/v1` import is deprecated and will be permanently removed in August 2025.
+Use the updated [tfplan/v2](/sentinel/docs/features/terraform/tfplan-v2) import as soon as possible to avoid disruptions.
+The `tfplan/v2` import offers improved functionality and is designed to better support your policy enforcement needs.
+
+The `tfplan/v1` import provides access to a Terraform plan. A Terraform plan is the
+file created as a result of `terraform plan` and is the input to `terraform
+apply`. The plan represents the changes that Terraform needs to make to
+infrastructure to reach the desired state represented by the configuration.
+
+In addition to the diff data available in the plan, there is an
+[`applied`](#value-applied) state available that merges the plan with the state
+to create the planned state after apply.
+
+Finally, this import also allows you to access the configuration files and the
+Terraform state at the time the plan was run. See the section on [accessing a
+plan's state and configuration
+data](#accessing-a-plan-39-s-state-and-configuration-data) for more information.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan and schemas file.
+
+```hcl
+import "plugin" "tfplan/v1" {
+ config = {
+ "plan_path": "./path/to/plan.json",
+ "schemas_path": "./path/to/schemas.json"
+ }
+}
+```
+
+## Namespace Overview
+
+The following is a tree view of the import namespace. For more detail on a
+particular part of the namespace, see below.
+
+-> Note that the root-level alias keys shown here (`data`, `path`, and
+`resources`) are shortcuts to a [module namespace](#namespace-module) scoped to
+the root module. For more details, see the section on [root namespace
+aliases](#root-namespace-aliases).
+
+```
+tfplan/v1
+├── module() (function)
+│ └── (module namespace)
+│ ├── path ([]string)
+│ ├── data
+│ │ └── TYPE.NAME[NUMBER]
+│ │ ├── applied (map of keys)
+│ │ └── diff
+│ │ └── KEY
+│ │ ├── computed (bool)
+│ │ ├── new (string)
+│ │ └── old (string)
+│ └── resources
+│ └── TYPE.NAME[NUMBER]
+│ ├── applied (map of keys)
+│ ├── destroy (bool)
+│ ├── requires_new (bool)
+│ └── diff
+│ └── KEY
+│ ├── computed (bool)
+│ ├── new (string)
+│ └── old (string)
+├── module_paths ([][]string)
+├── terraform_version (string)
+├── variables (map of keys)
+│
+├── data (root module alias)
+├── path (root module alias)
+├── resources (root module alias)
+│
+├── config (tfconfig namespace alias)
+└── state (tfstate import alias)
+```
+
+## Namespace: Root
+
+The root-level namespace consists of the values and functions documented below.
+
+In addition to this, the root-level `data`, `path`, and `resources` keys alias
+to their corresponding namespaces or values within the [module
+namespace](#namespace-module).
+
+### Accessing a Plan's State and Configuration Data
+
+The `config` and `state` keys alias to the [`tfconfig`](/sentinel/features/terraform/tfconfig-v1) and
+[`tfstate`](/sentinel/features/terraform/tfstate-v1) namespaces, respectively, with the data sourced from
+the Terraform _plan_ (as opposed to actual configuration and state).
+
+-> Note that these aliases are not represented as maps. While they will appear
+empty when viewed as maps, the specific import namespace keys will still be
+accessible.
+
+### Function: `module()`
+
+```
+module = func(ADDR)
+```
+
+* **Return Type:** A [module namespace](#namespace-module).
+
+The `module()` function in the [root namespace](#namespace-root) returns the
+[module namespace](#namespace-module) for a particular module address.
+
+The address must be a list and is the module address, split on the period (`.`),
+excluding the root module.
+
+Hence, a module with an address of simply `foo` (or `root.foo`) would be
+`["foo"]`, and a module within that (so address `foo.bar`) would be read as
+`["foo", "bar"]`.
+
+[`null`](/sentinel/language/spec#null) is returned if a module address is
+invalid, or if the module is not present in the diff.
+
+As an example, given the following module block:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+If the module contained the following content:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.module(["foo"]).resources.null_resource.foo[0].applied.triggers.foo is "bar" }
+```
+
+### Value: `module_paths`
+
+* **Value Type:** List of a list of strings.
+
+The `module_paths` value within the [root namespace](#namespace-root) is a list
+of all of the modules within the Terraform diff for the current plan.
+
+Modules not present in the diff will not be present here, even if they are
+present in the configuration or state.
+
+This data is represented as a list of a list of strings, with the inner list
+being the module address, split on the period (`.`).
+
+The root module is included in this list, represented as an empty inner list, as
+long as there are changes.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The value of `module_paths` would be:
+
+```
+[
+ [],
+ ["foo"],
+]
+```
+
+And the following policy would evaluate to `true`:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.module_paths contains ["foo"] }
+```
+
+-> Note the above example only applies if the module is present in the diff.
+
+#### Iterating Through Modules
+
+Iterating through all modules to find particular resources can be useful. This
+[example][iterate-over-modules] shows how to use `module_paths` with the
+[`module()` function](#function-module-) to find all resources of a
+particular type from all modules that have pending changes using the `tfplan/v1`
+import.
+
+[iterate-over-modules]: https://developer.hashicorp.com/terraform/cloud-docs/policy-enforcement/sentinel#iterate-over-modules-and-find-resources
+
+### Value: `terraform_version`
+
+* **Value Type:** String.
+
+The `terraform_version` value within the [root namespace](#namespace-root)
+represents the version of Terraform used to create the plan. This can be used to
+enforce a specific version of Terraform in a policy check.
+
+As an example, the following policy would evaluate to `true`, as long as the
+plan was made with a version of Terraform in the 0.11.x series, excluding any
+pre-release versions (example: `-beta1` or `-rc1`):
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.terraform_version matches "^0\\.11\\.\\d+$" }
+```
+
+### Value: `variables`
+
+* **Value Type:** A string-keyed map of values.
+
+The `variables` value within the [root namespace](#namespace-root) represents
+all of the variables that were set when creating the plan. This will only
+contain variables set for the root module.
+
+Note that unlike the [`default`][import-tfconfig-variables-default] value in the
+[`tfconfig` variables namespace][import-tfconfig-variables], primitive values
+here are stringified, and type conversion will need to be performed to perform
+comparison for int, float, or boolean values. This only applies to variables
+that are primitives themselves and not primitives within maps and lists, which
+will be their original types.
+
+[import-tfconfig-variables-default]: /sentinel/features/terraform/tfconfig-v1#value-default
+
+[import-tfconfig-variables]: /sentinel/features/terraform/tfconfig-v1#namespace-variables
+
+If a default was accepted for the particular variable, the default value will be
+populated here.
+
+As an example, given the following variable blocks:
+
+```hcl
+variable "foo" {
+ default = "bar"
+}
+
+variable "number" {
+ default = 42
+}
+
+variable "map" {
+ default = {
+ foo = "bar"
+ number = 42
+ }
+}
+```
+
+The following policy would evaluate to `true`, if no values were entered to
+change these variables:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+default_foo = rule { tfplan.variables.foo is "bar" }
+default_number = rule { tfplan.variables.number is "42" }
+default_map_string = rule { tfplan.variables.map["foo"] is "bar" }
+default_map_int = rule { tfplan.variables.map["number"] is 42 }
+
+main = rule { default_foo and default_number and default_map_string and default_map_int }
+```
+
+## Namespace: Module
+
+The **module namespace** can be loaded by calling
+[`module()`](#function-module-) for a particular module.
+
+It can be used to load the following child namespaces, in addition to the values
+documented below:
+
+* `data` - Loads the [resource namespace](#namespace-resources-data-sources),
+ filtered against data sources.
+* `resources` - Loads the [resource
+ namespace](#namespace-resources-data-sources), filtered against resources.
+
+### Root Namespace Aliases
+
+The root-level `data` and `resources` keys both alias to their corresponding
+namespaces within the module namespace, loaded for the root module. They are the
+equivalent of running `module([]).KEY`.
+
+### Value: `path`
+
+* **Value Type:** List of strings.
+
+The `path` value within the [module namespace](#namespace-module) contains the
+path of the module that the namespace represents. This is represented as a list
+of strings.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The following policy would evaluate to `true` _only_ if the diff had changes for
+that module:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.module(["foo"]).path contains "foo" }
+```
+
+## Namespace: Resources/Data Sources
+
+The **resource namespace** is a namespace _type_ that applies to both resources
+(accessed by using the `resources` namespace key) and data sources (accessed
+using the `data` namespace key).
+
+Accessing an individual resource or data source within each respective namespace
+can be accomplished by specifying the type, name, and resource number (as if the
+resource or data source had a `count` value in it) in the syntax
+`[resources|data].TYPE.NAME[NUMBER]`. Note that NUMBER is always needed, even if
+you did not use `count` in the resource.
+
+In addition, each of these namespace levels is a map, allowing you to filter
+based on type and name.
+
+-> The (somewhat strange) notation here of `TYPE.NAME[NUMBER]` may imply that
+the inner resource index map is actually a list, but it's not - using the square
+bracket notation over the dotted notation (`TYPE.NAME.NUMBER`) is required here
+as an identifier cannot start with a number.
+
+Some examples of multi-level access are below:
+
+* To fetch all `aws_instance.foo` resource instances within the root module, you
+ can specify `tfplan.resources.aws_instance.foo`. This would then be indexed by
+ resource count index (`0`, `1`, `2`, and so on). Note that as mentioned above,
+ these elements must be accessed using square-bracket map notation (so `[0]`,
+ `[1]`, `[2]`, and so on) instead of dotted notation.
+* To fetch all `aws_instance` resources within the root module, you can specify
+ `tfplan.resources.aws_instance`. This would be indexed from the names of
+ each resource (`foo`, `bar`, and so on), with each of those maps containing
+ instances indexed by resource count index as per above.
+* To fetch all resources within the root module, irrespective of type, use
+ `tfplan.resources`. This is indexed by type, as shown above with
+ `tfplan.resources.aws_instance`, with names being the next level down, and so
+ on.
+
+~> When [resource targeting](https://developer.hashicorp.com/terraform/cli/commands/plan#resource-targeting) is
+ in effect, `tfplan.resources` will only include the resources specified as
+ targets for the run. This may lead to unexpected outcomes if a policy expects
+ a resource to be present in the plan.
+
+Further explanation of the namespace will be in the context of resources. As
+mentioned, when operating on data sources, use the same syntax, except with
+`data` in place of `resources`.
+
+### Value: `applied`
+
+* **Value Type:** A string-keyed map of values.
+
+The `applied` value within the [resource
+namespace](#namespace-resources-data-sources) contains a "predicted"
+representation of the resource's state post-apply. It's created by merging the
+pending resource's diff on top of the existing data from the resource's state
+(if any). The map is a complex representation of these values with data going
+as far down as needed to represent any state values such as maps, lists, and
+sets.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if the resource was in the diff:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].applied.triggers.foo is "bar" }
+```
+
+-> Note that some values will not be available in the `applied` state because
+they cannot be known until the plan is actually applied. In Terraform 0.11 or
+earlier, these values are represented by a placeholder (the UUID value
+`74D93920-ED26-11E3-AC10-0800200C9A66`) and in Terraform 0.12 or later they
+are `undefined`. **In either case**, you should instead use the
+[`computed`](#value-computed) key within the [diff
+namespace](#namespace-resource-diff) to determine that a computed value will
+exist.
+
+-> If a resource is being destroyed, its `applied` value is omitted from the
+namespace and trying to fetch it will return undefined.
+
+### Value: `diff`
+
+* **Value Type:** A map of [diff namespaces](#namespace-resource-diff).
+
+The `diff` value within the [resource
+namespace](#namespace-resources-data-sources) contains the diff for a particular
+resource. Each key within the map links to a [diff
+namespace](#namespace-resource-diff) for that particular key.
+
+Note that unlike the [`applied`](#value-applied) value, this map is not complex;
+the map is only 1 level deep with each key possibly representing a diff for a
+particular complex value within the resource.
+
+See the below section for more details on the diff namespace, in addition to
+usage examples.
+
+### Value: `destroy`
+
+* **Value Type:** Boolean.
+
+The `destroy` value within the [resource
+namespace](#namespace-resources-data-sources) is `true` if a resource is being
+destroyed for _any_ reason, including cases where it's being deleted as part of
+a resource re-creation, in which case [`requires_new`](#value-requires_new) will
+also be set.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` when `null_resource.foo` is being
+destroyed:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].destroy }
+```
+
+### Value: `requires_new`
+
+* **Value Type:** Boolean.
+
+The `requires_new` value within the [resource
+namespace](#namespace-resources-data-sources) is `true` if the resource is still
+present in the configuration, but must be replaced to satisfy its current diff.
+Whenever `requires_new` is `true`, [`destroy`](#value-destroy) is also `true`.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if one of the `triggers` in
+`null_resource.foo` was being changed:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].requires_new }
+```
+
+## Namespace: Resource Diff
+
+The **diff namespace** is a namespace that represents the diff for a specific
+attribute within a resource. For details on reading a particular attribute,
+see the [`diff`](#value-diff) value in the [resource
+namespace](#namespace-resources-data-sources).
+
+### Value: `computed`
+
+* **Value Type:** Boolean.
+
+The `computed` value within the [diff namespace](#namespace-resource-diff) is
+`true` if the resource key in question depends on another value that isn't yet
+known. Typically, that means the value it depends on belongs to a resource that
+either doesn't exist yet, or is changing state in such a way as to affect the
+dependent value so that it can't be known until the apply is complete.
+
+-> Keep in mind that when using `computed` with complex structures such as maps,
+lists, and sets, it's sometimes necessary to test the count attribute for the
+structure, versus a key within it, depending on whether or not the diff has
+marked the whole structure as computed. This is demonstrated in the example
+below. Count keys are `%` for maps, and `#` for lists and sets. If you are
+having trouble determining the type of specific field within a resource, contact
+the support team.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+
+resource "null_resource" "bar" {
+ triggers = {
+ foo_id = "${null_resource.foo.id}"
+ }
+}
+```
+
+The following policy would evaluate to `true`, if the `id` of
+`null_resource.foo` was currently not known, such as when the resource is
+pending creation, or is being deleted and re-created:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.bar[0].diff["triggers.%"].computed }
+```
+
+### Value: `new`
+
+* **Value Type:** String.
+
+The `new` value within the [diff namespace](#namespace-resource-diff) contains
+the new value of a changing attribute, _if_ the value is known at plan time.
+
+-> `new` will be an empty string if the attribute's value is currently unknown.
+For more details on detecting unknown values, see [`computed`](#value-computed).
+
+Note that this value is _always_ a string, regardless of the actual type of the
+value changing. [Type conversion][ref-sentinel-type-conversion] within policy
+may be necessary to achieve the comparison needed.
+
+[ref-sentinel-type-conversion]: https://docs.hashicorp.com/sentinel/language/values#type-conversion
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`, if the resource was in the diff
+and each of the concerned keys were changing to new values:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].diff["triggers.foo"].new is "bar" }
+```
+
+### Value: `old`
+
+* **Value Type:** String.
+
+The `old` value within the [diff namespace](#namespace-resource-diff) contains
+the old value of a changing attribute.
+
+Note that this value is _always_ a string, regardless of the actual type of the
+value changing. [Type conversion][ref-sentinel-type-conversion] within policy
+may be necessary to achieve the comparison needed.
+
+If the value did not exist in the previous state, `old` will always be an empty
+string.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "baz"
+ }
+}
+```
+
+If that resource was previously in config as:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfplan/v1" as tfplan
+
+main = rule { tfplan.resources.null_resource.foo[0].diff["triggers.foo"].old is "bar" }
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/features/terraform/tfplan-v2.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/features/terraform/tfplan-v2.mdx
new file mode 100644
index 0000000000..61fb7b2bf2
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/features/terraform/tfplan-v2.mdx
@@ -0,0 +1,402 @@
+---
+page_title: tfplan/v2 - terraform - Features
+sidebar_current: docs-features-terraform-tfplan-v2
+description: >-
+ The tfplan import provides access to a Terraform plan. A Terraform plan is the
+ file created as a result of `terraform plan` and is the input to `terraform
+ apply`. The plan represents the changes that Terraform needs to make to
+ infrastructure to reach the desired state represented by the configuration.
+layout: docs
+---
+
+# Import: tfplan/v2
+
+The `tfplan/v2` import provides access to a Terraform plan.
+
+A Terraform plan is the file created as a result of `terraform plan` and is the
+input to `terraform apply`. The plan represents the changes that Terraform needs
+to make to infrastructure to reach the desired state represented by the
+configuration.
+
+In addition to the diff data available in the plan, there is a "planned state"
+that is available through this import, via the
+[`planned_values`](#the-planned_values-collection) collection. This collection
+presents the Terraform state as how it might look after the plan data is
+applied, but is not guaranteed to be the final state.
+
+The data in the `tfplan/v2` import is sourced from the JSON configuration file
+that is generated by the [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) command. For more information on
+the file format, see the [JSON Output Format](https://developer.hashicorp.com/terraform/internals/json-format)
+page.
+
+The entirety of the JSON output file is exposed as a Sentinel map via the
+[`raw`](#the-raw-collection) collection. This allows direct, low-level access to
+the JSON data, but should only be used in complex situations where the
+higher-level collections do not serve the purpose.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfplan/v2" {
+ config = {
+ "plan_path": "./path/to/plan.json"
+ }
+}
+```
+
+## Import Overview
+
+The `tfplan/v2` import is structured as a series of _collections_, keyed as a
+specific format depending on the collection.
+
+```
+tfplan/v2
+├── terraform_version (string)
+├── variables
+│ └── (indexed by name)
+│ ├── name (string)
+│ └── value (value)
+├── planned_values
+│ ├── outputs (tfstate/v2 outputs representation)
+│ └── resources (tfstate/v2 resources representation)
+├── resource_changes
+│ └── (indexed by address[:deposed])
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── index (float (number) or string)
+│ ├── provider_name (string)
+│ ├── deposed (string)
+│ └── change (change representation)
+├── resource_drift
+│ └── (indexed by address[:deposed])
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── index (float (number) or string)
+│ ├── provider_name (string)
+│ ├── deposed (string)
+│ └── change (change representation)
+├── output_changes
+│ └── (indexed by name)
+│ ├── name (string)
+│ └── change (change representation)
+└── raw (map)
+```
+
+The collections are:
+
+* [`variables`](#the-variables-collection) - The values of variables that have
+ been set in the plan itself. This collection only contains variables set in
+ the root module.
+* [`planned_values`](#the-planned_values-collection) - The state representation
+ of _planned values_, or an estimation of what the state will look like after
+ the plan is applied.
+* [`resource_changes`](#the-resource_changes-and-resource_drift-collections) - The set of change
+ operations for resources and data sources within this plan.
+* [`resource_drift`](#the-resource_changes-and-resource_drift-collections) - A description of the
+ changes Terraform detected when it compared the most recent state to the prior saved state.
+* [`output_changes`](#the-output_changes-collection) - The changes to outputs
+ within this plan. This collection only contains outputs set in the root
+ module.
+* [`raw`](#the-raw-collection) - Access to the raw plan data.
+
+These collections are specifically designed to be used with the
+[`filter`](/sentinel/language/collection-operations#filter-expression)
+quantifier expression in Sentinel, so that one can collect a list of resources
+to perform policy checks on without having to write complex discovery code. As
+an example, the following code will return all `aws_instance` resource changes,
+across all modules in the plan:
+
+```
+all_aws_instances = filter tfplan.resource_changes as _, rc {
+ rc.mode is "managed" and
+ rc.type is "aws_instance"
+}
+```
+
+You can add specific attributes to the filter to narrow the search, such as the
+module address, or the operation being performed. The following code would
+return resources in a module named `foo` only, and further narrow the search
+down to only resources that were being created:
+
+```
+all_aws_instances = filter tfplan.resource_changes as _, rc {
+ rc.module_address is "module.foo" and
+ rc.mode is "managed" and
+ rc.type is "aws_instance" and
+ rc.change.actions is ["create"]
+}
+```
+
+### Change Representation
+
+Certain collections in this import contain a _change representation_, an object
+with details about changes to a particular entity, such as a resource (within
+the [`resource_changes`](#the-resource_changes-collection) collection), or
+output (within the [`output_changes`](#the-output_changes-collection)
+collection).
+
+```
+(change representation)
+├── actions (list)
+├── before (value, or map)
+├── after (value, or map)
+└── after_unknown (boolean, or map of booleans)
+```
+
+This change representation contains the following fields:
+
+* `actions` - A list of actions being carried out for this change. The order is
+ important, for example a regular replace operation is denoted by `["delete",
+ "create"]`, but a
+ [`create_before_destroy`](https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle#create_before_destroy)
+ resource will have an operation order of `["create", "delete"]`.
+* `before` - The representation of the resource data object value before the
+ action. For create-only actions, this is unset. For no-op actions, this value
+ will be identical with `after`.
+* `after` - The representation of the resource data object value after the
+ action. For delete-only actions, this is unset. For no-op actions, this value
+ will be identical with `before`. Note that unknown values will not show up in
+ this field.
+* `after_unknown` - A deep object of booleans that denotes any values that are
+ unknown in a resource. These values were previously referred to as "computed"
+ values. If the value cannot be found in this map, then its value should be
+ available within `after`, so long as the operation supports it.
+
+#### Actions
+
+As mentioned above, actions show up within the `actions` field of a change
+representation and indicate the type of actions being performed as part of the
+change, and the order that they are being performed in.
+
+The current list of actions are as follows:
+
+* `create` - The action will create the associated entity. Depending on the
+ order this appears in, the entity may be created alongside a copy of the
+ entity before replacing it.
+* `read` - The action will read the associated entity. In practice, seeing this
+ change type should be rare, as reads generally happen before a plan is
+ executed (usually during a refresh).
+* `update` - The action will update the associated entity in a way that alters its state
+ in some way.
+* `delete` - The action will remove the associated entity, deleting any
+ applicable state and associated real resources or infrastructure.
+* `no-op` - No action will be performed on the associated entity.
+
+The `actions` field is a list, as some real-world actions are actually a
+composite of more than one primitive action. At this point in time, this
+is generally only applicable to resource replacement, in which the following
+action orders apply:
+
+* **Normal replacement:** `["delete", "create"]` - Applies to default lifecycle
+ configurations.
+* **Create-before-destroy:** `["create", "delete"]` - Applies when
+ [`create_before_destroy`](https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle#create_before_destroy)
+ is used in a lifecycle configuration.
+
+Note that, in most situations, the plan will list all "changes", including no-op
+changes. This makes filtering on change type crucial to the accurate selection
+of data if you are concerned with the state change of a particular resource.
+
+To filter on a change type, use exact list comparison. For example, the
+following example from the [Import Overview](#import-overview) filters on
+exactly the resources being created _only_:
+
+```
+all_aws_instances = filter tfplan.resource_changes as _, rc {
+ rc.module_address is "module.foo" and
+ rc.mode is "managed" and
+ rc.type is "aws_instance" and
+ rc.change.actions is ["create"]
+}
+```
+
+#### `before`, `after`, and `after_unknown`
+
+The exact attribute changes for a particular operation are outlined in the
+`before` and `after` attributes. Depending on the entity being operated on, this
+will either be a map (as with
+[`resource_changes`](#the-resource_changes-collection)) or a singular value (as
+with [`output_changes`](#the-output_changes-collection)).
+
+What you can expect in these fields varies depending on the operation:
+
+* For fresh create operations, `before` will generally be `null`, and `after`
+ will contain the data you can expect to see after the change.
+* For full delete operations, this will be reversed - `before` will contain
+ data, and `after` will be `null`.
+* Update or replace operations will have data in both fields relevant to their
+ states before and after the operation.
+* No-op operations should have identical data in `before` and `after`.
+
+For resources, if a field cannot be found in `after`, it generally means one of
+two things:
+
+* The attribute does not exist in the resource schema. Generally, known
+ attributes that do not have a value will show up as `null` or otherwise empty
+ in `after`.
+* The attribute is _unknown_, that is, it was unable to be determined at plan
+ time and will only be available after apply-time values have been able to be
+ calculated.
+
+In the latter case, there should be a value for the particular attribute in
+`after_unknown`, which can be checked to assert that the value is indeed
+unknown, versus invalid:
+
+```
+import "tfplan/v2" as tfplan
+
+no_unknown_amis = rule {
+ all filter tfplan.resource_changes as _, rc {
+ rc.module_address is "module.foo" and
+ rc.mode is "managed" and
+ rc.type is "aws_instance" and
+ rc.change.actions is ["create"]
+ } as _, rc {
+ rc.change.after_unknown.ami else false is false
+ }
+}
+```
+
+For output changes, `after_unknown` will simply be `true` if the value won't be
+known until the plan is applied.
+
+## The `terraform_version` Value
+
+The top-level `terraform_version` value in this import gives the Terraform
+version that made the plan. This can be used to do version validation.
+
+```
+import "tfplan/v2" as tfplan
+import "strings"
+
+v = strings.split(tfplan.terraform_version, ".")
+version_major = int(v[1])
+version_minor = int(v[2])
+
+main = rule {
+ version_major is 12 and version_minor >= 19
+}
+```
+
+## The `variables` Collection
+
+The `variables` collection is a collection of the variables set in the root
+module when creating the plan.
+
+This collection is indexed on the name of the variable.
+
+The valid values are:
+
+* `name` - The name of the variable, also used as the collection key.
+* `value` - The value of the variable assigned during the plan.
+
+## The `planned_values` Collection
+
+The `planned_values` collection is a special collection in that it contains two
+fields that alias to state collections with the _planned_ state set. This is the
+best prediction of what the state will look like after the plan is executed.
+
+The two fields are:
+
+* `outputs` - The prediction of what output values will look like after the
+ state is applied. For more details on the structure of this collection, see
+ the [`outputs`](/sentinel/features/terraform/tfstate-v2#the-outputs-collection) collection in the
+ [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2) documentation.
+* `resources` - The prediction of what resource values will look like after the
+ state is applied. For more details on the structure of this collection, see
+ the [`resources`](/sentinel/features/terraform/tfstate-v2#the-resources-collection) collection in the
+ [`tfstate/v2`](/sentinel/features/terraform/tfstate-v2) documentation.
+
+-> **NOTE:** Unknown values are omitted from the `planned_values` state
+representations, regardless of whether or not they existed before. Use
+[`resource_changes`](#the-resource_changes-collection) if awareness of unknown
+data is important.
+
+## The `resource_changes` and `resource_drift` Collections
+
+The `resource_changes` and `resource_drift` collections are a set of change operations for resources
+and data sources within this plan.
+
+The `resource_drift` collection provides a description of the changes Terraform detected
+when it compared the most recent state to the prior saved state.
+
+The `resource_changes` collection includes all resources that have been found in the configuration and state,
+regardless of whether or not they are changing.
+
+~> When [resource targeting](/terraform/cli/commands/plan#resource-targeting) is in effect, the `resource_changes` collection will only include the resources specified as targets for the run. This may lead to unexpected outcomes if a policy expects a resource to be present in the plan. To prohibit targeted runs altogether, ensure [`tfrun.target_addrs`](/terraform/cloud-docs/policy-enforcement/sentinel/import/tfrun#value-target_addrs) is undefined or empty.
+
+This collection is indexed on the complete resource address as the key. If
+`deposed` is non-empty, it is appended to the end, and may look something like
+`aws_instance.foo:deposed-abc123`.
+
+An element contains the following fields:
+
+* `address` - The absolute resource address - also the key for the collection's
+ index, if `deposed` is empty.
+
+* `module_address` - The module portion of the absolute resource address.
+
+* `mode` - The resource mode, either `managed` (resources) or `data` (data
+ sources).
+
+* `type` - The resource type, example: `aws_instance` for `aws_instance.foo`.
+
+* `name` - The resource name, example: `foo` for `aws_instance.foo`.
+
+* `index` - The resource index. Can be either a number or a string.
+
+* `provider_name` - The name of the provider this resource belongs to. This
+ allows the provider to be interpreted unambiguously in the unusual situation
+ where a provider offers a resource type whose name does not start with its own
+ name, such as the `googlebeta` provider offering `google_compute_instance`.
+
+ -> **Note:** Starting with Terraform 0.13, the `provider_name` field contains the
+ _full_ source address to the provider in the Terraform Registry. Example:
+ `registry.terraform.io/hashicorp/null` for the null provider.
+
+* `deposed` - An identifier used during replacement operations, and can be used
+ to identify the exact resource being replaced in state.
+
+* `change` - The data describing the change that will be made to this resource.
+ For more details, see [Change Representation](#change-representation).
+
+## The `output_changes` Collection
+
+The `output_changes` collection is a collection of the change operations for
+outputs within this plan.
+
+Only outputs for the root module are included.
+
+This collection is indexed by the name of the output. The fields in a collection
+value are below:
+
+* `name` - The name of the output, also the index key.
+* `change` - The data describing the change that will be made to this output.
+ For more details, see [Change Representation](#change-representation).
+
+## The `raw` Collection
+
+The `raw` collection exposes the raw, unprocessed plan data.
+
+This is the same data that is produced by [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) on the plan file for the run this
+policy check is attached to.
+
+Use of this data is only recommended in expert situations where the data the
+collections present may not exactly serve the needs of the policy. For more
+information on the file format, see the [JSON Output
+Format](https://developer.hashicorp.com/terraform/internals/json-format) page.
+
+-> **NOTE:** Although designed to be relatively stable, the actual makeup for
+the JSON output format is a Terraform CLI concern and as such not managed by
+Sentinel. Use at your own risk, follow the [Terraform CLI
+project](https://github.com/hashicorp/terraform), and watch the file format
+documentation for any changes.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/features/terraform/tfstate-v1.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/features/terraform/tfstate-v1.mdx
new file mode 100644
index 0000000000..7ff2690947
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/features/terraform/tfstate-v1.mdx
@@ -0,0 +1,556 @@
+---
+page_title: tfstate/v1 - terraform - Features
+sidebar_current: docs-features-terraform-tfstate-v1
+description: The tfstate/v1 import provides access to a Terraform state.
+layout: docs
+---
+
+# Import: tfstate/v1
+
+~> **Warning:** The `tfstate/v1` import is deprecated and will be permanently removed in August 2025.
+Use the updated [tfstate/v2](/sentinel/docs/features/terraform/tfstate-v2) import as soon as possible to avoid disruptions.
+The `tfstate/v2` import offers improved functionality and is designed to better support your policy enforcement needs.
+
+The `tfstate/v1` import provides access to the Terraform state.
+
+The _state_ is the data that Terraform has recorded about a workspace at a
+particular point in its lifecycle, usually after an apply. You can read more
+general information about how Terraform uses state [here][ref-tf-state].
+
+[ref-tf-state]: https://developer.hashicorp.com/terraform/language/state
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfstate/v1" {
+ config = {
+ "path": "./path/to/plan.json"
+ }
+}
+```
+
+## Namespace Overview
+
+The following is a tree view of the import namespace. For more detail on a
+particular part of the namespace, see below.
+
+-> Note that the root-level alias keys shown here (`data`, `outputs`, `path`,
+and `resources`) are shortcuts to a [module namespace](#namespace-module) scoped
+to the root module. For more details, see the section on [root namespace
+aliases](#root-namespace-aliases).
+
+```
+tfstate/v1
+├── module() (function)
+│ └── (module namespace)
+│ ├── path ([]string)
+│ ├── data
+│ │ └── TYPE.NAME[NUMBER]
+│ │ ├── attr (map of keys)
+│ │ ├── depends_on ([]string)
+│ │ ├── id (string)
+│ │ └── tainted (boolean)
+│ ├── outputs (root module only in TF 0.12 or later)
+│ │ └── NAME
+│ │ ├── sensitive (bool)
+│ │ ├── type (string)
+│ │ └── value (value)
+│ └── resources
+│ └── TYPE.NAME[NUMBER]
+│ ├── attr (map of keys)
+│ ├── depends_on ([]string)
+│ ├── id (string)
+│ └── tainted (boolean)
+│
+├── module_paths ([][]string)
+├── terraform_version (string)
+│
+├── data (root module alias)
+├── outputs (root module alias)
+├── path (root module alias)
+└── resources (root module alias)
+```
+
+## Namespace: Root
+
+The root-level namespace consists of the values and functions documented below.
+
+In addition to this, the root-level `data`, `outputs`, `path`, and `resources`
+keys alias to their corresponding namespaces or values within the [module
+namespace](#namespace-module).
+
+### Function: `module()`
+
+```
+module = func(ADDR)
+```
+
+* **Return Type:** A [module namespace](#namespace-module).
+
+The `module()` function in the [root namespace](#namespace-root) returns the
+[module namespace](#namespace-module) for a particular module address.
+
+The address must be a list and is the module address, split on the period (`.`),
+excluding the root module.
+
+Hence, a module with an address of simply `foo` (or `root.foo`) would be
+`["foo"]`, and a module within that (so address `foo.bar`) would be read as
+`["foo", "bar"]`.
+
+[`null`][ref-null] is returned if a module address is invalid, or if the module
+is not present in the state.
+
+[ref-null]: /sentinel/language/spec#null
+
+As an example, given the following module block:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+If the module contained the following content:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if the resource was present in
+the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.module(["foo"]).resources.null_resource.foo[0].attr.triggers.foo is "bar" }
+```
+
+### Value: `module_paths`
+
+* **Value Type:** List of a list of strings.
+
+The `module_paths` value within the [root namespace](#namespace-root) is a list
+of all of the modules within the Terraform state at plan-time.
+
+Modules not present in the state will not be present here, even if they are
+present in the configuration or the diff.
+
+This data is represented as a list of a list of strings, with the inner list
+being the module address, split on the period (`.`).
+
+The root module is included in this list, represented as an empty inner list, as
+long as it is present in state.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The value of `module_paths` would be:
+
+```
+[
+ [],
+ ["foo"],
+]
+```
+
+And the following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.module_paths contains ["foo"] }
+```
+
+-> Note the above example only applies if the module is present in the state.
+
+#### Iterating Through Modules
+
+Iterating through all modules to find particular resources can be useful. This
+[example][iterate-over-modules] shows how to use `module_paths` with the
+[`module()` function](#function-module-) to find all resources of a
+particular type from all modules using the `tfplan` import. By changing `tfplan`
+in this function to `tfstate`, you could make a similar function find all
+resources of a specific type in the current state.
+
+[iterate-over-modules]: https://developer.hashicorp.com/terraform/cloud-docs/policy-enforcement/sentinel#sentinel-imports
+
+### Value: `terraform_version`
+
+* **Value Type:** String.
+
+The `terraform_version` value within the [root namespace](#namespace-root)
+represents the version of Terraform in use when the state was saved. This can be
+used to enforce a specific version of Terraform in a policy check.
+
+As an example, the following policy would evaluate to `true` as long as the
+state was made with a version of Terraform in the 0.11.x series, excluding any
+pre-release versions (example: `-beta1` or `-rc1`):
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.terraform_version matches "^0\\.11\\.\\d+$" }
+```
+
+-> **NOTE:** This value is also available via the [`tfplan`](/sentinel/features/terraform/tfplan-v1)
+import, which will be more current when a policy check is run against a plan.
+It's recommended you use the value in `tfplan` until HCP Terraform
+supports policy checks in other stages of the workspace lifecycle. See the
+[`terraform_version`][import-tfplan-terraform-version] reference within the
+`tfplan` import for more details.
+
+[import-tfplan-terraform-version]: /sentinel/features/terraform/tfplan-v1#value-terraform_version
+
+## Namespace: Module
+
+The **module namespace** can be loaded by calling
+[`module()`](#function-module-) for a particular module.
+
+It can be used to load the following child namespaces, in addition to the values
+documented below:
+
+* `data` - Loads the [resource namespace](#namespace-resources-data-sources),
+ filtered against data sources.
+* `outputs` - Loads the [output namespace](#namespace-outputs), which supply the
+ outputs present in this module's state. Note that with Terraform 0.12 or
+ later, this value is only available for the root namespace.
+* `resources` - Loads the [resource
+ namespace](#namespace-resources-data-sources), filtered against resources.
+
+### Root Namespace Aliases
+
+The root-level `data`, `outputs`, and `resources` keys both alias to their
+corresponding namespaces within the module namespace, loaded for the root
+module. They are the equivalent of running `module([]).KEY`.
+
+### Value: `path`
+
+* **Value Type:** List of strings.
+
+The `path` value within the [module namespace](#namespace-module) contains the
+path of the module that the namespace represents. This is represented as a list
+of strings.
+
+As an example, if the following module block was present within a Terraform
+configuration:
+
+```hcl
+module "foo" {
+ # ...
+}
+```
+
+The following policy would evaluate to `true`, _only_ if the module was present
+in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.module(["foo"]).path contains "foo" }
+```
+
+## Namespace: Resources/Data Sources
+
+The **resource namespace** is a namespace _type_ that applies to both resources
+(accessed by using the `resources` namespace key) and data sources (accessed
+using the `data` namespace key).
+
+Accessing an individual resource or data source within each respective namespace
+can be accomplished by specifying the type, name, and resource number (as if the
+resource or data source had a `count` value in it) in the syntax
+`[resources|data].TYPE.NAME[NUMBER]`. Note that NUMBER is always needed, even if
+you did not use `count` in the resource.
+
+In addition, each of these namespace levels is a map, allowing you to filter
+based on type and name.
+
+-> The (somewhat strange) notation here of `TYPE.NAME[NUMBER]` may imply that
+the inner resource index map is actually a list, but it's not - using the square
+bracket notation over the dotted notation (`TYPE.NAME.NUMBER`) is required here
+as an identifier cannot start with number.
+
+Some examples of multi-level access are below:
+
+* To fetch all `aws_instance.foo` resource instances within the root module, you
+ can specify `tfstate.resources.aws_instance.foo`. This would then be indexed
+ by resource count index (`0`, `1`, `2`, and so on). Note that as mentioned
+ above, these elements must be accessed using square-bracket map notation (so
+ `[0]`, `[1]`, `[2]`, and so on) instead of dotted notation.
+* To fetch all `aws_instance` resources within the root module, you can specify
+ `tfstate.resources.aws_instance`. This would be indexed from the names of
+ each resource (`foo`, `bar`, and so on), with each of those maps containing
+ instances indexed by resource count index as per above.
+* To fetch all resources within the root module, irrespective of type, use
+ `tfstate.resources`. This is indexed by type, as shown above with
+ `tfstate.resources.aws_instance`, with names being the next level down, and so
+ on.
+
+Further explanation of the namespace will be in the context of resources. As
+mentioned, when operating on data sources, use the same syntax, except with
+`data` in place of `resources`.
+
+### Value: `attr`
+
+* **Value Type:** A string-keyed map of values.
+
+The `attr` value within the [resource
+namespace](#namespace-resources-data-sources) is a direct mapping to the state
+of the resource.
+
+The map is a complex representation of these values with data going as far down
+as needed to represent any state values such as maps, lists, and sets.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true` if the resource was in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.resources.null_resource.foo[0].attr.triggers.foo is "bar" }
+```
+
+### Value: `depends_on`
+
+* **Value Type:** A list of strings.
+
+The `depends_on` value within the [resource
+namespace](#namespace-resources-data-sources) contains the dependencies for the
+resource.
+
+This is a list of full resource addresses, relative to the module (example:
+`null_resource.foo`).
+
+As an example, given the following resources:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+
+resource "null_resource" "bar" {
+ # ...
+
+ depends_on = [
+ "null_resource.foo",
+ ]
+}
+```
+
+The following policy would evaluate to `true` if the resource was in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.resources.null_resource.bar[0].depends_on contains "null_resource.foo" }
+```
+
+### Value: `id`
+
+* **Value Type:** String.
+
+The `id` value within the [resource
+namespace](#namespace-resources-data-sources) contains the id of the resource.
+
+-> **NOTE:** The example below uses a _data source_ here because the
+[`null_data_source`][ref-tf-null-data-source] data source gives a static ID,
+which makes documenting the example easier. As previously mentioned, data
+sources share the same namespace as resources, but need to be loaded with the
+`data` key. For more information, see the
+[synopsis](#namespace-resources-data-sources) for the namespace itself.
+
+[ref-tf-null-data-source]: https://registry.terraform.io/providers/hashicorp/null/latest/docs/data-sources/data_source
+
+As an example, given the following data source:
+
+```hcl
+data "null_data_source" "foo" {
+ # ...
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.data.null_data_source.foo[0].id is "static" }
+```
+
+### Value: `tainted`
+
+* **Value Type:** Boolean.
+
+The `tainted` value within the [resource
+namespace](#namespace-resources-data-sources) is `true` if the resource is
+marked as tainted in Terraform state.
+
+As an example, given the following resource:
+
+```hcl
+resource "null_resource" "foo" {
+ triggers = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`, if the resource was marked as
+tainted in the state:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.resources.null_resource.foo[0].tainted }
+```
+
+## Namespace: Outputs
+
+The **output namespace** represents all of the outputs present within a
+[module](#namespace-module). Outputs are present in a state if they were saved
+during a previous apply, or if they were updated with known values during the
+pre-plan refresh.
+
+**With Terraform 0.11 or earlier** this can be used to fetch both the outputs
+of the root module, and the outputs of any module in the state below the root.
+This makes it possible to see outputs that have not been threaded to the root
+module.
+
+**With Terraform 0.12 or later** outputs are available in the top-level (root
+module) namespace only and not accessible within submodules.
+
+This namespace is indexed by output name.
+
+### Value: `sensitive`
+
+* **Value Type:** Boolean.
+
+The `sensitive` value within the [output namespace](#namespace-outputs) is
+`true` when the output has been [marked as sensitive][ref-tf-sensitive-outputs].
+
+[ref-tf-sensitive-outputs]: https://developer.hashicorp.com/terraform/language/values/outputs#sensitive-suppressing-values-in-cli-output
+
+As an example, given the following output:
+
+```hcl
+output "foo" {
+ sensitive = true
+ value = "bar"
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+main = rule { tfstate.outputs.foo.sensitive }
+```
+
+### Value: `type`
+
+* **Value Type:** String.
+
+The `type` value within the [output namespace](#namespace-outputs) gives the
+output's type. This will be one of `string`, `list`, or `map`. These are
+currently the only types available for outputs in Terraform.
+
+As an example, given the following output:
+
+```hcl
+output "string" {
+ value = "foo"
+}
+
+output "list" {
+ value = [
+ "foo",
+ "bar",
+ ]
+}
+
+output "map" {
+ value = {
+ foo = "bar"
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+type_string = rule { tfstate.outputs.string.type is "string" }
+type_list = rule { tfstate.outputs.list.type is "list" }
+type_map = rule { tfstate.outputs.map.type is "map" }
+
+main = rule { type_string and type_list and type_map }
+```
+
+### Value: `value`
+
+* **Value Type:** String, list, or map.
+
+The `value` value within the [output namespace](#namespace-outputs) is the value
+of the output in question.
+
+Note that the only valid primitive output type in Terraform is currently a
+string, which means that any int, float, or boolean value will need to be
+converted before it can be used in comparison. This does not apply to primitives
+within maps and lists, which will be their original types.
+
+As an example, given the following output blocks:
+
+```hcl
+output "foo" {
+ value = "bar"
+}
+
+output "number" {
+ value = "42"
+}
+
+output "map" {
+ value = {
+ foo = "bar"
+ number = 42
+ }
+}
+```
+
+The following policy would evaluate to `true`:
+
+```sentinel
+import "tfstate/v1" as tfstate
+
+value_foo = rule { tfstate.outputs.foo.value is "bar" }
+value_number = rule { int(tfstate.outputs.number.value) is 42 }
+value_map_string = rule { tfstate.outputs.map.value["foo"] is "bar" }
+value_map_int = rule { tfstate.outputs.map.value["number"] is 42 }
+
+main = rule { value_foo and value_number and value_map_string and value_map_int }
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/features/terraform/tfstate-v2.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/features/terraform/tfstate-v2.mdx
new file mode 100644
index 0000000000..9b29aa2c51
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/features/terraform/tfstate-v2.mdx
@@ -0,0 +1,176 @@
+---
+page_title: tfstate/v2 - terraform - Features
+sidebar_current: docs-features-terraform-tfstate-v2
+description: The tfstate/v2 import provides access to a Terraform state.
+layout: docs
+---
+
+# Import: tfstate/v2
+
+The `tfstate/v2` import provides access to a Terraform state.
+
+The _state_ is the data that Terraform has recorded about a workspace at a
+particular point in its lifecycle, usually after an apply. You can read more
+general information about how Terraform uses state
+[here](https://developer.hashicorp.com/terraform/language/state).
+
+The data in the `tfstate/v2` import is sourced from the JSON configuration file
+that is generated by the [`terraform show -json`](https://developer.hashicorp.com/terraform/cli/commands/show#json-output) command. For more information on
+the file format, see the [JSON Output Format](https://developer.hashicorp.com/terraform/internals/json-format)
+page.
+
+## Configuration Overview
+
+To configure the import, you must add the import to your configuration file
+and provide the path to the appropriate plan file.
+
+```hcl
+import "plugin" "tfstate/v2" {
+ config = {
+ "path": "./path/to/plan.json"
+ }
+}
+```
+
+## Import Overview
+
+The `tfstate/v2` import is structured as currently two _collections_, keyed in
+resource address and output name, respectively.
+
+```
+(tfstate/v2)
+├── terraform_version (string)
+├── resources
+│ └── (indexed by address)
+│ ├── address (string)
+│ ├── module_address (string)
+│ ├── mode (string)
+│ ├── type (string)
+│ ├── name (string)
+│ ├── index (float (number) or string)
+│ ├── provider_name (string)
+│ ├── values (map)
+│ ├── depends_on (list of strings)
+│ ├── tainted (boolean)
+│ └── deposed_key (string)
+└── outputs
+ └── (indexed by name)
+ ├── name (string)
+ ├── sensitive (boolean)
+ └── value (value)
+```
+
+The collections are:
+
+* [`resources`](#the-resources-collection) - The state of all resources across
+ all modules in the state.
+* [`outputs`](#the-outputs-collection) - The state of all outputs from the root module in the state.
+
+These collections are specifically designed to be used with the
+[`filter`](/sentinel/language/collection-operations#filter-expression)
+quantifier expression in Sentinel, so that one can collect a list of resources
+to perform policy checks on without having to write complex module traversal. As
+an example, the following code will return all `aws_instance` resource types
+within the state, regardless of what module they are in:
+
+```
+all_aws_instances = filter tfstate.resources as _, r {
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+You can add specific attributes to the filter to narrow the search, such as the
+module address. The following code would return resources in a module named
+`foo` only:
+
+```
+all_aws_instances = filter tfstate.resources as _, r {
+ r.module_address is "module.foo" and
+ r.mode is "managed" and
+ r.type is "aws_instance"
+}
+```
+
+## The `terraform_version` Value
+
+The top-level `terraform_version` value in this import gives the Terraform
+version that recorded the state. This can be used to do version validation.
+
+```
+import "tfstate/v2" as tfstate
+import "strings"
+
+v = strings.split(tfstate.terraform_version, ".")
+version_major = int(v[1])
+version_minor = int(v[2])
+
+main = rule {
+ version_major is 12 and version_minor >= 19
+}
+```
+
+## The `resources` Collection
+
+The `resources` collection is a collection representing all of the resources in
+the state, across all modules.
+
+This collection is indexed on the complete resource address as the key.
+
+An element in the collection has the following values:
+
+* `address` - The absolute resource address - also the key for the collection's
+ index.
+
+* `module_address` - The address portion of the absolute resource address.
+
+* `mode` - The resource mode, either `managed` (resources) or `data` (data
+ sources).
+
+* `type` - The resource type, example: `aws_instance` for `aws_instance.foo`.
+
+* `name` - The resource name, example: `foo` for `aws_instance.foo`.
+
+* `index` - The resource index. Can be either a number or a string.
+
+* `provider_name` - The name of the provider this resource belongs to. This
+ allows the provider to be interpreted unambiguously in the unusual situation
+ where a provider offers a resource type whose name does not start with its own
+ name, such as the `googlebeta` provider offering `google_compute_instance`.
+
+ -> **Note:** Starting with Terraform 0.13, the `provider_name` field contains the
+ _full_ source address to the provider in the Terraform Registry. Example:
+ `registry.terraform.io/hashicorp/null` for the null provider.
+
+* `values` - An object (map) representation of the attribute values of the
+ resource, whose structure depends on the resource type schema. When accessing
+ proposed state through the [`planned_values`](/sentinel/features/terraform/tfplan-v2#the-planned_values-collection)
+ collection of the tfplan/v2 import, unknown values will be omitted.
+
+* `depends_on` - The addresses of the resources that this resource depends on.
+
+* `tainted` - `true` if the resource has been explicitly marked as
+ [tainted](https://developer.hashicorp.com/terraform/cli/commands/taint) in the state.
+
+* `deposed_key` - Set if the resource has been marked deposed and will be
+ destroyed on the next apply. This matches the deposed field in the
+ [`resource_changes`](/sentinel/features/terraform/tfplan-v2#the-resource_changes-collection)
+ collection in the [`tfplan/v2`](/sentinel/features/terraform/tfplan-v2) import.
+
+## The `outputs` Collection
+
+The `outputs` collection is a collection of outputs from the root module of the
+state.
+
+Note that no child modules are included in this output set, and there is no way
+to fetch child module output values. This is to encourage the correct flow of
+outputs to the recommended root consumption level.
+
+The collection is indexed on the output name, with the following fields:
+
+* `name`: The name of the output, also the collection key.
+* `sensitive`: Whether or not the value was marked as
+ [sensitive](https://developer.hashicorp.com/terraform/language/values/outputs#sensitive-suppressing-values-in-cli-output)
+ in
+ configuration.
+* `value`: The value of the output.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/functions/append.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/functions/append.mdx
new file mode 100644
index 0000000000..81395154f6
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/functions/append.mdx
@@ -0,0 +1,46 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Append'
+sidebar_current: docs-funcs-append
+description: The built-in function `append` appends a value to the end of a list.
+layout: docs
+---
+
+# Builtin Function: append
+
+The built-in function `append` appends a value to the end of a list. The list
+is modified in-place. The return value will always be [undefined](/sentinel/language/undefined).
+
+`append` called on any value other than a list or undefined will result
+in an immediate error.
+
+Examples:
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+### Why does this function return undefined?
+
+This function returns `undefined` to avoid unintended side effects of the function in the context
+of a [rule](/sentinel/language/rules).
+
+For example, if `append` returned the list value as a result, the following policy would depend
+on the order in which rules are referenced:
+
+```sentinel
+list = []
+a = rule { append(list, 1) contains 1 }
+b = rule { append(list, 2) contains 2 }
+
+// Scenario 1: list becomes [1, 2]
+main = rule { a and b }
+
+// Scenario 2: list becomes [2, 1]
+main = rule { b and a }
+```
+
+This sort of side effect behavior introduces complex and difficult to track logical errors in programs.
+As a result, functions like this return `undefined` and modify values in-place.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/functions/compare.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/functions/compare.mdx
new file mode 100644
index 0000000000..ed55775454
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/functions/compare.mdx
@@ -0,0 +1,42 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Compare'
+sidebar_current: docs-funcs-compare
+description: The built-in function `compare` compares two values.
+layout: docs
+---
+
+# Builtin Function: compare
+
+**_compare(value1, value2)_**
+
+The built-in function `compare` compares two values. The only valid types that
+can be provided are integers, floats or strings. Strings are compared according
+to lexicographic ordering; which is comparable to alphabetical
+ordering, but also takes into account symbols.
+
+The following table provides an overview of the possible return values:
+
+| Result | Description |
+|--------|-------------|
+| -1 | `value1` is less than `value2` |
+| 0 | `value1` is equal to `value2` |
+| +1 | `value1` is greater than `value2` |
+
+## Examples
+
+```sentinel
+// ints
+compare(1, 4) // -1
+compare(1, 4) // 0
+compare(4, 1) // +1
+
+// floats
+compare(1.0, 4.0) // -1
+compare(1.0, 4.0) // 0
+compare(4.0, 1.0) // +1
+
+// strings
+compare("apple", "banana") // -1
+compare("apple", "apple") // 0
+compare("banana", "apple") // +1
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/functions/delete.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/functions/delete.mdx
new file mode 100644
index 0000000000..3b60803997
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/functions/delete.mdx
@@ -0,0 +1,25 @@
+---
+page_title: 'Sentinel Language - Builtin Function: delete'
+sidebar_current: docs-funcs-delete
+description: The built-in function `delete` deletes an element from a map.
+layout: docs
+---
+
+# Builtin Function: delete
+
+The built-in function `delete` deletes an element from a map.
+
+If the element to delete does not exist, the map is left unchanged.
+Calling `delete` on a value that is not a map or
+[undefined](/sentinel/language/undefined)
+is an immediate error. The result of `delete` is always `undefined`.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/functions/error.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/functions/error.mdx
new file mode 100644
index 0000000000..96d253b662
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/functions/error.mdx
@@ -0,0 +1,15 @@
+---
+page_title: 'Sentinel Language - Builtin Function: error'
+sidebar_current: docs-funcs-error
+description: >-
+ The built-in function `error` is used to exit immediately with an error
+ message.
+layout: docs
+---
+
+# Builtin Function: error
+
+The built-in function `error` is used to exit immediately with an error message.
+
+Please see the [page on errors](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/functions/index.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/functions/index.mdx
new file mode 100644
index 0000000000..4e12143ad0
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/functions/index.mdx
@@ -0,0 +1,13 @@
+---
+page_title: Sentinel Language - Builtin Functions
+sidebar_current: docs-funcs
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Language: Builtin Functions
+
+Builtin functions are
+[functions](/sentinel/language/functions)
+that are available in all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in function.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/functions/keys.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/functions/keys.mdx
new file mode 100644
index 0000000000..a96cafe897
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/functions/keys.mdx
@@ -0,0 +1,22 @@
+---
+page_title: 'Sentinel Language - Builtin Function: keys'
+sidebar_current: docs-funcs-keys
+description: The built-in function `keys` returns the keys of a map.
+layout: docs
+---
+
+# Builtin Function: keys
+
+The built-in function `keys` returns the keys of a map.
+
+`keys` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned keys are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/functions/length.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/functions/length.mdx
new file mode 100644
index 0000000000..8b874b14c4
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/functions/length.mdx
@@ -0,0 +1,23 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Length'
+sidebar_current: docs-funcs-length
+description: Builtin functions are functions that are available in all Sentinel policies.
+layout: docs
+---
+
+# Builtin Function: length
+
+The built-in function `length` returns the length of a collection or string.
+
+The length of a string is the number of bytes in the string. It is not
+necessarilly the number of characters. The length of a collection is the
+number of elements in that collection. The length of
+[undefined](/sentinel/language/undefined) is `undefined`.
+
+Examples:
+
+```sentinel
+length("foo") // 3
+length([1, 2, 3, 4]) // 4
+length({ "key": "value" }) // 1
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/functions/print.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/functions/print.mdx
new file mode 100644
index 0000000000..bcf441d09d
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/functions/print.mdx
@@ -0,0 +1,13 @@
+---
+page_title: 'Sentinel Language - Builtin Function: print'
+sidebar_current: docs-funcs-print
+description: The built-in function `print` is used for logging and debugging.
+layout: docs
+---
+
+# Builtin Function: print
+
+The built-in function `print` is used for logging and debugging.
+
+Please see the [page on logging](/sentinel/language/logging-errors)
+for more information and usage.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/functions/range.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/functions/range.mdx
new file mode 100644
index 0000000000..0f86bfc402
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/functions/range.mdx
@@ -0,0 +1,49 @@
+---
+page_title: 'Sentinel Language - Builtin Function: Range'
+sidebar_current: docs-funcs-range
+description: The built-in function `range` returns a list of numbers in a range.
+layout: docs
+---
+
+# Builtin Function: range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it
+defaults to `1`.
+
+Negative steps are permitted and count down from `start` towards `end`, instead
+of up.
+
+The range ends when the next value given by the sum of the last value and `step`
+would exceed `end`, regardless of if it has been reached.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4] - starts at 1 instead of 0
+range(1, 5, 2) // [1,3] - 3+2 does not satisfy r[-1] < end
+range(0, -3, -1) // [0,-1,-2] - example of negative range
+```
+
+Impossible ranges, or ranges where `start` will never reach `end` given `step`,
+yield an empty list.
+
+```
+range(1, 1) // [] - already starting at 1
+range(0, 1, -1) // [] - negative step will never reach 1
+```
+
+Supplying an undefined value to any argument of `range` yields an undefined
+value back.
+
+A range with a zero step is a runtime error.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/functions/values.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/functions/values.mdx
new file mode 100644
index 0000000000..8f98d3ab56
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/functions/values.mdx
@@ -0,0 +1,22 @@
+---
+page_title: 'Sentinel Language - Builtin Function: values'
+sidebar_current: docs-funcs-values
+description: The built-in function `values` returns the values of a map.
+layout: docs
+---
+
+# Builtin Function: values
+
+The built-in function `values` returns the values of a map.
+
+`values` called on any value other than a map or undefined will result
+in an immediate error.
+
+The returned values are not in any specified order since maps do not have an order.
+
+Examples:
+
+```sentinel
+data = { "a": 2, "b": 3 }
+values(data) // [3, 2]
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/imports/base64.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/imports/base64.mdx
new file mode 100644
index 0000000000..4e58429268
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/imports/base64.mdx
@@ -0,0 +1,46 @@
+---
+page_title: 'Import: base64'
+sidebar_current: docs-imports-base64
+description: The base64 import enables a Sentinel policy to encode and decode Base64 values.
+layout: docs
+---
+
+# Import: base64
+
+The base64 import enables a Sentinel policy to encode and decode Base64 values.
+
+### base64.encode(str)
+
+Encodes the string `str` into a Base64 encoded string, using the standard
+encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-4).
+
+```sentinel
+enc = base64.encode("foo") // "Zm9v"
+```
+
+### base64.decode(str)
+
+Decodes the Base64 encoded string `str`, returning the string result,
+using the standard encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-4).
+
+```sentinel
+dec = base64.decode("Zm9v") // "foo"
+```
+
+### base64.urlencode(str)
+
+Encodes the string `str` into a Base64 encoded string, using the alternate
+encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-5).
+
+```sentinel
+enc = base64.urlencode("foo") // "Zm9v"
+```
+
+### base64.urldecode(str)
+
+Decodes the Base64 encoded string `str`, returning the string result, using the
+alternate encoding as defined in [RFC 4648](https://tools.ietf.org/html/rfc4648#section-5).
+
+```sentinel
+dec = base64.urldecode("Zm9v") // "foo"
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/imports/collection/index.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/imports/collection/index.mdx
new file mode 100644
index 0000000000..ae6db20bbb
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/imports/collection/index.mdx
@@ -0,0 +1,207 @@
+---
+page_title: 'Import: collection'
+sidebar_current: docs-imports-collection
+description: The collection import provides useful helpers for working with maps and lists.
+layout: docs
+---
+
+# Import: collection
+
+The `collection` import provides helpers for working with [maps](/sentinel/language/maps) and [lists](/sentinel/language/lists).
+
+## filter
+
+**_filter(items, predicate)_**
+
+Calls [predicate](#predicates) for each element in collection, returning a list of elements that the
+predicate __does__ return true for.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| items | the list or map to perform the filter against. |
+| predicate | a [predicate](#predicates) function to call for each element in the collection. A response of `true` will add the element to the result list. |
+
+### Examples
+
+Return all even numbers:
+
+```sentinel playground
+import "collection"
+
+items = [2, 3, 4, 5, 6, 7, 8]
+result = collection.filter(items, func(el) {
+ return el % 2 is 0
+})
+main = result is [2, 4, 6, 8]
+```
+
+## find
+
+**_find(items, predicate)_**
+
+Find and return an element within a collection according to the provided [predicate](#predicates). If nothing is found, returns [undefined](/sentinel/language/undefined).
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| items | the list or map to perform the find against. |
+| predicate | a [predicate](#predicates) function to call for each element in the collection. A response of `true` will return the element and complete the find. |
+
+### Examples
+
+Find an element in a collection based on its value:
+
+```sentinel playground
+import "collection"
+
+items = ["foo", "bar", "qux"]
+item = collection.find(items, func(el) {
+ return el is "bar"
+})
+main = item is "bar"
+```
+
+Find an element in a collection based on its key:
+
+```sentinel playground
+import "collection"
+
+items = {"foo": 2, "bar": 4}
+item = collection.find(items, func(el, key) {
+ return key is "bar"
+})
+main = item is 4
+```
+
+## matches
+
+**_matches(items, partial)_**
+
+Compare each element in a collection against a partial map using deep comparison. Returns
+the list of elements that returned true for the partial comparison. If no matches are found, returns an empty list.
+
+### Arguments
+
+| Name | Description |
+|---------|-------------|
+| items | the list or map to perform the match against. |
+| partial | the map used for partial deep comparison against each element in the collection. |
+
+### Examples
+
+Return all items that contain `{"foo": {"bar": "wip"}}`:
+
+```sentinel playground
+import "collection"
+
+items = [
+ # This item should match
+ {
+ "foo": { "bar": "wip"},
+ "baz": "qux",
+ },
+ # This item will not match
+ {
+ "foo": "bar",
+ "baz": "bar",
+ },
+]
+result = collection.matches(items, {"foo": {"bar": "wip"}})
+main = result is [{"foo": {"bar": "wip"}, "baz": "qux"}]
+```
+
+## reduce
+
+**_reduce(items, accumulator[, initial])_**
+
+Call an accumulator function for each element in collection, supplying the
+previous accumulated value as the accumulation parameter.
+
+### Arguments
+
+| Name | Description |
+|-------------|-------------|
+| items | the list or map to perform the reduce against. |
+| accumulator | a function that is called for each element in the collection, used to accumulate the value. The first argument is the current accumulated value, the remaining arguments use the same rules as [predicate](#predicates) functions. |
+| initial | the initial value to use. It is an optional argument, and if not provided the first element in the collection is used as the initial value. |
+
+### Examples
+
+Reduce the collection by adding each element together:
+
+```sentinel playground
+import "collection"
+
+items = [1, 2, 3, 4, 5]
+result = collection.reduce(items, func(acc, el) {
+ return acc + el
+}, 0)
+main = result is 15
+```
+
+## reject
+
+**_reject(items, predicate)_**
+
+Calls [predicate](#predicates) for each element in collection, returning a list of elements that the
+predicate __does not__ return true for.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| items | the list or map to perform the reject against. |
+| predicate | a [predicate](#predicates) function to call for each element in the collection. A response of `false` will add the element to the result list. |
+
+### Examples
+
+Return all odd numbers by rejecting even ones:
+
+```sentinel playground
+import "collection"
+
+items = [2, 3, 4, 5, 6, 7, 8]
+result = collection.reject(items, func(el) {
+ return el % 2 is 0
+})
+main = result is [3, 5, 7]
+```
+
+## Predicates
+
+Some helpers accept a predicate function, which has the purpose of making an
+assertion. Each predicate may accept differing arguments and return differing
+types. If the collection is a list, the parameters will be the element and index.
+If the collection is a Map, the parameters will be the value and the key.
+
+### Examples
+
+A predicate for a list of items:
+
+```sentinel
+// including index
+func(item, index) {
+ return true
+}
+
+// excluding index
+func(item) {
+ return true
+}
+```
+
+A predicate for a map of items:
+```sentinel
+// including key
+func(value, key) {
+ return true
+}
+
+// excluding key
+func(value) {
+ return true
+}
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/imports/collection/lists.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/imports/collection/lists.mdx
new file mode 100644
index 0000000000..cf258a3956
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/imports/collection/lists.mdx
@@ -0,0 +1,211 @@
+---
+page_title: 'Import: collection/lists'
+sidebar_current: docs-imports-collection-lists
+description: The collection/lists import provides useful helpers for working with lists.
+layout: docs
+---
+
+# Import: collection/lists
+
+The `collection/lists` import provides helpers for working with [lists](/sentinel/language/lists).
+
+## concat
+
+**_concat(items, others[, ...additional])_**
+
+Join multiple lists together, returning the resulting list. This helper must
+have at least two arguments supplied. Order is important,
+as the order of arguments is the order that lists will be appended.
+
+### Arguments
+
+| Name | Description |
+|------------|-------------|
+| items | the first list to add to the resulting value |
+| others | the second list to add to the resulting value |
+| additional | any number of additional lists to add to the resulting value |
+
+### Examples
+
+Concatenate two lists:
+
+```sentinel playground
+import "collection/lists" as lists
+
+result = lists.concat([1], [2, 3])
+main = result is [1, 2, 3]
+```
+
+Concatenate many lists:
+```sentinel playground
+import "collection/lists" as lists
+
+result = lists.concat([1, 2], [10, 20], [100, 200])
+main = result is [1, 2, 10, 20, 100, 200]
+```
+
+## difference
+
+**_difference(items[, ...additional])_**
+
+Difference returns a new list with all values that exist in the first list
+that are not present in all remaining provided lists.
+
+### Arguments
+
+| Name | Description |
+|------------|-------------|
+| items | the first list, used as the source for ordering |
+| additional | any number of additional lists used to subtract from the source list |
+
+### Aliases
+
+__diff__
+
+### Examples
+
+Difference between two lists:
+
+```sentinel playground
+import "collection/lists" as lists
+
+result = lists.difference([1, 2], [2, 3])
+main = result is [1]
+```
+
+Difference between many lists:
+```sentinel playground
+import "collection/lists" as lists
+
+result = lists.difference([1, 2, 3, 4, 5], [2, 5], [1])
+main = result is [3, 4]
+```
+
+## intersection
+
+**_intersection(items[, ...additional])_**
+
+Intersection returns a list consisting of unique values that are present in all
+of the provided lists.
+
+### Arguments
+
+| Name | Description |
+|------------|-------------|
+| items | the first list to use for intersection |
+| additional | any number of additional lists used to intersect |
+
+### Examples
+
+Intersection between three lists:
+
+```sentinel playground
+import "collection/lists" as lists
+
+result = lists.intersection([1, 2], [2, 3], [3, 2])
+main = result is [2]
+```
+
+## sort
+
+**_sort(items, sort_function)_**
+
+Sort will sort a list of elements according to the provided sort function, returning
+a new, sorted list.
+
+### Arguments
+
+| Name | Description |
+|------|-------------|
+| items | the list to sort |
+| sort_function | the function that is used to perform the sort |
+
+### Sort Function
+
+The provided sort function accepts two arguments, which can be considered as the
+`current` and `next` item. The function should return one of the following values:
+
+| Result | Description |
+|--------|-------------|
+| -1 | `current` is less than `next` |
+| 0 | `current` is equal to `next` |
+| +1 | `current` is greater than `next` |
+
+The [compare](/sentinel/functions/compare) built-in method provides a way of
+performing comparisons against integers, floats or strings to return a supported
+value.
+
+### Examples
+
+Sort a list of words:
+
+```sentinel playground
+import "collection/lists" as lists
+
+items = ["zebra", "bat", "horse"]
+result = lists.sort(items, func(x, y) {
+ return compare(x, y)
+})
+main = result is ["bat", "horse", "zebra"]
+```
+
+Sort a list of objects by a key:
+
+```sentinel playground
+import "collection/lists" as lists
+
+items = [{"foo": 8}, {"foo": 4}, {"foo": 100}]
+result = lists.sort(items, func(x, y) {
+ return compare(x.foo, y.foo)
+})
+main = result is [{"foo": 4}, {"foo": 8}, {"foo": 100}]
+```
+
+## sum
+
+**_sum(items)_**
+
+Sum will add all elements within the provided list. The
+list must only contain integers or floats. The return value will always be a float.
+
+### Arguments
+
+| Name | Description |
+|------|-------------|
+| items | the list to sum |
+
+### Examples
+
+Perform a sum on a list of numbers:
+
+```sentinel playground
+import "collection/lists" as lists
+
+items = [2, 5, 10, 500]
+main = lists.sum(items) is 517
+```
+
+## union
+
+**_union(items[, ...additional])_**
+
+Union will return a list that contains unique values from all provided lists.
+
+### Arguments
+
+| Name | Description |
+|------------|-------------|
+| items | the first list of values |
+| additional | any number of additional lists of values |
+
+### Examples
+
+Perform a union on multiple lists:
+
+```sentinel playground
+import "collection/lists" as lists
+
+items = lists.union([1, 3], [2, 3, 5], [6, 2, 1])
+
+main = items is [1, 3, 2, 5, 6]
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/imports/collection/maps.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/imports/collection/maps.mdx
new file mode 100644
index 0000000000..d96bb15639
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/imports/collection/maps.mdx
@@ -0,0 +1,360 @@
+---
+page_title: 'Import: collection/maps'
+sidebar_current: docs-imports-collection-maps
+description: The collection/maps import provides useful helpers for working with maps.
+layout: docs
+---
+
+# Import: collection/maps
+
+The `collection/maps` import provides helpers for working with [maps](/sentinel/language/maps).
+
+## get
+
+**_get(object, path[, default])_**
+
+Get the value from the provided object using the [path](#paths).
+When the path is invalid or the object doesn't contain a value at the path, then the default is returned. The default return value is undefined.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the get against. |
+| path | a [path](#paths) to a key within object to retreive the value |
+| default | an optional value that will return if the path does not exist or returns undefined |
+
+### Examples
+
+Get a value from a simple object:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+item = maps.get(object, "foo")
+main = item is "bar"
+```
+
+Get a nested value from a complex object:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [{"bar": {"baz": 4}}]}
+item = maps.get(object, "foo.0.bar.baz")
+main = item is 4
+```
+
+Get a list of values using [splat](#splat):
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [{"bar": 4}, {"bar": 8}, {"bar": 45}]}
+item = maps.get(object, "foo.*.bar")
+main = item is [4, 8, 45]
+```
+
+Get an invalid path, providing a default:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {}
+item = maps.get(object, "foo", "bar")
+main = item is "bar"
+```
+
+Get an invalid path, not providing a default:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {}
+item = maps.get(object, "foo")
+main = item is not defined
+```
+
+## has
+
+**_has(object, path)_**
+
+Return a boolean value if the object has the path within its structure.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the has against. |
+| path | a [path](#paths) to a key within object to determine if it exists |
+
+### Examples
+
+An object has a valid key:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+main = maps.has(object, "foo")
+```
+
+An object does not have a valid key:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {}
+main = maps.has(object, "foo") is false
+```
+
+## omit
+
+**_omit(object, paths)_**
+
+Return a map, removing each path from the paths provided from the source object.
+The original structure of the map is maintained, as seen in the below examples.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the omit against. |
+| paths | a list of [paths](#paths) to be removed from the source object |
+
+### Examples
+
+Omitting a single value:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+main = maps.omit(object, ["foo"]) is {}
+```
+
+Omitting multiple values:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar", "baz": "qux", "nit": "nat"}
+main = maps.omit(object, ["foo", "baz"]) is {"nit": "nat"}
+```
+
+Omitting deep values:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {
+ "foo": "bar",
+ "baz": [
+ { "qux": 4 },
+ { "qux": 10 }
+ ],
+ "nit": {
+ "nat": "pak"
+ }
+}
+main = maps.omit(object, ["baz.1.qux", "nit.nat"]) is {"foo": "bar", "baz": [{"qux": 4}]}
+```
+
+## pick
+
+**_pick(object, paths)_**
+
+Return a map consisting of each value found in object from the paths provided.
+The original structure of the map is maintained, as seen in the below examples.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the pick against. |
+| paths | a list of [paths](#paths), each used to select a value from the object. |
+
+### Examples
+
+Picking a single value:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+main = maps.pick(object, ["foo"]) is {"foo": "bar"}
+```
+
+Picking multiple values:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar", "baz": "qux", "nit": "nat"}
+main = maps.pick(object, ["foo", "baz"]) is {"foo": "bar", "baz": "qux"}
+```
+
+Picking deep values:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {
+ "foo": "bar",
+ "baz": [
+ { "qux": 4 },
+ { "qux": 10 }
+ ],
+ "nit": {
+ "nat": "pak"
+ }
+}
+main = maps.pick(object, ["baz.1.qux", "nit.nat"]) is {"baz": [{"qux": 10}], "nit": {"nat": "pak"}}
+```
+
+## set
+
+**_set(object, path, value)_**
+
+Return a new map, assigning the provided value to the provided path. It will
+not modify the provided map in place.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the set against. |
+| path | the [path](#paths) to assign value |
+| value | the value to be assigned |
+
+### Examples
+
+Set a value on a simple path:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+object = maps.set(object, "foo", "qux")
+main = object is {"foo": "qux"}
+```
+
+Set a value on a deep path:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {}
+object = maps.set(object, "foo.bar.baz", 10)
+main = object is {"foo": { "bar": { "baz": 10 } } }
+```
+
+Set a value on an index in a list:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [ 2, 5 ] }
+object = maps.set(object, "foo.0", 10)
+main = object is {"foo": [ 10, 5 ] }
+```
+
+Set a value on every key within a list key:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [ { "bar": true }, { "bar": false } ] }
+object = maps.set(object, "foo.*.bar", true)
+main = object is {"foo": [ { "bar": true }, { "bar": true } ] }
+```
+
+## unset
+
+**_unset(object, path)_**
+
+Return a new map, removing the provided path from the source object. It will
+not modify the provided map in place.
+
+### Arguments
+
+| Name | Description |
+|-----------|-------------|
+| object | the map to perform the unset against. |
+| path | the [path](#paths) to remove |
+
+### Examples
+
+Unset on a simple path:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": "bar"}
+object = maps.unset(object, "foo")
+main = object is {}
+```
+
+Unset on a deep path:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": { "bar": { "baz": 10, "qux": 15 } } }
+object = maps.unset(object, "foo.bar.baz")
+main = object is {"foo": { "bar": { "qux": 15 } } }
+```
+
+Unset an index in a list:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [ 2, 5 ] }
+object = maps.unset(object, "foo.0")
+main = object is {"foo": [ 5 ] }
+```
+
+Unset a list:
+
+```sentinel playground
+import "collection/maps" as maps
+
+object = {"foo": [ { "bar": true }, { "bar": false } ] }
+object = maps.unset(object, "foo.*", true)
+main = object is {"foo": [] }
+```
+
+## Paths
+
+Map helpers often receive a **path** argument that allows for looking up a nested
+key. Generally speaking, a path is a series of keys separated by `.`, however there
+are some additional capabilites that need to be explained.
+
+### Lists
+
+When traversing a nested map that contains a list, a specific index can be retrieved
+by providing the index as the part of the path.
+
+In the following code sample, the path will first enter the key `"foo"` within the
+map, followed by entering the first index of the list.
+
+```sentinel
+path = "foo.0"
+object = {"foo": [1]}
+```
+
+### Splat
+
+To provide advanced capabilities when using paths, you can also use the splat (`*`)
+operator to iterate through **all** elements in a list, with all parts of the path
+following the splat occuring on each entry.
+
+In the following code sample, the path will first enter the key `"foo"` within the map.
+It will then enter each item in the list, entering the `"bar"` key for each nested object.
+
+```sentinel
+path = "foo.*.bar"
+object = {"foo": [{"bar": 1}, {"bar": 2}]}
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/imports/decimal.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/imports/decimal.mdx
new file mode 100644
index 0000000000..a6633a38e0
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/imports/decimal.mdx
@@ -0,0 +1,318 @@
+---
+page_title: 'Import: decimal'
+sidebar_current: docs-imports-decimal
+description: |-
+ The decimal import provides functions for operating on numbers represented
+ as decimals.
+layout: docs
+---
+
+# Import: decimal
+
+The decimal import provides functions for operating on numbers represented
+as decimals.
+
+Binary floating-point numbers provided by the `float` type are unable to
+fully represent numbers like `1.1` and `2.2`. The decimal import can
+represent these numbers exactly, and so should be used when exactness is
+required.
+
+Decimals are created through the `new` function, which takes any type that
+can represent a number. Arguments to the decimal operators can also take
+a value of any of these types. The full list is:
+
+- float
+- int
+- string
+- existing decimal value
+
+### decimal.infinity(sign)
+
+Creates an infinite-value decimal. Infinite-value decimals are always larger
+or smaller, depending on sign, than any non-infinite decimals.
+
+```sentinel
+decimal.infinity(1) // +Infinity
+decimal.infinity(-1) // -Infinity
+```
+
+Also available as `decimal.inf`.
+
+### decimal.nan
+
+A decimal representing a "not-a-number" value. NaNs are not equal to any
+other number, even themselves.
+
+```sentinel
+decimal.new(-1).sqrt() // NaN
+decimal.new(0).mul(decimal.inf(1)) // NaN
+decimal.nan.is(decimal.nan) // false
+```
+
+### decimal.is_infinite(d)
+
+Evaluates to true if the decimal is infinite.
+
+```sentinel
+decimal.is_infinite(100) // false
+decimal.is_infinite("Infinity") // true
+```
+
+Also available as `decimal.is_inf`, `decimal.isinf` and `decimal.isinfinite`.
+
+### decimal.is_nan(d)
+
+Evaluates to true if the decimal is not-a-number.
+
+```sentinel
+decimal.is_nan("NaN") // true
+```
+
+Also available as `decimal.isnan`.
+
+### decimal.new(v)
+
+Constructs a decimal from another value.
+
+```sentinel
+decimal.new("1.1") // Constructs a decimal from a string.
+decimal.new(2.2) // Constructs a decimal from a float.
+decimal.new(3) // Constructs a decimal from an integer.
+decimal.new("1.1234E+400") // Constructs a decimal from a string using E-notation.
+```
+
+## Type: number
+
+Numbers are represented internally by a sign, coefficient, and exponent.
+The value is calculated with the formula `sign * coefficient * 10^exponent`.
+Exponent is limited to 32 bits, and the coefficient is limited by the total
+precision.
+
+The maximum precision a number can represent is 100 digits. This includes
+both before and after the decimal place.
+
+### number.string
+
+A string representation of the decimal.
+
+```sentinel
+decimal.new(1.5).string // 1.5
+```
+
+### number.sign
+
+A number between `-1` and `+1` representing the sign of the decimal.
+
+```sentinel
+decimal.new(1.5).sign // 1
+```
+
+### number.coefficient
+
+The coefficient component of the decimal.
+
+```sentinel
+decimal.new(1.5).coefficient // 15
+```
+
+### number.exponent
+
+The exponent component of the decimal.
+
+```sentinel
+decimal.new(1.5).exponent // -1
+```
+
+### number.float
+
+A floating-point representation of the decimal.
+
+```sentinel
+decimal.new(1.5).float // 1.500000
+```
+
+### number.int
+
+An integer representation of the decimal. This always rounds down to the nearest integer.
+
+```sentinel
+decimal.new(1.5).int // 1
+```
+
+### number.is(v)
+
+Test for equality with another value.
+
+```sentinel
+decimal.new(1.5).is(1) // false
+```
+
+### number.is_not(v)
+
+Test for inequality with another value.
+
+```sentinel
+decimal.new(1.5).is_not(1) // true
+```
+
+### number.less_than(v)
+
+Test that the decimal is less than another value.
+Can also be called as `number.lt(v)`
+
+```sentinel
+decimal.new(1.5).less_than(1) // false
+```
+
+### number.less_than_or_equals(v)
+
+Test that the decimal is less than or equals to another value.
+Can also be called as `number.lte(v)`
+
+```sentinel
+decimal.new(1.5).less_than_or_equals(1) // false
+```
+
+### number.greater_than(v)
+
+Test that the decimal is greater than another value.
+Can also be called as `number.gt(v)`
+
+```sentinel
+decimal.new(1.5).greater_than(1) // true
+```
+
+### number.greater_than_or_equals(v)
+
+Test that the decimal is greater than or equals to another value.
+Can also be called as `number.gte(v)`
+
+```sentinel
+decimal.new(1.5).greater_than_or_equals(1) // true
+```
+
+### number.add(v)
+
+Add another number to the decimal.
+
+```sentinel
+decimal.new(1.5).add(1) // 2.5
+```
+
+### number.subtract(v)
+
+Subtract another number from the decimal.
+Can also be called as `number.sub(v)`
+
+```sentinel
+decimal.new(1.5).subtract(1) // 0.5
+```
+
+### number.multiply(v)
+
+Multiply the decimal by a number.
+Can also be called as `number.mul(v)`
+
+```sentinel
+decimal.new(1.5).multiply(2) // 3.0
+```
+
+### number.divide(v)
+
+Divide the decimal by a number.
+Can also be called as `number.div(v)`
+
+```sentinel
+decimal.new(3.0).divide(1.5) // 2
+```
+
+### number.modulo(v)
+
+Find the remainder after dividing the decimal by a number.
+Can also be called as `mod`, `remainder`, or `rem`.
+
+```sentinel
+decimal.new(1.5).modulo(1) // 0.5
+```
+
+### number.power(v)
+
+Raise the decimal to a power.
+Can also be called as `number.pow(v)`
+
+```sentinel
+decimal.new(1.5).power(2) // 2.25
+```
+
+### number.exp()
+
+The natural exponent of the decimal. This calculates `e^number`.
+
+```sentinel
+decimal.new(1.5).exp() // 4.4816...
+```
+
+### number.loge()
+
+The natural logarithm of the decimal.
+Can also be called as `number.ln()`
+
+```sentinel
+decimal.new(1.5).loge() // 0.4054...
+```
+
+### number.log10()
+
+The base-10 logarithm of the decimal.
+Can also be called as `number.log()`
+
+```sentinel
+decimal.new(1.5).log10() // 0.1760...
+```
+
+### number.square_root()
+
+The square root of the decimal.
+Can also be called as `number.sqrt()`
+
+```sentinel
+decimal.new(1.5).square_root() // 1.2247...
+```
+
+### number.ceiling()
+
+Round the decimal to the smallest integer greater or equal to itself.
+Can also be called as `number.ceil()`
+
+```sentinel
+decimal.new(1.5).ceiling() // 2
+```
+
+### number.floor()
+
+Round the decimal to the largest integer less than or equal to itself.
+
+```sentinel
+decimal.new(1.5).floor() // 1
+```
+
+### number.absolute()
+
+The absolute value of the decimal.
+Can also be called as `number.abs()`
+
+```sentinel
+decimal.new(1.5).absolute() // 1.5
+decimal.new(-1.5).absolute() // 1.5
+```
+
+### number.negate()
+
+The negated value of the decimal. A positive decimal will become negative,
+and a negative decimal will become positive.
+Can also be called as `number.neg()`
+
+```sentinel
+decimal.new(1.5).negate() // -1.5
+decimal.new(-1.5).negate() // 1.5
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/imports/http.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/imports/http.mdx
new file mode 100644
index 0000000000..f94ae98c27
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/imports/http.mdx
@@ -0,0 +1,370 @@
+---
+page_title: 'Import: http'
+sidebar_current: docs-imports-http
+description: The http import enables the use of HTTP-accessible data from outside the runtime in Sentinel policy rules.
+layout: docs
+---
+
+# Import: http
+
+The http import enables the use of HTTP-accessible data from outside
+the runtime in Sentinel policy rules.
+
+The simplest example of the import in use would be:
+
+```sentinel
+import "http"
+
+resp = http.get("https://example.hashicorp.com")
+main = rule { resp.body contains "something" }
+```
+
+By default, any request response that is not a successful "200 OK" HTTP
+response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information and
+how to customize this behavior.
+
+For more advanced requests which require setting request headers, use a
+[`request` type](#type-request):
+
+```sentinel
+import "http"
+import "json"
+
+param token
+
+req = http.request("https://example.hashicorp.com").with_header("Authorization", "token "+token)
+resp = json.unmarshal(http.get(req).body)
+main = rule { resp["some_key"] is true }
+```
+
+The `with_header` function above returns the `request` object with the
+`Authorization` HTTP header set to value of the variable `some_value`. Many
+of the methods within the http import API are designed around this concept
+of method chaining, allowing for complex building of HTTP requests
+encapsulated in functions. The following is an idiomatic example further
+demonstrating this pattern:
+
+```sentinel
+import "http"
+import "json"
+
+param github_user
+param github_token
+
+client = func(req) {
+ return http.with_timeout(5).with_retries(3)
+}
+
+github_request = func(path) {
+ full_url = "https://api.github.com" + path
+ return http.request(full_url).
+ with_basic_auth(github_user, github_token).
+ with_header("Accept", "application/vnd.github.v3+json").
+ with_header("Custom", "foo"),
+}
+
+default_response = client.get(github_request("/example"))
+
+# Override the Custom header for this single request
+custom_header_response = json.unmarshal(
+ client.get(
+ github_request("/example").with_header("Custom", "bar"),
+ ),
+)
+
+# Override the timeout value for a known slower request
+slow_response = json.unmarshal(
+ client.with_timeout(15).get(github_request("/slow-endpoint")),
+)
+
+some_key_is_true = rule { json.unmarshal(default_response.body)["some_key"] is true }
+body_contains_text = rule { custom_header_response.body contains "something" }
+response_is_ok = rule { slow_response.status_code is 200 }
+
+main = rule { some_key_is_true and body_contains_text and response_is_ok }
+```
+
+### http.client
+
+Retrieve the base HTTP client with default settings. The returned client may be
+configured by calling configuration functions on it; all of these configuration
+functions on are also aliased as top-level functions in this namespace. See
+[`client`](#type-client) below.
+
+### http.request(url)
+
+Create a new [`request`](#type-request) to the specified `url` (string). A
+`request` type enables customization of headers and other concerns before then
+providing the constructed `request` to [`get()`](#client-get-url_or_request),
+[`post()`](#client-post-url_or_request) or [`send()`](#client-send-request).
+
+```sentinel
+req = http.request("example.hashicorp.com/foo").with_header("Foo", "bar")
+resp = http.get(req)
+```
+
+### http.methodPost
+
+Returns a string containing the accepted verb for POST requests, `"POST"`.
+
+### http.methodGet
+
+Returns a string containing the accepted verb for GET requests, `"GET"`.
+
+## Type: client
+
+All of the following configuration functions on the default client are also
+aliased as top-level functions in the import. This allows a short-hand (and
+idiomatic) way of configuring a new client without having to directly access
+`http.client` each time:
+
+```sentinel
+# The following two lines are semantically the same:
+resp = http.client.with_timeout(5).get(url)
+resp = http.with_timeout(5).get(url)
+```
+
+### url_or_request
+
+The [`get()`](#client-get-url_or_request) and [`post()`](#client-post-url_or_request)
+functions accept either a URL string or a request object, noted as
+`url_or_request` throughout the documentation.
+
+When `url_or_request` is a string, a request is made with the URL
+specified by the string. To customize HTTP headers and other settings, pass
+a request. By default, a request has a timeout value of 10 seconds and 1
+retry. These settings can be adjusted with [`with_timeout()`](#clientwith_timeoutinteger) and
+[`with_retries()`](#clientwith_retriesinteger), respectively.
+
+### Redirects
+
+If the response is one of the following redirect codes, the client follows the
+redirect, up to a maximum of 10 redirects:
+
+- 301 (Moved Permanently)
+- 302 (Found)
+- 303 (See Other)
+- 307 (Temporary Redirect)
+- 308 (Permanent Redirect)
+
+If the redirect limit is exceeded, the result is a policy error.
+
+By default, after any redirects are followed (up to the maximum), any request
+response that is not a successful "200 OK" response causes a policy error. See
+[`accept_status_codes`](#client-accept_status_codes-list) for more information
+and how to customize this behavior.
+
+### client.get(url_or_request)
+
+Issue a GET request with a URL string or a `request` type. Returns a `response`
+type.
+
+```sentinel
+import "http"
+
+resp = http.get("https://example.hashicorp.com")
+main = rule { resp.body contains "something" }
+```
+
+### client.post(url_or_request)
+
+Issue a POST request with a URL string or a `request` type. Returns a `response`
+type.
+
+```sentinel
+import "http"
+
+req = http.request("https://example.hashicorp.com")
+ .with_body(json.marshal({"foo": "bar"}))
+resp = http.post(req)
+main = rule { resp.body contains "something" }
+```
+
+### client.send(request)
+
+Make a request using the supplied `request` type. Returns a `response`.
+
+```sentinel
+import "http"
+
+req = http.request("https://example.hashicorp.com")
+resp = http.send(req)
+main = rule { resp.body contains "something" }
+```
+
+### client.accept_status_codes(list)
+
+Configures a client to not automatically cause a policy failure if
+the resulting response's status code is in `list`. Any status code received
+that is not present in this list will trigger a runtime error.
+
+By default, any request response that is not a successful "200 OK" response
+causes a policy error. This is for convenience, as the most common scenario
+by far is to receive a response and immediately signal a policy error if the
+status code is not 200. Use this scoping to specify a list of non-redirect
+HTTP codes that you wish to accept without error and evaluate the response
+yourself.
+
+Redirects are not considered final status codes in this context. That is,
+redirects are always followed up to the maximum allowed value (see [`Redirects`](#redirects))
+and the final response code in the redirect chain is validated against
+`list`.
+
+```sentinel
+resp = http.accept_status_codes([200, 404]).get(url)
+main = rule { resp.status_code is 404 }
+```
+
+### client.accepted_status_codes
+
+Returns a list of the client's accepted status codes.
+
+```sentinel
+client = http.accept_status_codes([200, 404])
+main = rule { client.accepted_status_codes contains 404 }
+```
+
+### client.with_retries(integer)
+
+Sets the maximum number of retries, specified by `integer`, that should be
+attempted on an unsuccessful request. An unsuccessful request is considered
+to be any 5xx response except `501 (Not Implemented)` or a request timeout.
+By default, one retry is attempted.
+
+The maximum number of retries is 10, for a total of 11 request attempts.
+When a request exceeds the maximum number of retries without a response, the
+result is a policy error. Specifying a number larger than this will result
+in a runtime error.
+
+Between each retry, an exponentially increasing delay is observed, allowing
+time for the server to recover. This delay starts at 1 second for the first
+retry and increases each attempt thereafter. The maximum delay for a single
+attempt is 30 seconds.
+
+Retries can be disabled by specifying 0.
+
+### client.retries
+
+Returns the client's configured number of retries as an integer.
+
+### client.with_timeout(integer)
+
+Sets the request timeout to the number of seconds indicated by `integer`.
+
+Each individual request performed by the HTTP client will be limited by the
+given request timeout. This timeout includes connection time, any redirects,
+and reading the response body.
+
+A maximum of 30 seconds is allowed. Specifying a number larger than this
+will result in a runtime error.
+
+By default, requests will time out after 10 seconds.
+
+### client.timeout
+
+Returns the client's configured timeout in whole seconds as an integer.
+
+### client.without_certificate_verification()
+
+Skips verification of TLS certificates during secure HTTP operations,
+allowing for requests to `https://` endpoints which are secured with a TLS
+certificate signed by an untrusted certificate authority. Takes no arguments.
+
+By default, the HTTP client will trigger a runtime error if a TLS certificate
+presented by a server is from an untrusted certificate authority.
+
+Do not change this setting unless you are aware of the risks involved.
+Without verification, TLS accepts any certificate presented by the server
+and any host name in that certificate. In this mode, TLS is susceptible to
+man-in-the-middle attacks. This option should only be used for testing or in
+trusted networks with self-signed certificates.
+
+```sentinel
+http.without_certificate_verification().get(url)
+```
+
+### client.certificate_verification
+
+Returns true if the client requires TLS certificates to be verifiable.
+
+## Type: request
+
+### request.url
+
+Returns the request URL as a string.
+
+### request.headers
+
+Returns the configured request headers as a string-keyed map of strings.
+
+### request.body
+
+Returns the configured body as a string.
+
+### request.with_header(key, value)
+
+Returns a `request` with the header `key` (string) set to `value` (string).
+
+Values are overridden on subsequent calls to the same `key`. To specify
+multiple values, use the existing value when setting the new one.
+
+```sentinel
+original = http.request("http://example.hashicorp.com").with_header("Foo", "bar")
+overridden = original.with_header("Foo", "baz")
+multi_value = original.with_header("Foo", original.headers["Foo"] + ",baz")
+
+main = rule {
+ original.headers["Foo"] is "bar" and
+ overridden.headers["Foo"] is "baz" and
+ multi_value.headers["Foo"] is "bar,baz"
+}
+```
+
+### request.with_basic_auth(username, password)
+
+Returns a `request` with the `Authorization` header set to use HTTP Basic
+Authentication with the provided username and password.
+
+Note that with HTTP Basic Authentication the provided username and password
+are encoded, but not encrypted.
+
+### request.with_body(body)
+
+Returns a `request` with the `body` set. This value will then be sent as a body
+as part of the request. Commonly used along with the [`post()`](#client-post-url_or_request) function.
+
+```sentinel
+req = http.request("http://example.hashicorp.com")
+ .with_header("Content-Type", "application/json")
+ .with_body(json.marshal({ "foo": "bar" }))
+
+resp = http.post(req)
+```
+
+## Type: response
+
+### response.status_code
+
+The response status code as an integer, e.g. `200`.
+
+### response.headers
+
+The response headers as a map.
+
+### response.body
+
+The response body as a string. Callers should unmarshal the content to a useful
+representation as necessary based on the content type. For example, if the
+response is in JSON:
+
+```sentinel
+import "http"
+import "json"
+
+# This example endpoint returns {"some_key": true}
+req = http.request("https://example.hashicorp.com/some-json")
+
+resp = json.unmarshal(http.get(req).body)
+main = rule { keys(resp) contains "some_key" and resp["some_key"] is true }
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/imports/index.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/imports/index.mdx
new file mode 100644
index 0000000000..e45bf81b81
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/imports/index.mdx
@@ -0,0 +1,18 @@
+---
+page_title: Sentinel Language - Standard Imports
+sidebar_current: docs-imports
+description: >-
+ Standard Imports are the built-in imports that are available to all Sentinel
+ policies.
+layout: docs
+---
+
+# Standard Imports
+
+Standard Imports are the built-in [imports](/sentinel/language/imports)
+that are available to all Sentinel policies. Use the navigation to the left
+to view the documentation for each built-in import.
+
+Sentinel-embedded applications can choose to allow or deny certain
+standard imports. Please reference the documentation for the Sentinel-enabled
+application you're using to determine if all standard imports are available.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/imports/json.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/imports/json.mdx
new file mode 100644
index 0000000000..deb10ae98a
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/imports/json.mdx
@@ -0,0 +1,48 @@
+---
+page_title: 'Import: json'
+sidebar_current: docs-imports-json
+description: The json import enables a Sentinel policy to parse and access a JSON document.
+layout: docs
+---
+
+# Import: json
+
+The json import enables a Sentinel policy to parse and access a JSON
+document.
+
+### json.unmarshal(obj)
+
+Unmarshals the JSON object `obj` into a native Sentinel structure.
+
+All native JSON types can be represented perfectly as Sentinel native types.
+
+The `obj` argument must be a string.
+
+```sentinel
+// Typically the input for this would come from an external source.
+config = json.unmarshal("{ \"foo\": 42 }")
+config.foo // 42
+config.bar // undefined (as usual for accessing a non-existent map key)
+```
+
+### json.marshal(obj)
+
+Marshals the Sentinel object `obj` into a JSON object encoded as a string.
+
+Sentinel's functions cannot be natively encoded, and will cause an error if
+it is present within the provided object.
+
+Sentinel's `undefined` cannot be natively encoded as JSON. If `undefined`
+is supplied directly to `marshal`, it will return `undefined`. If `undefined`
+is present within any map or list within the provided object, the associated
+element will be removed prior to performing the JSON encoding.
+
+### json.valid(obj)
+
+Validates the object `obj` is valid JSON format.
+
+The `obj` argument must be a string.
+
+```sentinel
+json.valid("{ \"foo\": 42 }") // true
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/imports/runtime.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/imports/runtime.mdx
new file mode 100644
index 0000000000..6493bbbb22
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/imports/runtime.mdx
@@ -0,0 +1,16 @@
+---
+page_title: 'Import: runtime'
+sidebar_current: docs-imports-runtime
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: runtime
+
+The runtime import contains various information about the Sentinel runtime. It
+can be used to get information about the runtime as it's embedded in the CLI or
+a specific integration, such as validating a specific runtime version.
+
+### runtime.version
+
+Returns the version of Sentinel within this implementation.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/imports/sockaddr.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/imports/sockaddr.mdx
new file mode 100644
index 0000000000..a6842ba5f0
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/imports/sockaddr.mdx
@@ -0,0 +1,41 @@
+---
+page_title: 'Import: sockaddr'
+sidebar_current: docs-imports-sockaddr
+description: The sockaddr import makes working with IP addresses easy.
+layout: docs
+---
+
+# Import: sockaddr
+
+The sockaddr import makes working with IP addresses easy.
+
+### sockaddr.is_contained(outer, inner)
+
+The is_contained function returns true if `inner` is an address that
+is contained within `outer`.
+
+```sentinel
+sockaddr.is_contained("192.168.0.0/24", "192.168.0.32") // true
+sockaddr.is_contained("192.168.0.0/24", "192.174.1.1") // false
+```
+
+### sockaddr.is_equal(addr1, addr2)
+
+The is_equal function returns true if addr1 is equal to addr2.
+
+```sentinel
+sockaddr.is_equal("192.168.12.24", "192.168.12.24") // true
+```
+
+### sockaddr.is_ipv4(addr)
+
+The is_ipv4 function returns true if addr is an IPv4 address.
+
+```sentinel
+sockaddr.is_ipv4("192.168.12.24") // true
+sockaddr.is_ipv4("2001:0db8:85a3:0000:0000:8a2e:0370:7334") // false
+```
+
+### sockaddr.is_ipv6(addr)
+
+The is_ipv6 function returns true if addr is an IPv6 address.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/imports/strings.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/imports/strings.mdx
new file mode 100644
index 0000000000..e1ba9a9e2a
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/imports/strings.mdx
@@ -0,0 +1,150 @@
+---
+page_title: 'Import: strings'
+sidebar_current: docs-imports-strings
+description: The strings import exposes common string operations.
+layout: docs
+---
+
+# Import: strings
+
+The strings import exposes common string operations.
+
+### strings.has_prefix(s, prefix)
+
+Returns true if `s` starts with `prefix`.
+
+```sentinel
+strings.has_prefix("billing-id", "billing-") // true
+strings.has_prefix("bill-id", "billing-") // false
+```
+
+### strings.has_suffix(s, suffix)
+
+Returns true if `s` ends with `suffix`.
+
+```sentinel
+strings.has_suffix("billing-id", "id") // true
+strings.has_suffix("billing-name", "id") // false
+```
+
+### strings.join(a, sep)
+
+Joins a list with the string supplied by `sep`.
+
+Multi-dimensional lists are supported, and non-string primitive
+types will be converted to their string equivalents. Maps and
+other complex non-list types are not supported.
+
+```sentinel
+strings.join(["foo", "bar", "baz"], ".") // "foo.bar.baz"
+strings.join([["foo", "bar"], "baz"], ".") // "foo.bar.baz"
+strings.join(["a", 1, 1.01, true], ",") // "a,1,1.01,true"
+```
+
+### strings.replace(s, old, new, times)
+
+Replace the substring `old` with the substring `new` in the string `s`
+by how many times the int parameter `times` is specified.
+If the string `s` doesn't the substrings or if `times` is zero,
+then the string is returned without any changes applied.
+
+```
+strings.replace("foobar", "foo", "bar") // "barbar"
+strings.replace("foofoofoobar", "foo", "bar", 1) // "barfoofoobar"
+strings.replace("foofoofoobar", "foo", "bar", 2) // "barbarfoobar"
+strings.replace("foobar", "test", "bar") // "foobar"
+strings.replace("foobar", "foo", "bar", 0) // "foobar"
+```
+
+### strings.trim(s, cutset)
+
+Trim the leading and trailing characters in `cutset` from the string `s`.
+If the string doesn't have the cutset, then the string is returned
+unmodified.
+
+```
+strings.trim("iiifoobariii", "i") // "foobar"
+strings.trim("!1!bar foo!!1", "!1") // "bar foo"
+```
+
+### strings.trim_left(s, cutset)
+
+Trim the leading characters contained in `cutset` from the string `s`.
+If the string doesn't have the cutset, then the string is returned
+unmodified.
+
+```sentinel
+strings.trim_left("aaaaaaaafoo", "a") // "foo"
+strings.trim_left("!!!bar!!!", "!") // "bar!!!"
+```
+
+### strings.trim_right(s, cutset)
+
+Trim the trailing characters contained in the `cutset` from the
+string `s`. If the string doesn't have the cutset, then the string
+is returned unmodified.
+
+```sentinel
+strings.trim_right("foo_bar...", ".") // "foo_bar"
+strings.trim_right("billing---","-") // "billing"
+```
+
+### strings.trim_space(s)
+
+Trim leading and trailing white space from the string `s`. If the
+string doesn't have any surrounding white space, then the string is
+returned unmodified.
+
+```sentinel
+strings.trim_space(" foo ") // "foo"
+strings.trim_space(" bar foo ") // "bar foo"
+```
+
+### strings.trim_prefix(s, prefix)
+
+Trim the prefix from the string `s`. If the string doesn't have the
+prefix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_prefix("billing-id", "billing-") // "id"
+strings.trim_prefix("bill-id", "billing-") // "bill-id"
+```
+
+### strings.trim_suffix(s, suffix)
+
+Trim the suffix from the string `s`. If the string doesn't have the
+suffix, then the string is returned unmodified.
+
+```sentinel
+strings.trim_suffix("billing-id", "-id") // "billing"
+strings.trim_suffix("bill-id", "-foo") // "bill-id"
+```
+
+### strings.to_lower(s)
+
+Lowercase the string s.
+
+```sentinel
+strings.to_lower("FoO") // "foo"
+```
+
+### strings.to_upper(s)
+
+Uppercase the string s.
+
+```sentinel
+strings.to_upper("FoO") // "FOO"
+```
+
+### strings.split(s, sep)
+
+Split the string 's' via a substring 'sep'. If 'sep' is not contained in
+'s', the return value will be a single-element list containing only 's'.
+As a special-case, if the input is already a list, it will be returned
+as-is. This allows calling the split function when not knowing if the input
+is already a list or not.
+
+```sentinel
+strings.split("foo,bar,baz", ",") // ["foo", "bar", "baz"]
+strings.split(["foo", "bar", "baz"], ",") // ["foo", "bar", "baz"]
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/imports/time.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/imports/time.mdx
new file mode 100644
index 0000000000..3a675e2bbd
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/imports/time.mdx
@@ -0,0 +1,257 @@
+---
+page_title: 'Import: time'
+sidebar_current: docs-imports-time
+description: The time import provides access to the execution time and time functions.
+layout: docs
+---
+
+# Import: time
+
+The time import provides access to the execution time and time functions.
+
+During the execution of a policy the time is fixed to the moment of
+execution. This gives the illusion that a policy instantly executes at a
+single moment of time.
+
+The import uses Coordinated Universal Time (UTC).
+
+As an example of this, if a policy accessed `time.now.second` multiple times,
+for each access the same value would be returned even if they are several seconds apart.
+This is the execution `timespace`, which is mapped to `time.now`.
+
+It is also possible to run functions on a custom time by using the
+`time.load()` function to create a new timespace from the specified value.
+See the documentation for available actions on timespaces.
+
+Times specified as the reference time or via the `time.load()` function can
+be specified in a `timeish` fashion. This is either one of:
+
+- The number of seconds since the Unix epoch (Thursday, 1 January 1970,
+ 00:00:00 UTC),
+- A timestamp matching the format outlined in RFC3339, either in the
+ format `2006-01-02T15:04:05+07:00`, which includes a timezone offset, or
+ `2006-01-02T15:04:05Z`, which indicates UTC.
+
+### time.now
+
+Create a `timespace` locked either to execution time or, if set, the
+reference time. In a given execution, this will always produce the same
+value.
+
+```sentinel
+time.now // {"day": 17, "hour": 23, "minute": 19, "month": 11 ... }
+```
+
+### time.load(timeish)
+
+Create a timespace using the given value. Please see the import
+documentation for valid values for "timeish".
+
+```sentinel
+time.load("2019-11-16T01:20:30Z") // {"day": 16, "hour": 1, "minute": 20, "month": 11 ... }
+```
+
+### time.nanosecond
+
+A nanosecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.nanosecond * 1) // 2019-11-16T01:20:30.000000001Z
+```
+
+### time.microsecond
+
+A microsecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.microsecond * 1) // 2019-11-16T01:20:30.000001Z
+```
+
+### time.millisecond
+
+A millisecond duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.millisecond * 1) // 2019-11-16T01:20:30.001Z
+```
+
+### time.second
+
+A second duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.second * 1) // 2019-11-16T01:20:31Z
+```
+
+### time.minute
+
+A minute for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.minute * 1) // 2019-11-16T01:21:30Z
+```
+
+### time.hour
+
+An hour duration unit for use with the `add` timespace function.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+## Type: timespace
+
+### timespace.hour
+
+The hour from 0 to 23.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").hour // 1
+```
+
+### timespace.minute
+
+The minute from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").minute // 20
+```
+
+### timespace.second
+
+The second from 0 to 59.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").second // 30
+```
+
+### timespace.day
+
+The day of the month, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").day // 16
+```
+
+### timespace.month
+
+The month of the year as an integer, starting from 1.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month // 11
+```
+
+### timespace.month_name
+
+The name of the month of the year, in English. Example: `January`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").month_name // November
+```
+
+### timespace.weekday
+
+The weekday as an integer, where 0 is Sunday and 6 is Saturday.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday // 6
+```
+
+### timespace.weekday_name
+
+The name of the day of the week, in English. Example: `Sunday`.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").weekday_name // Saturday
+```
+
+### timespace.year
+
+The year as an integer.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").year // 2019
+```
+
+### timespace.unix
+
+The number of seconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix // 1573867230
+```
+
+### timespace.unix_nano
+
+The number of nanoseconds since the Unix epoch.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").unix_nano // 1573867230000000000
+```
+
+### timespace.zone
+
+The timezone offset, in seconds. This can be a negative number to indicate
+time west of UTC.
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone // -28800
+```
+
+### timespace.zone_string
+
+The timezone offset, in the format `+HH:MM` (example: `-08:00` for PST).
+
+```sentinel
+time.load("2019-11-16T01:20:30-08:00").zone_string // -08:00
+```
+
+### timespace.before(timeish_or_timespace)
+
+Check if the time in the timespace is before the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").before("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.after(timeish_or_timespace)
+
+Check if the time in the timespace is after the given time
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").after("2019-11-15T01:20:30Z") // true
+```
+
+### timespace.equal(timeish_or_timespace)
+
+Check if two times are equivalent
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").equal("2019-11-15T01:20:30Z") // false
+```
+
+### timespace.add(duration)
+
+Add a duration in nanoseconds to the time in the timespace and return a new timespace. The
+duration can be negative. The duration should be specified as an integer
+multiplied with the correct unit.
+
+```sentinel
+time.load("2019-11-16T01:20:30Z").add(time.hour * 1) // 2019-11-16T02:20:30Z
+```
+
+### timespace.sub(timeish_or_timespace)
+
+Subtract a time from the time in the timespace and return a duration in nanoseconds.
+
+```sentinel
+// Gives a duration of 3 hours.
+duration = time.load(
+ "2019-11-16T01:20:30Z",
+).sub("2019-11-16T04:20:30Z")
+
+// Returns 2019-11-16T01:20:30Z
+time.load(
+ "2019-11-16T04:20:30Z",
+).add(duration)
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/imports/types.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/imports/types.mdx
new file mode 100644
index 0000000000..fcfdbd2ae5
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/imports/types.mdx
@@ -0,0 +1,35 @@
+---
+page_title: 'Import: types'
+sidebar_current: docs-imports-types
+description: The types import enables a Sentinel policy to parse an object's type.
+layout: docs
+---
+
+# Import: types
+
+The types import enables a Sentinel policy to parse an object's type.
+
+### types.type_of(obj)
+
+Returns the object's type as a string
+
+```sentinel playground
+import "types"
+
+// Typically the inputs would come from an external source.
+is_boolean = rule { types.type_of(true) is "bool" }
+is_string = rule { types.type_of("Hello!") is "string" }
+is_integer = rule { types.type_of(42) is "int" }
+is_float = rule { types.type_of(42.123) is "float" }
+is_null = rule { types.type_of(null) is "null" }
+is_undefined = rule { types.type_of(undefined) is "undefined" }
+
+main = rule {
+ is_boolean and
+ is_string and
+ is_integer and
+ is_float and
+ is_null and
+ is_undefined
+}
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/imports/units.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/imports/units.mdx
new file mode 100644
index 0000000000..e2ad3442f9
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/imports/units.mdx
@@ -0,0 +1,39 @@
+---
+page_title: 'Import: units'
+sidebar_current: docs-imports-units
+description: The runtime import contains various information about the Sentinel runtime.
+layout: docs
+---
+
+# Import: units
+
+The units import provides access to quick calculations for various byte
+units.
+
+Note that this import uses base-2 terminology:
+
+- One kilobyte is 1024 bytes
+- One megabyte is 1024^2 = 1048576 bytes
+- One gigabyte is 1024^3 = 1073741824 bytes
+- One terabyte is 1024^4 = 1099511627776 bytes
+- One petabyte is 1024^5 = 1125899906842624 bytes
+
+### units.kilobyte
+
+The base-2 representation of a kilobyte (1024 bytes).
+
+### units.megabyte
+
+The base-2 representation of a megabyte (1048576 bytes).
+
+### units.gigabyte
+
+The base-2 representation of a gigabyte (1073741824 bytes).
+
+### units.terabyte
+
+The base-2 representation of a terabyte (1099511627776 bytes).
+
+### units.petabyte
+
+The base-2 representation of a petabyte (1125899906842624 bytes).
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/imports/version.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/imports/version.mdx
new file mode 100644
index 0000000000..59366c1087
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/imports/version.mdx
@@ -0,0 +1,151 @@
+---
+page_title: 'Import: version'
+sidebar_current: docs-imports-version
+description: |-
+ The version import provides functions for parsing versions and version constraints,
+ and verifying versions against a set of constraints.
+layout: docs
+---
+
+# Import: version
+
+The version import provides functions for parsing versions and version constraints,
+and verifying versions against a set of constraints.
+
+Versions are created through the `new` function, which accepts a string type in dotted-tri format (i.e. 1.0.0-alpha.1+001) that can represent a version.
+
+### version.new(v)
+
+Constructs a version from string value.
+
+```sentinel
+version.new("1.0.0-alpha.1+001")
+```
+
+### version.major
+
+An integer representation of the Major number for a given version .
+
+```sentinel
+version.new("1.0.0-alpha.1+001").major // 1
+```
+
+### version.minor
+
+An integer representation of the Minor number for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").minor // 0
+```
+
+### version.patch
+
+An integer representation of the Patch number for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").patch // 0
+```
+
+### version.prerelease
+
+A string representation of the Prerelease for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").prerelease // "alpha.1"
+```
+
+### version.metadata
+
+A string representation of the Metadata for a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").metadata // "001"
+```
+
+### version.version
+
+A string representation of the version including pre-release and metadata information.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").version // "1.0.0-alpha.1+001"
+```
+
+### version.greater_than(v)
+
+Tests that the version is greater than a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").greater_than("0.12.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").gt("1.0.1-alpha.1+001") // False
+```
+
+### version.greater_than_or_equals(v)
+
+Tests that the version is greater than or equal to a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").greater_than_or_equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").gte("1.0.1-alpha.1+001") // False
+```
+
+### version.less_than(v)
+
+Tests that the version is less than a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").less_than("1.0.1-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").lt("0.12.0-alpha.1+001") // False
+```
+
+### version.less_than_or_equals(v)
+
+Tests that the version is less than or equal to a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").less_than_or_equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").lte("0.12.0-alpha.1+001") // False
+```
+
+### version.less_than_or_equals(v)
+
+Tests that the version equals a given version.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").equals("1.0.0-alpha.1+001") // True
+version.new("1.0.0-alpha.1+001").eq("0.12.0-alpha.1+001") // False
+```
+
+### version.compare(v)
+
+Compares one version with a given version. Compare returns -1, 0, or 1 if the version is less, equal, or greater than the other version, respectively.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").compare("0.12.0-alpha.1+001") // -1
+version.new("1.0.0-alpha.1+001").compare("1.0.0-alpha.1+001") // 0
+version.new("0.12.0-alpha.1+001").cmp("1.0.0-alpha.1+001") // 1
+```
+
+### version.satisfies(v, x)
+
+Tests that a version adheres to one or more version constraints. Satisfies supports the following restriction operators:
+
+- =
+- !=
+- \>
+- <
+- \>=
+- <=
+- ~>
+
+Satisfies supports the following usage scenarios:
+
+- A constraint with a pre-release can only match a pre-release version that contains the same major, minor and patch identifiers.
+- A constraint without a pre-release can only match a version without a pre-release.
+
+```sentinel
+version.new("1.0.0-alpha.1+001").satisfies("> 1.0.0-alpha.1+001") // false
+version.new("1.0.0-alpha.1+001").satisfies(">= 1.0.0-alpha.1+001") // true
+version.new("1.0.0-alpha.1+001").satisfies(">= 1.0.0-alpha.1+001, < 1.0.0-beta+001") // true
+version.new("1.0.0").sat("= 1.0.0") // true
+version.new("0.12.7").sat("~> 0.12.0") // true
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/index.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/index.mdx
new file mode 100644
index 0000000000..664d9b30e2
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/index.mdx
@@ -0,0 +1,25 @@
+---
+layout: 'docs'
+page_title: 'Documentation'
+sidebar_current: 'docs-home'
+description: |-
+ Sentinel is a language and framework for policy built to be embedded in existing software to enable fine-grained, logic-based policy decisions.
+---
+
+# Sentinel Documentation
+
+Welcome to the Sentinel documentation!
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+["Why Sentinel?"](/sentinel/why) to understand what Sentinel is, how it
+compares to other software, and more.
+
+
\ No newline at end of file
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/intro.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/intro.mdx
new file mode 100644
index 0000000000..5b1bfd2c09
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/intro.mdx
@@ -0,0 +1,68 @@
+---
+page_title: Introduction to Sentinel
+sidebar_current: docs-intro
+description: >-
+ Sentinel is a language and framework for policy built to be embedded in
+ existing software to enable fine-grained, logic-based policy decisions.
+layout: docs
+---
+
+# Introduction to Sentinel
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise-only feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+This documentation should serve as a reference guide for developing Sentinel
+policies, embedding Sentinel into your own software, extending Sentinel with
+plugins, and more. If you're just getting started with Sentinel, please start with the
+["Why Sentinel?"](/sentinel/docs/why) to understand what Sentinel is, how it
+compares to other software, and more.
+
+## What is Sentinel?
+
+Sentinel is a language and framework for policy built to be embedded
+in existing software to enable fine-grained, logic-based policy decisions.
+A policy describes under what circumstances certain behaviors are allowed.
+Sentinel is an enterprise feature of HashiCorp Consul, Nomad, Terraform,
+and Vault.
+
+Sentinel provides a language for writing policy and a workflow for developing
+and testing policies independent of the software they'll be written to. We
+also provide a framework for developers to build plugins that allow a
+Sentinel-enabled system to access external information to make policy
+decisions.
+
+Most systems today have some degree of access control. You are able to define
+identities and what they have access to. These ACL systems solve an immediate
+and necessary problem of locking down a system in very broad strokes. Sentinel
+is a reusable system for more advanced software policy decisions. Sentinel
+enables:
+
+- **Fine-Grained Policy**: Most ACL systems only enable coarse-grained
+ behaviors: "read", "write", etc. Sentinel enables fine-grained behavior such
+ as disallowing a certain API call when specific parameters are present.
+
+- **Logic-Based Policy**: You can write policy using full
+ conditional logic. For example, you may only allow a certain application behavior
+ on Monday to Thursday unless there is a manager override.
+
+- **Accessing External Information**: Sentinel can source external information
+ to be used in policy decisions. For example, a policy that restricts the size
+ of a payload may read data from [Consul](https://www.consul.io) to determine
+ the payload size limit.
+
+- **Enforcement levels**: Sentinel allows policies to be defined along
+ with an "enforcement level" that dictates the pass/fail behavior of a policy.
+ Advisory policies warn if they fail, soft mandatory policies can have their
+ failures overridden, and hard mandatory policies must pass under all
+ circumstances. Having this as a built-in concept enables you to model
+ policy more accurately and completely for your organization.
+
+
+
+## Next steps
+
+Refer the page on [Why Sentinel?](/sentinel/why) to learn more about the origins of Sentinel.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/language/arithmetic.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/language/arithmetic.mdx
new file mode 100644
index 0000000000..c49f2fd0ce
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/language/arithmetic.mdx
@@ -0,0 +1,67 @@
+---
+page_title: Sentinel Language - Arithmetic
+sidebar_current: docs-language-arith
+description: >-
+ Sentinel supports arithmetic operators for integers and floats. Sentinel
+ supports sum, difference, product, quotient, and remainder.
+layout: docs
+---
+
+# Language: Arithmetic
+
+Sentinel supports arithmetic operators for integers and floats. Sentinel
+supports sum, difference, product, quotient, and remainder.
+
+```plaintext
++ sum
+- difference
+* product
+/ quotient
+% remainder
+```
+
+These operators work in a typical infix style:
+
+```sentinel
+4 + 8 // 12
+8 * 2 // 16
+8 / 4 // 2
+8 / 5 // 1
+8 % 5 // 3
+```
+
+## Order of Operations
+
+Arithmetic follows a standard mathematical order of operations.
+Grouping with parentheses can be used to affect ordering.
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+(4 + 5) * 2 // 18
+```
+
+A full table of operator precendence can be found on the
+[boolean expressions page](/sentinel/language/boolexpr). This
+shows how arithmetic operators relate to other operators.
+
+## Integer Division
+
+Integer division with a remainder rounds down to the nearest integer.
+Example: `8 / 3 is 2`.
+
+If the divisor is zero, an error occurs.
+
+## Mixed Numeric Operations
+
+Mixed numeric operations between integer and floating-point values are
+permitted. The result is a floating-point operation with the integer converted
+to a floating-point value for purposes of calculation.
+
+```sentinel
+import "types"
+
+a = 1.1 + 1 // 2.1
+types.type_of(a) // "float"
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/language/boolexpr.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/language/boolexpr.mdx
new file mode 100644
index 0000000000..f1c518c3bf
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/language/boolexpr.mdx
@@ -0,0 +1,225 @@
+---
+page_title: Sentinel Language - Boolean Expressions
+sidebar_current: docs-language-boolexpr
+description: >-
+ Boolean expressions are expressions that evaluate to a boolean value from the
+ result of a combination of one or more comparisons and logical operators.
+layout: docs
+---
+
+# Language: Boolean Expressions
+
+Boolean expressions are expressions that evaluate to a boolean value
+from the result of a combination of one or more comparisons and logical
+operators. Boolean expressions form the basis of policy since policy can be broken
+down to a set of logical decisions that turn into true or false.
+
+A single boolean expression is the body of a [rule](/sentinel/language/rules).
+Additionally, boolean expressions are used with [conditionals](/sentinel/language/conditionals),
+and more.
+
+## Order of Operations
+
+The precedence of operators is shown below. Operators with higher
+precedence values (larger numbers) are evaluated first. Operators with
+the same precedence value are evaluated left-to-right.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Examples of this precedence are shown below:
+
+```sentinel
+4 * 5 / 5 // 4
+4 * 5 + 2 // 22
+4 + 5 * 2 // 14
+```
+
+## Logical Operators
+
+Logical operators are used to change or combine logical expressions.
+There are three binary operators `and`, `or`, and `xor`. There is
+a single unary operator `not` (which can also be specified as `!`).
+
+The binary operators require two boolean operands and the unary
+operator requires a single boolean operand. In both case, the operands
+are values or expressions that are boolean types.
+
+Below is a basic explanation of the logical operators directly from
+the specification:
+
+```sentinel
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+Using parentheses to group boolean expressions, you can combine
+boolean expressions to become more complex or affect ordering:
+
+```sentinel
+(p and q) or r
+p and (q or r)
+```
+
+## Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+For any comparison, the two operands must be equivalent types. The one
+exception are integers and floats. When an integer is compared with
+a float, the integer is promoted to a floating point value.
+
+The available comparison operators are:
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+The behavior of `is` with `==` and `is not` with `!=` is identical.
+
+Example comparisons:
+
+```sentinel
+name is "Mitchell"
+idnumber > 42
+idnumber <= 12
+```
+
+Using the logical operators, these can be combined:
+
+```sentinel
+name is "Mitchell" and idnumber > 42
+```
+
+The language specification provides more detail on exactly
+[how comparison behaves](/sentinel/language/spec#comparison-operators).
+
+## Set Operators
+
+The set operators `contains` and `in` test for inclusion in a collection
+(a list or map).
+
+Set operators may 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.
+
+`contains` tests if the left-hand collection contains the right-hand value.
+`in` tests if the right-hand collection contains the left-hand value. For
+maps, "contains" looks at the keys, not the values.
+
+Examples:
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+```
+
+## Matches Operator
+
+The `matches` operator tests if a string matches a regular expression.
+The `matches` operator can be negated by prefixing the operator with
+`not`, such as `not matches`.
+
+The left-hand value is the string to test. The right-hand value is
+a string value representing a regular expression. The syntax of the regular
+expression is the same general syntax used by Python, Ruby, and other languages.
+The precise syntax accepted is the syntax accepted by RE2.
+
+Regular expressions are not anchored by default; any anchoring must be
+explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+## Any, All Expressions
+
+`any` and `all` expressions allow testing that any or all elements of a
+collection match some boolean expression. The result of an `any` and `all`
+expression is a boolean.
+
+`any` returns the boolean value `true` if any value in the collection expression
+results in the body expression evaluating to `true`. If the body expression
+evalutes to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression
+result in the body expression evaluating to `true`. If any value in the
+collection expression result in the body expression evaluating to `false`,
+the `all` expression returns `false`.
+
+For empty collections, `any` returns `false` and `all` returns `true`.
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" }
+any group.tasks as t { t.driver is "vmware" }
+```
+
+Since `any` and `all` expressions are themselves boolean expressions, they
+can be combined with other operators:
+
+```sentinel
+any ["a", "b"] as char { char is "a" } or
+other_value is "another"
+```
+
+## Emptiness Comparison
+
+The expressions `is empty` and `is not empty` provide a convenience
+method for determining the emptiness of a value. It supports the same types
+as the [built-in length](/sentinel/functions/length), collections and strings.
+
+```sentinel
+[] is empty // true
+[] is not empty // false
+["foo"] is empty // false
+["foo"] is not empty // true
+undefined is empty // undefined
+undefined is not empty // undefined
+```
+
+## Defined Comparison
+
+The expressions `is defined` and `is not defined` provide a convenience
+method for determining if a value has been defined. In other words, any value
+other than `undefined` can be considered as `defined`.
+
+```sentinel
+[] is defined // true
+4 is defined // true
+true is defined // true
+{} is defined // true
+undefined is defined // false
+[] is not defined // false
+4 is not defined // false
+true is not defined // false
+undefined is not defined // true
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/language/collection-operations.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/language/collection-operations.mdx
new file mode 100644
index 0000000000..41da8f3584
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/language/collection-operations.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Sentinel Language - Collection Operations
+sidebar_current: docs-language-collection-operations
+description: |-
+ Operations for interacting with collections (list, map).
+layout: docs
+---
+
+# Language: Collection Operations
+
+Collection operations are expressions that are performed on a list or map to
+return a variation of the initial data.
+
+At the moment, `filter` is the only collection operation available.
+
+## Filter Expression
+
+`filter` is a [quantifier
+expression](/sentinel/language/spec#quantifier-expressions-any-all-filter-map) that
+returns a subset of the provided collection. Only elements whose filter body
+returns true will be returned. If any of the elements filter body returns
+undefined, the final result will be undefined.
+
+Filter uses the same syntax as the `any` and `all` [boolean
+expressions](/sentinel/language/boolexpr#any-all-expressions):
+
+```sentinel
+filter list as value { condition } // Single-iterator, list
+filter list as idx, value { condition } // Double-iterator, list
+
+filter map as key { condition } // Single-iterator, map
+filter map as key, value { condition } // Double-iterator, map
+```
+
+Examples:
+
+```sentinel
+l = [1, 1, 2, 3, 5, 8]
+evens = filter l as v { v % 2 is 0 } // [2, 8]
+
+m = { "a": "foo", "b": "bar" }
+matched_foo = filter m as _, v { v is "foo" } // { "a": "foo" }
+```
+
+## Map Expression
+
+`map` is a [quantifier
+expression](/sentinel/language/spec#quantifier-expressions-any-all-filter-map) that
+returns a list, regardless of the input collection type. Each element within the
+input collection is evaluted according to the map expression body and appended
+to the result.
+
+```sentinel
+l = [1, 2]
+r = map l as v { v % 2 } // [false, true]
+
+m = { "a": "foo", "b": "bar" }
+r = map m as k, v { v } // ["foo", "bar"]
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/language/conditionals.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/language/conditionals.mdx
new file mode 100644
index 0000000000..1736795dbb
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/language/conditionals.mdx
@@ -0,0 +1,161 @@
+---
+page_title: Sentinel Language - Conditionals
+sidebar_current: docs-language-conditional
+description: |-
+ Conditional statements allow your policy to behave differently depending on a condition.
+layout: docs
+---
+
+# Language: Conditionals
+
+Conditional statements allow your policy to behave differently depending
+on a condition.
+
+Conditional statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## If Statements
+
+`if` statements only execute their bodies if a condition is met.
+The syntax of an `if` statement is:
+
+```sentinel
+if condition {
+ // ... this is executed if condition is true
+}
+```
+
+The `condition` must result in a boolean, such as by calling a function
+or evaluating a [boolean expression](/sentinel/language/boolexpr). If
+the `condition` is `true`, the body (within the `{}`) is executed. Otherwise,
+the body is skipped.
+
+Examples:
+
+```sentinel
+// This would execute the body
+value = 12
+if value is 18 {
+ print("condition met")
+}
+
+// Direct boolean values can be used
+value = true
+if value {
+ print("condition met")
+}
+
+// This would not execute the body since the boolean expression will
+// result in undefined.
+value = {}
+if value["key"] > 12 {
+ print("condition met")
+}
+```
+
+### Else, Else If
+
+An `else` clause can be given to an `if` statement to execute a body
+in the case the condition is _not met_. By putting another `if` statement
+directly after the `else`, multiple conditions can be tested for.
+The syntax is:
+
+```sentinel
+if condition {
+ // ...
+} else {
+ // ...
+}
+
+if condition {
+ // ...
+} else if other_condition {
+ // ...
+} else {
+ // ...
+}
+```
+
+### Scoping
+
+The body of an `if` statement does not create a new
+[scope](/sentinel/language/scope). Any variables assigned within the body
+of an if statement will modify the scope that the `if` statement itself
+is in.
+
+Example:
+
+```sentinel
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+```sentinel
+a = 18
+if true {
+ a = 42
+}
+
+print(a) // 42
+```
+
+## Case Statements
+
+`case` statements are a selection control mechanism that execute a clause based
+on matching expressions. It is worth noting that the expression for `case` is
+optional. When no expression is provided, it defaults the expression to `true`.
+Additionally, the order of clauses is important, as they are evaluated from top
+to bottom, executing the first match.
+The syntax of a case statement is:
+
+```sentinel
+case expression {
+ when clause_expression:
+ // executed when clause_expression and expression are equal
+ else:
+ // executed if no clause matches expression
+}
+```
+
+### When Clause
+
+Any clause that has an expression for comparison must use the `when` keyword.
+It accepts a list of expressions, seperated by a `,`.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+}
+```
+
+```sentinel
+case {
+ when x > 40:
+ return true
+}
+```
+
+### Else Clause
+
+The `else` keyword allows for capturing any expressions that have no matching
+`when` clause.
+
+Example:
+
+```sentinel
+case x {
+ when "foo", "bar":
+ return true
+ else:
+ return false
+}
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/language/functions.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/language/functions.mdx
new file mode 100644
index 0000000000..8214078b6b
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/language/functions.mdx
@@ -0,0 +1,229 @@
+---
+page_title: Sentinel Language - Functions
+sidebar_current: docs-language-functions
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Functions
+
+Functions allow you to create reusable code to perform computations.
+
+Below is a trivial example function that doubles a number and shows
+the main rule using the function:
+
+```sentinel playground
+double = func(x) {
+ return x * 2
+}
+
+main = rule { double(12) is 24 }
+```
+
+Functions may contain any expressions,
+[conditionals](/sentinel/language/conditionals),
+and [loops](/sentinel/language/loops). They must return a value
+at the end of their execution. If no reasonable return value exists,
+the function should return [undefined](/sentinel/language/undefined).
+
+## Function Types
+
+### Named Functions
+
+-> **NOTE:** Named functions must be created within the [package scope](/sentinel/language/scope#package-scope).
+
+Named functions are declared using the `func` keyword as its own statement.
+They provide a safe method of creating functions and have additional
+restrictions that do not apply to anonymous functions.
+
+Firstly, named functions cannot be re-assigned, and also cannot use a name
+that is already used elsewhere. For instance, the below example will error due
+to the attempt to reassign the named function identifier to a new value:
+
+```sentinel
+func sum(a, b) {
+ return a + b
+}
+
+sum = 4
+```
+
+Additionally, the following will error due to the named function attempting
+to make use of an already assigned identifier:
+
+```sentinel
+sum = 4
+
+func sum(a, b) {
+ return a + b
+}
+```
+
+Named functions are helpful for policy authors to declare critical functions
+whose value or implementation should not be changed.
+
+### Anonymous Functions
+
+An anonymous function is created by assigning a variable to a `func`. The
+variable can be reassigned at any time including to different value types.
+Anonymous functions are helpful for use cases like closures, where a function
+can return another function.
+
+```sentinel
+func makeAdder(a) {
+ return func(b) {
+ return a + b
+ }
+}
+```
+
+## Creating a Function
+
+A function can have zero or more parameters. These parameters are specified
+within parentheses `()` separated by commas. If a function has no parameters,
+the parentheses must still be present.
+
+A function must terminate with a `return` statement to return a value back to
+the caller. Only a single value can be returned. The type of a return can
+vary between return statements.
+
+Anonymous function example:
+
+```sentinel
+add1 = func(x) { return x + 1 }
+```
+
+Named function example:
+
+```sentinel
+func add1(x) {
+ return x + 1
+}
+```
+
+Both examples create a function that adds 1 to the parameter `x`.
+
+## Calling a Function
+
+Functions are called by accessing their name followed by parentheses with
+a list of comma-separated values for the parameters. If the function takes no
+parameters, an empty set of parentheses must still be used to denote a
+function call.
+
+To call the function in the example above:
+
+```sentinel
+x = 1
+y = add1(x)
+```
+
+## Scoping
+
+The body of a `func` creates a new [scope](/sentinel/language/scope).
+
+The parent scope is the scope in which the function is created. This allows
+for the creation of functions within functions that can access their outer
+scopes.
+
+Example:
+
+```sentinel
+f = func() {
+ a = 42
+ print(a) // 42
+ return undefined
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+f = func() {
+ a = 42
+ return undefined
+}
+
+print(a) // 18
+```
+
+And below is an example of creating a function in a function which uses
+outer values:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double()
+}
+
+print(f()) // 84
+```
+
+A more complex example below shows how scoping works when passing
+functions around as arguments or results:
+
+```sentinel
+f = func() {
+ a = 42
+ double = func() { return a * 2 }
+ return double
+}
+
+double = f()
+print(double()) // 84
+```
+
+## Pass By Value
+
+The parameters to a function are passed by value, but not deep copied.
+This means that elements of collections can still be modified but the
+root value cannot be changed.
+
+Example:
+
+```sentinel
+f = func(x) {
+ x = "value"
+ return x
+}
+
+x = "outside"
+f(x)
+print(x) // "outside"
+```
+
+```sentinel
+f = func(x) {
+ append(x, "value")
+ return x
+}
+
+x = []
+f(x)
+print(x) // ["value"]
+```
+
+## Recursion
+
+A function is allowed to call other functions, including itself.
+This can be used to implement recursion. For example, the fibonacci sequence
+is implemented below:
+
+```sentinel
+fib = func(x) {
+ if x <= 0 {
+ return undefined
+ }
+
+ if x == 1 {
+ return 1
+ } else {
+ return x + fib(x - 1)
+ }
+}
+```
+
+Note that this example also shows using
+[undefined](/sentinel/language/undefined)
+as a return value in cases where a function has undefined behavior.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/language/imports.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/language/imports.mdx
new file mode 100644
index 0000000000..0ed1541b85
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/language/imports.mdx
@@ -0,0 +1,116 @@
+---
+page_title: Sentinel Language - Imports
+sidebar_current: docs-language-imports
+description: >-
+ Imports enable a Sentinel policy to access reusable libraries and external
+ data and functions.
+layout: docs
+---
+
+# Language: Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external
+data and functions. The exact list of available imports is dependent on the
+host system. Host systems may also make the available imports configurable
+via plugins.
+
+Imports are extremely powerful. They enable policy decision based on arbitrary
+external information. For example, a behavior coming into a system can be
+allowed or denied based on information from a separate system where the two
+systems can be unaware of each other.
+
+The example below shows a concrete example. For the example, imagine that
+the import `calendar` allows access to engineer calendars. This import
+only exists for the purpose of this example and at the time of writing is
+not a real available import.
+
+```sentinel
+import "calendar"
+
+// Get the calendar for Bob for today
+bob_calendar = calendar.for("bob").today
+
+// Allow this policy to pass if Bob is not on vacation.
+main = rule { not bob_calendar.has_event("vacation") }
+```
+
+This example shows an import being used to deny a behavior if Bob is on
+vacation. Hopefully real policies do not depend on a single person being
+in the office, but the example shows the power of imports.
+
+In addition to consuming imports, you can also
+[extend Sentinel by writing custom imports](/sentinel/extending)
+This allows you to add any arbitrary behavior to your Sentinel-protected
+systems.
+
+## Importing
+
+Whether accessing a built-in library or an external plugin, the syntax
+for an import is the same:
+
+```sentinel
+import "name"
+```
+
+This adds the import with the name `name`. It can be referenced using
+the identifier `name`. For example, if the import above exposed first
+and last name data, you could access it via `name.first` or `name.last`.
+
+You can shadow imports by assigning a variable. In the example below,
+the import `name` becomes unavailable within the function because it is
+shadowed by a variable of the same name.
+
+Shadowing an import is not the same as overwriting a variable. Imports
+are not part of the [scope](/sentinel/language/scope), meaning that
+the shadow only exists for the scope it is created within. For everything
+else, the import works even after it is shadowed. The example below shows that
+as well.
+
+```sentinel
+import "name"
+
+f = func() {
+ name = "jane"
+ return name
+}
+
+print(f()) // "jane"
+print(name.first) // "bob"
+```
+
+## Aliases
+
+An import can be aliased to another name using the syntax below:
+
+```sentinel
+import "name" as other
+```
+
+This would make the import "name" available as `other`.
+
+An import may only be imported once, regardless of aliases. The following
+would be **invalid** and would result in an error:
+
+```sentinel
+import "name" as one
+import "name" as two
+```
+
+## Accessing Import Data
+
+Imports are accessed with dot-separated identifiers, such as `name.first` or
+`name.stage.first`. These are called _selector expressions_. An import may
+return nested data that further can be accessed via selector expressions.
+
+In the first example on this page with the "calendar" import, we see this:
+
+```sentinel
+import "calendar"
+
+a = calendar.for("bob") // selector expression calendar.for
+b = a.today // selector expression on the result
+c = b.vacation // accessing further nested data
+```
+
+The exact structure of an import is dependent on the import. Please reference
+the documentation for an import to learn more.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/language/index.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/language/index.mdx
new file mode 100644
index 0000000000..7f588c94cd
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/language/index.mdx
@@ -0,0 +1,114 @@
+---
+page_title: Sentinel Language
+sidebar_current: docs-language-basics
+description: |-
+ Sentinel policies are written using the Sentinel language. This language
+ is easy to learn and easy to write. You can learn the Sentinel language
+ and be productive within an hour. Learning Sentinel doesn't require any
+ formal programming experience.
+layout: docs
+---
+
+# Sentinel Language
+
+Sentinel policies are written using the Sentinel language. This language
+is easy to learn and easy to write. You can learn the Sentinel language
+and be productive within an hour. Learning Sentinel doesn't require any
+formal programming experience.
+
+This language guide will contain many details that are not necessary to
+be productive with Sentinel or are targeted to those who are looking for
+exact information about the language (such as what types of line endings are
+allowed). You can safely ignore these sentences.
+
+You may also view the
+[official language specification](/sentinel/language/spec)
+This is a specific and detailed document on the syntax and behavior of
+the language primarily intended for implementation creators and to disambiguate
+the language.
+
+## Simplest Example
+
+The example below is about the simplest practical example of Sentinel.
+It is reasonable to imagine this as a realistic policy. This shows that
+in most cases, Sentinel will be extremely simple:
+
+```sentinel
+main = rule { request.method is "GET" and request.headers contains "X-Key" }
+```
+
+## Files
+
+Sentinel policies are single files that end in the `.sentinel` file extension.
+There is currently no built-in mechanism to Sentinel for merging multiple
+files. This is purposefully done to make Sentinel policies easy to submit
+to systems that support Sentinel policies.
+
+Sentinel policy files must be UTF-8 encoded and can end in both
+Unix (LF) or Windows (CRLF) line breaks. When running the
+[auto-formatter](#),
+line endings will always use Unix line breaks.
+
+## Ordering
+
+Sentinel policies are executed top-down. For example:
+
+```sentinel
+a = 1 // a = 1 here
+b = a + 1 // b = 2 here
+a = 3 // a = 3, b = 2
+```
+
+In this example, the value of `a` and `b` is shown at each line. Since Sentinel
+executes values top-down, the final value of `a` is 3 and `b` is 2. `b` _does
+not_ become 4.
+
+## Main
+
+Sentinel expects there to be a `main` [rule](/sentinel/language/rules).
+The value of this rule is the result of the entire policy.
+
+The result of the policy depends on the evaluated contents of the `main` rule.
+For booleans, a policy passes on a `true` value, and fails on a `false` value.
+Other types generally follow a zero or zero-length pattern for determining
+success.
+
+| Type | Passing Condition | Failing Condition |
+| ------- | ----------------- | -------------------------- |
+| Boolean | `true` | `false` |
+| String | `""` | Any non-zero length string |
+| Integer | `0` | Any non-zero value |
+| Float | `0.0` | Any non-zero value |
+| List | `[]` | Any non-zero length list |
+| Map | `{}` | Any non-zero length map |
+
+A value of main that falls outside of the above types will result in a policy
+error. As a special case, if `main` evaluates to an undefined value, the error
+message will indicate as such, with a reference to where the undefined value was
+encountered.
+
+## More Complex Example
+
+The simple example above is a full working example. In our experience with
+Sentinel, many policies can be representing using this simple form. However,
+to show more features of the language, a more complex example is shown below.
+This example is also a realistic example of what Sentinel may be used for.
+
+```sentinel
+import "units"
+
+memory = func(job) {
+ result = 0
+ for job.groups as g {
+ for g.tasks as t {
+ result += t.resources.memory else 0
+ }
+ }
+
+ return result
+}
+
+main = rule {
+ memory(job) < 1 * units.gigabyte
+}
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/language/lists.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/language/lists.mdx
new file mode 100644
index 0000000000..bc5269d8eb
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/language/lists.mdx
@@ -0,0 +1,135 @@
+---
+page_title: Sentinel Language - Lists
+sidebar_current: docs-language-lists
+description: Lists are a collection of zero or more values.
+layout: docs
+---
+
+# Language: Lists
+
+Lists are a collection of zero or more values.
+
+Lists can be created using by wrapping values in `[]` and separating them
+by commas. An optional trailing comma is allowed. List elements can be
+differing types. Examples:
+
+```sentinel
+[] // An empty list
+["foo"] // Single element list
+["foo", 1, 2, true] // Multi element list with different types
+["foo", [1, 2]] // List containing another list
+```
+
+A list can be [sliced](/sentinel/language/slices) to create sublists.
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for value inclusion in a list.
+
+## Accessing Elements
+
+List elements can be accessed with the syntax `name[index]` where
+`index` is zero-indexed.
+
+A negative index accesses the list in reverse. It is the same as
+reversing a list and then using a positive index. Similar to a positive
+index, it is bounded by the length of the list as a negative value.
+
+Accessing beyond the length of the list results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+a = ["foo", 1, true, [1, 2]]
+
+a[0] // "foo"
+a[2] // true
+a[4] // undefined
+a[-2] // true
+a[-4] // "foo"
+a[-5] // undefined
+a[3][1] // 2
+```
+
+## List Append
+
+Values can be appended to a list using the built-in
+[append](/sentinel/functions/append) function.
+
+This modifies the list in-place and returns
+[undefined](/sentinel/language/undefined). For more information on
+why `append` behaves this way, please read the full documentation for the
+[append](/sentinel/functions/append) function.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append([1,2], "foo") // [1, 2, "foo"]
+append([1,2], [3]) // [1, 2, [3]]
+append(1, 3) // error()
+```
+
+## List Concatenation
+
+Two lists can be concatenated using the `+` operator or the shorthand
+`+=` assignment operator. For the `+` operator, a new list is returned.
+For `+=`, the left-hand list is modified in place.
+
+Examples:
+
+```sentinel
+[1] + [2] // [1, 2]
+[1] + [[1]] // [1, [1]]
+[1] + 1 // error
+
+a = [1]
+a += [2] // a = [1, 2]
+a += 3 // error
+```
+
+## List Length
+
+The length of a list can be retrieved using the
+[length](/sentinel/functions/length) function.
+
+Examples:
+
+```sentinel
+length([]) // 0
+length(["foo"]) // 1
+```
+
+## Removing Items From a List
+
+You can use a combination of [list concatenation](#list-concatenation) and
+[slicing](/sentinel/language/slices) to remove elements from a list.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+a = a[:2] + a[3:] // [1, 2, 4, 5]
+```
+
+The shorthand shown here is effectively the same as `a[0:2] + a[3:length(a)]`,
+which creates a new list out of the concatenation two sub-lists composed of the
+first two elements, and the rest of the list starting at index 3. This
+effectively removes the 3rd element from the list (index 2).
+
+## List Comparison
+
+Lists may be [compared](/sentinel/language/boolexpr#comparison-operators)
+for equality. Lists are equal if they are of equal length and their
+corresponding elements are comparable and equal. Lists with the same elements
+but in a different order are not equal.
+
+```sentinel
+[1, 2] is [1, 2] // true
+[1, 2] is [2, 1] // false
+["a"] is ["a", "b"] // false
+["a", ["b", "c"]] is ["a", ["b", "c"]] // true
+```
+
+List comparison speed is O(N), meaning that the speed of the comparison is
+_linearly_ proportional to the number of elements in the list. The more
+elements, the more iterations that are necessary to verify equality.
+
+-> The N-value quoted above should account for the sum of the elements of _all_
+lists in the subjects of comparison, as list comparison will recurse into these
+lists to check for equality.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/language/logging-errors.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/language/logging-errors.mdx
new file mode 100644
index 0000000000..705a72cbd4
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/language/logging-errors.mdx
@@ -0,0 +1,52 @@
+---
+page_title: Sentinel Language - Logging and Errors
+sidebar_current: docs-language-log
+description: The built-in functions `print` and `error` can be used for logging and errors.
+layout: docs
+---
+
+# Language: Logging and Errors
+
+The built-in functions `print` and `error` can be used for logging
+and errors. Logging is helpful to understand how a policy is executing
+in development. And errors are useful to halting execution immediately in
+certain scenarios.
+
+## Print
+
+The `print` function is used to output debug information. The exact location
+where this print statements go is dependent on the host application and
+the Sentinel runtime itself.
+
+The `print` function takes zero or more Sentinel values as arguments.
+Each value is formatted for human-friendly output and space-separated.
+Example:
+
+```sentinel
+value = 42
+print("the value is", value) // the value is 42
+
+map = { "foo": false }
+print(map) // { "foo": false }
+
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // false
+```
+
+## Errors
+
+Certain actions in Sentinel, such as accessing an undefined variable,
+attempting to add two incompatible types, etc. result in _runtime errors_.
+These errors halt the execution of a policy immediately. The pass/fail result
+of a policy is considered fail.
+
+Runtime errors can be manually triggered using the `error` function.
+The `error` function behaves exactly like the `print` function except that
+execution is halted immediately.
+
+This should only be used in scenarios where the policy _must fail_ due to
+some unexpected or invalid condition. The line between `error` and
+[undefined](/sentinel/language/undefined) can sometimes be unclear. Undefined
+is recommended when a policy can conceivably recover or safely ignore the
+undefined behavior. An error should be used when the policy should fail and
+notify someone regardless.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/language/loops.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/language/loops.mdx
new file mode 100644
index 0000000000..622732bd35
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/language/loops.mdx
@@ -0,0 +1,92 @@
+---
+page_title: Sentinel Language - Loops
+sidebar_current: docs-language-loops
+description: >-
+ Loop statements allow you to execute a body of code for each element in a
+ collection or for some fixed number of times.
+layout: docs
+---
+
+# Language: Loops
+
+Loop statements allow you to execute a body of code for each element in
+a collection or for some fixed number of times.
+
+Loop statements may only appear outside of
+[rule expressions](/sentinel/language/rules), such as in
+[functions](/sentinel/language/functions) or in the global scope
+of a policy. This is because rules are only allowed to contain a single
+boolean expression.
+
+## For Statements
+
+`for` statements allow repeated execution of a block for each
+element in a collection.
+
+Example:
+
+```sentinel
+// A basic sum
+count = 0
+for [1, 2, 3] as num {
+ count += num
+}
+```
+
+The syntax is `for COLLECTION as value`. This will iterate over the
+collection, assigning each element to `value`. In the example above,
+each element is assigned to `num`. The body is executed for each element.
+In the example above, the body adds `num` to the `count` variable. This
+creates a basic sum of all values in the collection.
+
+For a map, the assigned element is the key in the map. In the example
+below, `name` would be assigned map keys.
+
+```sentinel
+list = []
+for { "a": 1, "b": 2 } as name {
+ append(list, name)
+}
+
+print(list) // ["a" "b"]
+```
+
+An alternate syntax is `for COLLECTION as key, value`. This will assign
+both the key and value to a variable. For a list, the key is the element
+index. For a map, it is the key and value is assigned the element value.
+Example:
+
+```sentinel
+count = 0
+for { "a": 1, "b": 2 } as name, num {
+ count += num
+}
+
+print(count) // 3
+```
+
+## Scoping
+
+The body of a `for` statement creates a new
+[scope](/sentinel/language/scope). If a variable is assigned within
+the body of a for statement that isn't assigned in a parent scope,
+that variable will only exist for the duration of the body execution.
+
+Example:
+
+```sentinel
+for list as value {
+ a = 42
+}
+
+print(a) // undefined
+```
+
+```sentinel
+a = 18
+for list as value {
+ a = 42
+}
+
+print(a) // 18
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/language/maps.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/language/maps.mdx
new file mode 100644
index 0000000000..298bf9a567
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/language/maps.mdx
@@ -0,0 +1,121 @@
+---
+page_title: Sentinel Language - Maps
+sidebar_current: docs-language-maps
+description: >-
+ Maps are a collection of zero or more key/value pairs. These are useful for
+ storing values that are looked up by a unique key.
+layout: docs
+---
+
+# Language: Maps
+
+Maps are a collection of zero or more key/value pairs. These are useful
+for storing values that are looked up by a unique key.
+
+Maps can be created using `{}` and specifying key/value pairs. Keys and
+values can be differing types. An optional trailing comma is allowed.
+Examples:
+
+```sentinel
+// Empty map
+{}
+
+// Map with a single value on one line
+{ "key": "value" }
+
+// Map with multiple values with differing types on multiple lines
+{
+ "key": "value",
+ 42: true,
+}
+```
+
+Maps are **unordered**. When
+[looping](/sentinel/language/loops)
+over a map, the key/value pairs can be returned in any order.
+
+The [set operators](/sentinel/language/boolexpr#set-operators) can be used
+to test for key inclusion in a map.
+
+## Accessing Elements
+
+Map elements can be accessed with the syntax `name[key]`. This looks up
+a value by a key.
+
+Accessing a key that doesn't exist results in
+[undefined](/sentinel/language/undefined).
+
+Examples:
+
+```sentinel
+map = { "key": "value", 42: true, }
+
+map["key"] // "value"
+map[42] // true
+map[0] // undefined
+```
+
+## Modifying or Adding Elements
+
+Elements can be added or modified in a map by assigning to `name[key]`.
+If the key doesn't exist, the value is added. If the key already exists,
+the value is overridden.
+
+```sentinel
+map = { "key": "value" }
+
+map[42] = true // Add a new key/value
+map["key"] = 12 // Modify the value of "key"
+```
+
+## Deleting Elements
+
+An element can be deleted from a map using the
+[delete](/sentinel/functions/delete) function.
+
+Examples:
+
+```sentinel
+map = { "key": "value" }
+delete(map, "key") // map is now empty
+delete(map, "other") // no effect for non-existent key
+```
+
+## Keys and Values
+
+The keys and values of a map can be retrieved as
+[lists](/sentinel/language/lists) using the
+[keys](/sentinel/functions/keys) and
+[values](/sentinel/functions/values) functions.
+
+Because maps are unordered, the keys and values are returned in an
+unspecified order. It should not be assumed that keys and values will be
+returned in a consistent order.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+## Map Comparison
+
+Maps may be [compared](/sentinel/language/boolexpr#comparison-operators) for
+equality. Maps are equal if they are of equal length and both their
+corresponding keys and values are comparable and equal.
+
+```sentinel
+{"foo": "bar"} is {"foo": "bar"} // true
+{"foo": "bar"} is {"baz": "bar"} // false
+{"foo": "bar"} is {"foo": "baz"} // false
+{"foo": "bar"} is {"foo": "bar", "baz": "qux"} // false
+{1: "a"} is {1.0: "a"} // true (int/float comparable)
+
+// also true (maps are not ordered):
+{"m": {"a": "b"}, "l": ["a"]} is {"l": ["a"], "m": {"a": " b"}}
+```
+
+-> The current worst-case comparison speed for maps is O(N²), or _quadratic
+time_. This is the square of the cumulative elements or the parent and all child
+maps contained within the map. Consider this when making comparisons on larger,
+more complex maps. This may be optimized in the future.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/language/parameters.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/language/parameters.mdx
new file mode 100644
index 0000000000..2e008d469b
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/language/parameters.mdx
@@ -0,0 +1,146 @@
+---
+page_title: Sentinel Language - Parameters
+sidebar_current: docs-language-parameters
+description: >-
+ Parameteres allow a Sentinel policy to describe variables that are expected
+ to be supplied by the calling application, in addition to any default value.
+layout: docs
+---
+
+# Language: Parameters
+
+Sentinel allows a policy author to supply parameters to help facilitate policy
+reuse and ensure sensitive values do not need to be hard-coded in a policy.
+
+Parameters are supplied by using the `param` keyword, followed by an identifier.
+A default value can also be supplied by using the `default` keyword.
+
+```sentinel
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+```
+
+Once declared, parameters can be used like any other variable, including being
+re-assigned.
+
+```sentinel
+param foo default 1 // 1 (default)
+
+foo = foo + 1 // 2
+```
+
+## Variable Descriptions
+
+You can supply a description to a parameter by adding a comment at the top of
+it. This value can be communicated to a specific implementation of Sentinel to
+provide information about what the parameter is for during configuration.
+
+```sentinel
+// An example parameter. Must be supplied or the policy will fail.
+param foo
+```
+
+## Supplying Parameter Values Using the Sentinel CLI
+
+In a production implementation, supplying parameters to a policy is an
+implementation-specific detail - see the documentation for your particular
+implementation for details.
+
+Using the [Sentinel CLI](/sentinel/commands), you can supply parameters one of
+four ways.
+
+### Supplying Parameter Values Using the Configuration File
+
+You can supply parameters using the
+[`param`](/sentinel/configuration#parameters) section of the
+[configuration file](/sentinel/configuration).
+
+```hcl
+param "foo" {
+ value = "bar"
+}
+```
+
+This method works for both `sentinel apply` and `sentinel test`.
+
+In addition to the above, you can supply targeted parameters to each
+[policy block](/sentinel/configuration#policies) in the configuration file.
+
+```hcl
+policy "foo" {
+ source = "foo.sentinel"
+ enforcement_level = "hard-mandatory"
+ params = {
+ "name" = "Sample"
+ }
+}
+```
+
+### Supplying Parameter Values Using the Environment
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can supply a value using environment variables - prefix the parameter with
+`SENTINEL_PARAM_`, using the name of the parameter to supply.
+
+```plaintext
+SENTINEL_PARAM_foo=bar sentinel apply policy.sentinel
+```
+
+### Supplying Parameter Values Using CLI Arguments
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+You can also use the `-param` CLI argument to supply parameter in a `key=value`
+pair.
+
+```plaintext
+sentinel apply -param foo=bar policy.sentinel
+```
+
+### Interactive CLI Prompting
+
+-> **NOTE:** This method of supplying parameters is only supported by `sentinel apply`.
+
+If a required value has not been supplied when a policy is run with `sentinel apply`, it will be prompted for, along with its description:
+
+
+
+ $ sentinel apply policy.sentinel
+
+ policy.sentinel:2:7: requires value for parameter foo
+
+ An example parameter. Must be supplied or the policy will fail.
+
+
+ Values can be strings, floats, or JSON array or object values. To
+ force
+
+ strings, use quotes.
+
+
+ Enter a value: bar
+
+
+
+ Pass
+
+
+
+
+### CLI Value Format
+
+-> **NOTE:** This section contains details for the parameter features supported by `sentinel apply`.
+
+The CLI takes either strings, or JSON numbers, arrays, or maps. If you need a
+literal string value, quote the value.
+
+```sentinel
+foo // string
+42 // number (float)
+"42" // string ("42", without quotes)
+[1, 2] // array (list)
+{"a": "b"} // object (map)
+```
+
+-> **NOTE:** Boolean values are not supported by this method.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/language/rules.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/language/rules.mdx
new file mode 100644
index 0000000000..e31924d414
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/language/rules.mdx
@@ -0,0 +1,204 @@
+---
+page_title: Sentinel Language - Rules
+sidebar_current: docs-language-rules
+description: >-
+ Rules form the basis of a policy by representing behavior that is either
+ passing or failing (true or false).
+layout: docs
+---
+
+# Language: Rules
+
+Rules form the basis of a policy by representing behavior that is either passing
+or failing. A policy can be broken down into a set of rules. Breaking down a
+policy into a set of rules can make it more understandable and aids with
+testing.
+
+An example is shown below:
+
+```sentinel playground
+weather = "sunny"
+day = "wednesday"
+is_sunny = rule { weather is "sunny" }
+is_wednesday = rule { day is "wednesday" }
+
+main = rule {
+ is_sunny and
+ is_wednesday
+}
+```
+
+A rule contains a single expression. This can be a simple [boolean
+expression](/sentinel/language/boolexpr), or an expression representing the
+discovery of a set of violations using more in-depth expressions like [`filter`
+and `map`](/sentinel/language/collection-operations). You can split expressions
+into multiple lines for readability, as you would with any other expression
+within Sentinel.
+
+A rule is also _lazy_ and _memoized_. _Lazy_ means that the rule is only
+evaluated when it is actually required, not at the point that it is
+created. This is covered in more detail in the [lazy](#lazy-and-memoized) section.
+_Memoized_ means that the value is only computed once and then saved. Once
+a rule is evaluated, the result of it is reused anytime the rule is referenced
+again.
+
+## Making rules easier to understand
+
+Rules are an abstraction that make policies much more understandable
+by making it easier for humans to see what the policy is trying to do.
+
+For example, consider the policy before which doesn't abstract into rules:
+
+```sentinel
+main = rule {
+ ((day is "saturday" or day is "sunday") and homework is "") or
+ (day in ["monday", "tuesday", "wednesday", "thursday", "friday"] and
+ not school_today and homework is "")
+}
+```
+
+Assume that `day`, `homework`, and `school_day` are available.
+
+In plain English, this policy is trying to say: you can play with your friends
+only on the weekend as long as there is no homework, or during the week if
+there is no school and no homework.
+
+Despite the relatively simple nature of this policy (a few lines, about 5
+logical expressions), it is difficult to read and understand, especially if
+you've not seen it before.
+
+The same policy using rules as an abstraction:
+
+```sentinel
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+is_valid_weekend = rule { is_weekend and homework is "" }
+is_valid_weekday = rule { not is_weekend and not school_today and homework is "" }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+By reading the names of the rules, its much clearer to see what each individual
+part of the policy is trying to achieve. The readability is improved even
+further when adding comments to the rules to explain them further, which is
+a recommended practice:
+
+```sentinel
+// A weekend is Sat or Sun
+is_weekend = rule { day in ["saturday", "sunday"] }
+
+// A valid weekend is a weekend without homework
+is_valid_weekend = rule { is_weekend and homework is "" }
+
+// A valid weekday is a weekday without school
+is_valid_weekday = rule { not is_weekend and not school_today and homework is "" }
+
+main = rule { is_valid_weekend or is_valid_weekday }
+```
+
+## "When" Predicates
+
+A rule may have an optional `when` predicate attached to it. When this is
+present, the rule is only evaluated when the predicate results in `true`.
+Otherwise, the rule is not evaluated and the result of the rule is always
+`true`.
+
+"When" predicates should be used to define the context in which the rule
+is semantically meaningful. It is common when translating human language
+policy into Sentinel to have scenarios such as "when the key has a prefix
+of `/account/`, the remainder must be numeric." In that example, when the
+key _does not_ have the specified prefix, the policy doesn't apply.
+
+Assume you have rules `is_prefix` and `is_numeric` to check both the
+conditions in the example above. An example is shown with and without
+the "when" predicate:
+
+```sentinel
+example_no_when = rule { (is_prefix and is_numeric) or not is_prefix }
+
+example_when = rule when is_prefix { is_numeric }
+```
+
+The rules are equivalent in behavior for all values of `is_prefix` and
+`is_numeric`, but the second rule is more succint and easier to understand.
+
+## Testing
+
+To verify a policy works correct, the
+[built-in Sentinel test framework](/sentinel/writing/testing)
+uses rules as the point where you can assert behavior. You say that
+you expect certain rules to be true or false. By doing so, you ensure that
+the policy results in the value you expect using the rule flow that you
+expect.
+
+Therefore, in addition to readability, we recommend splitting policies into
+rules to aid testability.
+
+## Non-Boolean Values
+
+Sentinel supports non-boolean values in rules. This can be useful for passing
+more detail along to a calling integration when more detail is required than a
+boolean value would be able to communicate. This data shows up in the [policy
+trace](/sentinel/writing/tracing) and can be utilized in different ways
+depending on the integration you are using Sentinel with.
+
+Consider a different take on our policy above:
+
+```sentinel
+// A policy to check a set of scheduled days to determine what days you can't
+// come out and play, based on the day not being a weekend day and there being
+// homework to do.
+
+param days
+
+main = rule {
+ filter days as d {
+ d.day not in ["saturday", "sunday"] and
+ d.homework is not ""
+ }
+}
+```
+
+When given a value in the set of `days` that has a `day` that is a weekday, and
+`homework` present, the policy will fail. Additional, when tracing was enabled,
+the days that were detected as violating the policy would be given in the trace
+for the `main` rule.
+
+Generally, for non-boolean values, a policy fails on non-zero data. See the
+details on [the `main` rule](/sentinel/language#main) for more details.
+
+The result of a rule must be either a boolean, string, integer, float, list, or
+map. All other types will result in a runtime error.
+
+## Lazy and Memoized
+
+A rule is _lazy_ and _memoized_.
+
+_Lazy_ means that the rule is only evaluated when it is actually required,
+not at the point that it is created. And _memoized_ means that the value is
+only computed once and then saved. Once a rule is evaluated, the result of it
+is reused anytime the rule is referenced again.
+
+Both of these properties have important implications for Sentinel
+policies:
+
+**Performance:** Rules are a way to improve the performance of a Sentinel
+policy. If you have a boolean expression that is reused a lot, a rule will
+only be evaluated once. In the example shown above, `is_weekend` is used
+multiple times, but will only have to be evaluated once.
+
+**Behavior:** Rules accessing variables see the value of those variables
+at _the time they are evaluated_. This can lead to surprising behavior
+in some cases. For example:
+
+```sentinel playground
+a = 1
+b = rule { a == 1 }
+a = 2
+main = b
+```
+
+In this example, `main` will actually result to `false`. It is evaluated
+when it is needed, which is when the policy executes `main`. At this point,
+`a` is now 2 and `b` has not been evaluated yet. Therefore, `b` becomes `false`.
+All future references to `b` will return `false` since a rule is memoized.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/language/scope.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/language/scope.mdx
new file mode 100644
index 0000000000..f498e26dc2
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/language/scope.mdx
@@ -0,0 +1,47 @@
+---
+page_title: Sentinel Language - Scope
+sidebar_current: docs-language-scope
+description: Functions allow you to create reusable code to perform computations.
+layout: docs
+---
+
+# Language: Scope
+
+Scope is the context in which variables are created and accessed. If a
+variable exists in a parent scope, it is accessed and can be modified.
+Otherwise, the variable is created in your current scope.
+
+Scopes are created with _blocks_. A block is a possibly empty sequence
+of statements within matching brace brackets `{}`. You may nest blocks
+to create nested scopes.
+
+## Package Scope
+
+The package scope is the top level scope within a policy file and encapsulates
+the entire file contents. Imports, parameters and named functions must be
+declared within the package scope.
+
+## Implicit Scopes
+
+Each `any`, `all`, and `for` statement is considered to be in its own block.
+Note that `if` statements _do not_ create their own block.
+
+## Examples
+
+The example policy below shows various effects of scopes:
+
+```sentinel
+a = 1
+print(a) // 1
+
+f = func() {
+ print(a) // 1
+ a = 12
+ b = 1
+ return undefined
+}
+f()
+
+print(a) // 12
+print(b) // undefined
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/language/slices.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/language/slices.mdx
new file mode 100644
index 0000000000..82dc7f4c18
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/language/slices.mdx
@@ -0,0 +1,45 @@
+---
+page_title: Sentinel Language - Slices
+sidebar_current: docs-language-slices
+description: >-
+ Slices are an efficient way to create a substring or sublist from an existing
+ string or list, respectively.
+layout: docs
+---
+
+# Language: Slices
+
+Slices are an efficient way to create a substring or sublist from an
+existing string or list, respectively.
+
+The syntax for creating a slice is:
+
+```sentinel
+a[low : high]
+```
+
+`low` is the lowest index to slice from. The resulting slice includes
+the value at `low`. `high` is the index to slice to. The resulting slice
+will not include the value at `high`. The length of the resulting list
+or string is always `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+
+a = "hello"
+b = a[1:4] // "ell"
+```
+
+## Convenience Shorthands
+
+Some convenience shorthands are available by omitting the value for either the
+low or high index in the slice expression: omitting `low` implies a low index of
+0, and omitting `high` implies a high index of the length of the list.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+
+b = a[:2] // [1, 2] (same as a[0:2])
+b = a[2:] // [3, 4, 5] (same as a[2:length(a)])
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/language/spec.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/language/spec.mdx
new file mode 100644
index 0000000000..d8efb378cd
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/language/spec.mdx
@@ -0,0 +1,1333 @@
+---
+page_title: Sentinel Language Specification
+sidebar_current: docs-language-spec
+description: This is the specification for the Sentinel policy language.
+layout: docs
+---
+
+# Sentinel Language Specification
+
+This is the specification for the Sentinel policy language.
+
+The Sentinel language is designed with policy enforcement in mind. It is dynamically typed and garbage collected and has explicit support for _rule_ construction representing boolean logic.
+
+The language is designed to be easy to learn and use by non-programmers. It is expected to be embedded within applications.
+
+## Table of Contents
+
+
+
+
+- [Source code representation](#source-code-representation)
+- [Declarations and Scope](#declarations-and-scope)
+- [Blocks](#blocks)
+- [Lexical Elements](#lexical-elements)
+ - [Comments](#comments)
+ - [Identifiers](#identifiers)
+ - [Keywords](#keywords)
+ - [Operators and Delimiters](#operators-and-delimiters)
+ - [Integer Literals](#integer-literals)
+ - [Floating-point Literals](#floating-point-literals)
+ - [String Literals](#string-literals)
+ - [Implicit Line Joining](#implicit-line-joining)
+ - [Whitespace](#whitespace)
+ - [Semicolons](#semicolons)
+- [Variables](#variables)
+- [Undefined](#undefined)
+- [Expressions](#expressions)
+ - [Operand](#operand)
+ - [Primary Expressions](#primary-expressions)
+ - [Null](#null)
+ - [Booleans](#booleans)
+ - [Boolean Literals](#boolean-literals)
+ - [Boolean Expressions](#boolean-expressions)
+ - [List Literals](#list-literals)
+ - [Map Literals](#map-literals)
+ - [Function Literals](#function-literals)
+ - [Rule Expressions](#rule-expressions)
+ - [Index Expressions](#index-expressions)
+ - [Selectors](#selectors)
+ - [Slice Expressions](#slice-expressions)
+ - [Calls](#calls)
+ - [Operators](#operators)
+ - [Operator Precedence](#operator-precedence)
+ - [Arithmetic Operators](#arithmetic-operators)
+ - [Integer operators](#integer-operators)
+ - [Integer overflow](#integer-overflow)
+ - [Floating-point operators](#floating-point-operators)
+ - [String Concatenation](#string-concatenation)
+ - [List Concatenation](#list-concatenation)
+ - [Comparison Operators](#comparison-operators)
+ - [Logical Operators](#logical-operators)
+ - [Set Operators](#set-operators)
+ - [Matches Operator](#matches-operator)
+ - [Else Operator](#else-operator)
+ - [Quantifier Expressions (any, all, filter, map)](#quantifier-expressions-any-all-filter-map)
+- [Statements](#statements)
+ - [Expression Statements](#expression-statements)
+ - [Assignments](#assignments)
+ - [If Statements](#if-statements)
+ - [Case Statements](#case-statements)
+ - [For Statements](#for-statements)
+ - [Break Statements](#break-statements)
+ - [Continue Statements](#continue-statements)
+ - [Return Statements](#return-statements)
+- [Imports](#imports)
+- [Parameters](#parameters)
+- [Built-in Functions](#built-in-functions)
+ - [Length](#length)
+ - [Collections](#collections)
+ - [List Append](#list-append)
+ - [Map Delete](#map-delete)
+ - [Keys and Values](#keys-and-values)
+ - [Range](#range)
+ - [Type Conversion](#type-conversion)
+ - [Printing](#printing)
+ - [Errors](#errors)
+- [Program Execution](#program-execution)
+
+
+
+## Source code representation
+
+Source code is Unicode text encoded in UTF-8. A "character" referenced in this document refers to a Unicode code point. Each code point is distinct: upper and lower case letters are different characters.
+
+The underscore character \_ (U+005F) is considered a "letter".
+
+```ebnf
+letter = unicode_letter | "_" .
+decimal_digit = "0" … "9" .
+octal_digit = "0" … "7" .
+hex_digit = "0" … "9" | "A" … "F" | "a" … "f" .
+```
+
+## Declarations and Scope
+
+A declaration binds an identifier to a value. An identifier is declared at the point it is first assigned. An assignment is considered a first assignment if the identifier hasn't already been previously declared in the current scope or any parent scopes.
+
+The scope of an identifier is the extent of source text in which the identifier denotes the specified value. Sentinel is lexically scoped using blocks.
+
+An identifier declared in a block may be redeclared in an inner block. While the identifier of the inner declaration is in scope, it denotes the value assigned in the inner declaration.
+
+## Blocks
+
+A block is a possibly empty sequence of statements within matching brace brackets.
+
+```ebnf
+Block = "{" StatementList "}" .
+StatementList = { Statement ";" } .
+```
+
+Blocks nest and affect scoping.
+
+In addition to explicit blocks in the source code, there are implicit blocks:
+
+1. The universe block encompasses all source text.
+2. Each program has a program block containing all source text for that program.
+3. Each "any", "all", and "for" statements is considered to be in its own implicit block. "if" does not create an implicit block.
+
+## Lexical Elements
+
+### Comments
+
+Comments are sections of source text used for documentation.
+
+```plaintext
+# Single line comment
+// Single line comment
+/* multi
+line
+ comment */
+```
+
+Three forms of comments are supported: two single-line forms and one multi-line form. A single-line comment begins with the token `//` or `#`. Everything between the starting token and the end of the line is ignored.
+
+A multi-line comment begins with the token `/*` and ends with the token `*/`. Everything between `/*` and `*/` is ignored.
+
+A comment may not start inside a string literal or inside another comment.
+
+A multi-line comment containing no newlines acts like a space.
+
+### Identifiers
+
+Identifiers name program entities such as rules, variables, and functions. An identifier is a sequence of one or more letters and digits. The first character in an identifier must be a letter.
+
+```ebnf
+identifier = letter { letter | unicode_digit } .
+```
+
+```sentinel
+a
+_a
+_A
+αβ
+```
+
+#### Pre-Declared Identifiers
+
+The following identifiers are implicitly declared in the [universe block](#blocks):
+
+```plaintext
+Constants:
+ false null true undefined
+
+Functions:
+ append bool delete error float int keys length print range string values
+```
+
+### Keywords
+
+The following keywords are reserved and may not be used as identifiers:
+
+```plaintext
+all
+any
+as
+break
+case
+continue
+default
+else
+empty
+filter
+for
+func
+if
+import
+map
+param
+return
+rule
+when
+```
+
+### Operators and Delimiters
+
+The following character sequences represent operators, delimiters, and other special tokens:
+
+```plaintext
++
+-
+*
+/
+%
++=
+-=
+*=
+/=
+%=
+==
+<
+>
+=
+!
+!=
+<=
+>=
+( )
+[ ]
+{ }
+,
+.
+:
+;
+and
+contains
+else
+in
+is
+matches
+not
+or
+xor
+```
+
+As a special case, `else` is both a keyword and operator, depending on the context. See [Else Operator](#else-operator) for more details.
+
+### Integer Literals
+
+An integer literal is a sequence of digits representing an integer constant. An optional prefix sets a non-decimal base: `0` for octal, `0x` or `0X` for hexadecimal. In hexadecimal literals, letters `a-f` and `A-F` represents values 10 through 15.
+
+Integers are signed 64-bit values (-9223372036854775808 to 9223372036854775807).
+
+```ebnf
+int_lit = decimal_lit | octal_lit | hex_lit .
+decimal_lit = ( "1" … "9" ) { decimal_digit } .
+octal_lit = "0" { octal_digit } .
+hex_lit = "0" ( "x" | "X" ) hex_digit { hex_digit } .
+```
+
+```
+42
+0600
+0xBadFace
+170141183460469231731687303715884105727
+```
+
+### Floating-point Literals
+
+A floating-point literal is a decimal representation of a floating-point constant. It has an integer part, a decimal point, a fractional part, and an exponent part. The integer and fractional part comprise decimal digits; the exponent part is an e or E followed by an optionally signed decimal exponent. One of the integer part or the fractional part may be elided; one of the decimal point or the exponent may be elided.
+
+Floating-point numbers are IEEE-754 64-bit floating numbers.
+
+```ebnf
+float_lit = decimals "." [ decimals ] [ exponent ] |
+ decimals exponent |
+ "." decimals [ exponent ] .
+decimals = decimal_digit { decimal_digit } .
+exponent = ( "e" | "E" ) [ "+" | "-" ] decimals .
+```
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+### String Literals
+
+String literals are character sequences between double quotes, as in "bar". Within the quotes, any character may appear except newline and unescaped double quote. The text between the quotes forms the value of the literal.
+
+A multi-character sequence beginning with a backslash encode values in various formats.
+
+Backslash escapes allow arbitrary values to be encoded as ASCII text. There are four ways to represent the integer value as a numeric constant: `\x` followed by exactly two hexadecimal digits; `\u` followed by exactly four hexadecimal digits; `\U` followed by exactly eight hexadecimal digits, and a plain backslash `\` followed by exactly three octal digits. In each case the value of the literal is the value represented by the digits in the corresponding base.
+
+After a backslash, certain single-character escapes represent special values:
+
+```plaintext
+\a U+0007 alert or bell
+\b U+0008 backspace
+\f U+000C form feed
+\n U+000A line feed or newline
+\r U+000D carriage return
+\t U+0009 horizontal tab
+\v U+000b vertical tab
+\\ U+005c backslash
+\" U+0022 double quote
+```
+
+The three-digit octal (\nnn) and two-digit hexadecimal (\xnn) escapes represent individual bytes of the resulting string; all other escapes represent the (possibly multi-byte) UTF-8 encoding of individual characters. Thus inside a string literal \377 and \xFF represent a single byte of value 0xFF=255, while ÿ, \u00FF, \U000000FF and \xc3\xbf represent the two bytes 0xc3 0xbf of the UTF-8 encoding of character U+00FF.
+
+A string value is a (possibly empty) sequence of bytes. Strings are immutable: once created, it is impossible to change the contents of a string.
+
+The length of a string s (its size in bytes) can be discovered using the built-in function `length`. An individual character (of type string) can be accessed by integer indices 0 through `length(s)-1`.
+
+```ebnf
+string_lit = `"` { unicode_value | byte_value } `"` .
+unicode_value = unicode_char | little_u_value | big_u_value | escaped_char .
+byte_value = octal_byte_value | hex_byte_value .
+octal_byte_value = `\` octal_digit octal_digit octal_digit .
+hex_byte_value = `\` "x" hex_digit hex_digit .
+little_u_value = `\` "u" hex_digit hex_digit hex_digit hex_digit .
+big_u_value = `\` "U" hex_digit hex_digit hex_digit hex_digit
+ hex_digit hex_digit hex_digit hex_digit .
+escaped_char = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | `"` ) .
+```
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+### Implicit Line Joining
+
+Expressions can be split over more than one line. For example:
+
+```sentinel
+a or
+b or # This is a comment
+c
+```
+
+Implicitly continued lines can have trailing comments. Blank continued lines are allowed.
+
+### Whitespace
+
+Whitespace is needed to separate tokens, but no distinction is made between the number and combination of whitespace characters. Whitespace characters can be space (U+0020), horizontal tabs (U+0009), carriage returns (U+000D), and newlines (U+000A).
+
+```sentinel
+a or b
+
+a or b
+
+a or
+ b
+
+rule { a or b }
+
+rule {
+ a or b }
+
+rule {
+ a or
+ b
+}
+```
+
+### Semicolons
+
+The formal grammar uses semicolons ";" as terminators in a number of productions.
+It is idiomatic Sentinel source to omit semicolons in most cases.
+
+A semicolon is automatically inserted into the token stream immediately after
+a line's final token if that token is:
+
+- An identifier
+- An integer, float, or string literal
+- The keyword `break`, `continue`, or `return`
+- The delimiter `)`, `]`, or `}`
+
+## Variables
+
+A variable is a storage location for holding a value.
+
+A variable has a dynamic type, which is the concrete type of the value assigned to the variable at run time. The dynamic type may vary during execution and change as a result of further assignments.
+
+A variable's value is retrieved by referring to the variable in an expression; it is the most recent value assigned to the variable. If a variable has not yet been declared (initially assigned), it is an error.
+
+```sentinel
+x = 7 // x is type int
+x = "foo" // x is now type string
+
+x = y // error if y is not previously assigned
+```
+
+## Undefined
+
+The value denoted by the keyword `undefined` represents undefined behavior or values. It can be created directly using the keyword `undefined`. It is also returned as a result of expressions in specified cases.
+
+`undefined` is a valid operand for any operations. Only `undefined or true` will result in true. All other operations result in `undefined`. An exception is if `undefined` is not reached as a result of short-circuit operations.
+
+```sentinel
+undefined OR true = true
+undefined OR false = undefined
+undefined OR undefined = undefined
+undefined AND true = undefined
+undefined AND false = undefined
+undefined AND undefined = undefined
+undefined XOR true = undefined
+undefined XOR false = undefined
+undefined XOR undefined = undefined
+
+// Short-circuit examples
+false OR true OR undefined = true
+false OR undefined OR true = true
+true AND false AND undefined = false
+true AND undefined AND false = undefined
+
+// Non-logical operators
+undefined + 5 = undefined
+-undefined = undefined
+!undefined = undefined
+```
+
+If the result of the main rule is `undefined`, it is treated as `false` but is indicative of erroneous logic.
+
+## Expressions
+
+### Operand
+
+Operands denote the elementary values in an expression. An operand may be a literal, an identifier denoting a variable, rule, or function, or a parenthesized expression.
+
+```ebnf
+Operand = Literal | OperandName | MethodExpr | "(" Expression ")" .
+Literal = BasicLit | MapLit | ListLit | FunctionLit | RuleLit .
+BasicLit = int_lit | float_lit | string_lit .
+OperandName = identifier .
+```
+
+### Primary Expressions
+
+Primary expressions are the operands for unary and binary expressions.
+
+```ebnf
+PrimaryExpr =
+ Operand |
+ PrimaryExpr Selector |
+ PrimaryExpr Index |
+ PrimaryExpr Slice |
+ PrimaryExpr Arguments .
+
+Selector = "." identifier .
+Index = "[" Expression "]" .
+Slice = "[" [ Expression ] ":" [ Expression ] "]" .
+Arguments = "(" [ Expression { "," Expression } ] ")" .
+```
+
+```sentinel
+x
+2
+(s + ".txt")
+f(3.1415, true)
+m["foo"]
+s[i : j + 1]
+obj.color
+f.p[i].x()
+x is empty
+x is not empty
+```
+
+### Null
+
+The reserved word `null` denote the singleton value `null`. Null represents the explicit absence of a value. Behavior of `null` within expressions is specified explicitly for each expression.
+
+### Booleans
+
+### Boolean Literals
+
+The reserved words `true` and `false` denote objects that represent the boolean values `true` and `false`, respectively. These are boolean literals.
+
+```ebnf
+BoolLit = "true" | "false" .
+```
+
+#### Boolean Expressions
+
+Boolean expressions are expressions that must result in a boolean value. Any other value type becomes the `undefined` value.
+
+```ebnf
+BoolExpr = Expression .
+```
+
+### List Literals
+
+A list literal denotes a list, which is an integer indexed collection of values.
+
+```ebnf
+ListLit = "[" [ ElementList [ "," ] ] "]" .
+ElementList = Element { "," Element } .
+Element = Expression | LiteralValue .
+```
+
+A list may contain zero or more values. The number of values in a list is its length.
+
+A list has a set of indices. An empty list has an empty set of indices. A non-empty list has the index set `{0...n - 1}` where `n` is the length of the list. Attempting to access a list using an index that is not a member of its set of indices results in the `undefined` value.
+
+### Map Literals
+
+A map literal denotes a map, which is an unordered group of elements indexed by a set of unique keys.
+
+```ebnf
+MapLit = "{" [ KeyedElementList [ "," ] ] "}" .
+KeyedElementList = KeyedElement { "," KeyedElement } .
+KeyedElement = Element ":" Element .
+```
+
+Keys can only be a boolean, numeric, or string type.
+
+The value of a non-existent key is the `undefined` value.
+
+### Function Literals
+
+A function literal represents a function.
+
+```ebnf
+FunctionLit = "func" Function .
+Function = Parameters FunctionBody .
+FunctionBody = Block .
+Parameters = "(" [ IdentifierList [ "," ] ] ")" .
+IdentifierList = identifier { "," identifier } .
+```
+
+```sentinel
+func(a, b) { return b }
+```
+
+Function literals are only allowed in the file scope. They may refer to variables defined in surrounding blocks.
+
+A function must terminate with a return statement. If a function has no meaningful return value, it should return `undefined`.
+
+### Rule Expressions
+
+A rule is an expression that is evaluated lazily and the result is memoized.
+
+If the optional "when" predicate is present, the rule is evaluated only when
+the "when" boolean expression results in true. If the predicate is false,
+the rule is not evaluated and returns true. The predicate is evaluated when
+the rule would be evaluated; it is also lazy and memoized in the same way.
+
+```ebnf
+RuleExpr = "rule" [ "when" BoolExpr ] "{" Expr "}" .
+```
+
+```sentinel
+rule { x is y }
+
+rule {
+ x == "value" or
+ y == "other"
+}
+
+rule when x is y { y > 42 }
+
+rule { map ["a", "b", "c"] as _, id {
+ { "id": id }
+}}
+```
+
+### Index Expressions
+
+A primary expression of the form `a[x]` denotes the element of a list or map indexed by x. The value x is called the index or key.
+
+For `a` of type map:
+
+- If `a` contains a key `x`, `a[x]` is the map value with key `x`.
+- If `a` does not contain key `x`, `a[x]` is the `undefined` value.
+
+For `a` of type list:
+
+- `x` must be an integer
+- `x` must be in the range `[-1 * length(a), length(a)-1]`
+- If `x` is contained in the set of indices of `a`, `a[x]` is the list element at index `x`. If `x` is negative, `a[x]` is equivalent to `a[length(a)+x]`.
+- If `x` is not contained in the set of indices of `a`, `a[x]` is the `undefined` value.
+
+For `a` of value `null`:
+
+- `a[x]` is the `undefined` value.
+
+Otherwise `a[x]` is an error.
+
+### Selectors
+
+For a primary expression x, the selector expression `x.f` denotes the field `f` of the value `x`. The identifier `f` is called the selector. The type of the selector expression is the type of the selector.
+
+As a special case, selectors can be [reserved words](#keywords) and keyword [operators](#operators-and-delimiters), but cannot be any other non-identifier element.
+
+Selectors are used to access data from [imports](#imports). The first primary expression `x` in `x.f` denotes the import name. The field `f` is the selector to access data from the import.
+
+Selectors may also be used to access map data with static keys. They are syntactic sugar over index expressions. `math.pi` is equivalent to `math["pi"]` and exhibit the same limitations and behavior as an index expression. Selectors cannot, however, be used to assign values to map data.
+
+Selectors on undefined result in undefined.
+
+```sentinel
+math.pi
+time.pst.hour
+```
+
+### Slice Expressions
+
+Slice expressions construct a substring or list from a string or list.
+
+The primary expression
+
+```sentinel
+a[low : high]
+```
+
+constructs a substring or list. The indices `low` and `high` select which elements of operand `a` appear in the result. The result has indices starting at 0 and length equal to `high - low`.
+
+```sentinel
+a = [1, 2, 3, 4, 5]
+b = a[1:4] // [2, 3, 4]
+```
+
+For convenience, any of the indices may be omitted. A missing `low` index defaults to zero; a missing `high` index defaults to the length of the sliced operand:
+
+```sentinel
+a[2:] // same as a[2 : length(a)]
+a[:3] // same as a[0 : 3]
+a[:] // same as a[0 : length(a)]
+```
+
+The indices are in range if `0 <= low <= high <= length(a)`, otherwise they are out of range. If the indices are out of range at run time, the result is the `undefined` value.
+
+If `a` is the value `null`, the result is the `undefined` value.
+
+If `a` is any other value type, it is an error.
+
+### Calls
+
+```ebnf
+CallExpr = identifier Arguments .
+```
+
+Given an expression `f` where `f` is a function value:
+
+```sentinel
+f(a1, a2, … an)
+```
+
+calls `f` with arguments `a1, a2, ... an`. The type of the expression is the result of `f`. The arguments are evaluated left to right. Arguments are passed by value.
+
+### Operators
+
+```ebnf
+Expression = UnaryExpr | Expression binary_op Expression .
+UnaryExpr = PrimaryExpr | unary_op UnaryExpr .
+
+binary_op = logical_op | set_op | rel_op | add_op | mul_op | else_op .
+logical_op = "and" | "or" | "xor" .
+set_op = ["not"] ( "contains" | "in" ).
+rel_op = "==" | "!=" | "<" | "<=" | ">" | ">=" |
+ "is" | "is not" | "matches" | "not matches" .
+add_op = "+" | "-" .
+mul_op = "*" | "/" | "%" .
+else_op = "else" .
+unary_op = "+" | "-" | "!" | "not" | empty_op | defined_op .
+empty_op = "is empty" | "is not empty" .
+defined_op = "is defined" | "is not defined" .
+```
+
+#### Operator Precedence
+
+Unary operators have the highest precedence.
+
+```plaintext
+Precedence Operator
+ 6 * / %
+ 5 + -
+ 4 else
+ 3 == != < <= > >= "is" "is not" "matches" "contains" "in"
+ 2 and
+ 1 or xor
+```
+
+Binary operators of the same precedence associate from left to right. For instance, x / y _ z is the same as (x / y) _ z.
+
+### Arithmetic Operators
+
+Arithmetic operators apply to numeric values and yield a result of the same type when both operands are the same.
+
+Arithmetic operations between integer and floating-point types result in a floating-point value with the integer operand treated as a floating-point value for purposes of calculation.
+
+Arithmetic operations between numeric values (integer, floating-point) and non-numeric values (strings, lists) are not permitted.
+
+All five supported arithmetic operators (+, -, \*, /, %) apply to both integer and floating-point types; + also applies to strings.
+
+```plaintext
++ sum integers, floats, strings, lists
+- difference integers, floats
+* product integers, floats
+/ quotient integers, floats
+% remainder integers, floats
+```
+
+#### Integer operators
+
+For two integer values x and y, the integer quotient `q = x / y` and remainder `r = x % y` satisfy the following relationships:
+
+```sentinel
+x = q*y + r and |r| < |y|
+```
+
+with x / y truncated towards zero.
+
+```plaintext
+ x y x / y x % y
+ 5 3 1 2
+-5 3 -1 -2
+ 5 -3 -1 2
+-5 -3 1 -2
+```
+
+As an exception to this rule, if the dividend x is the most negative value for the int type of x (-9223372036854775808), the quotient `q = x / -1` is equal to x (and r = 0).
+
+If the divisor is a constant, it must not be zero. If the divisor is zero at run time, an error occurs.
+
+#### Integer overflow
+
+For signed integers, the operations `+`, `-`, and `*` may legally overflow and the resulting value exists and is deterministically defined by the signed integer representation, the operation, and its operands. No exception is raised as a result of overflow.
+
+#### Floating-point operators
+
+For floating-point numbers, +x is the same as x, while -x is the negation of x. The result of a floating-point division by zero is not specified beyond the IEEE-754 standard.
+
+#### String Concatenation
+
+Strings can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = "hi"
+y = "hello"
+x = x + ", " + y // "hi, hello"
+x += " and good bye" // "hi, hello and good bye"
+```
+
+String addition creates a new string by concatenating the operands.
+
+#### List Concatenation
+
+Lists can be concatenated using the `+` operator or the `+=` assignment operator:
+
+```sentinel
+x = [1, 2]
+x = x + [2, 3] // [1, 2, 2, 3]
+x += [4] // [1, 2, 2, 3, 4]
+```
+
+List addition creates a new list by concatenating the operands.
+
+### Comparison Operators
+
+Comparison operators compare two operands and yield a boolean value.
+
+```plaintext
+== equal
+!= not equal
+< less
+<= less or equal
+> greater
+>= greater or equal
+"is" equal
+"is not" not equal
+```
+
+In any comparison, the two operands must be equivalent types. The only exception are integers and floats, which are considered numeric and may be compared. Any comparison between other non-matching types results in the `undefined` value.
+
+The equality operators `==`, `!=`, `is`, and `is not` apply to operands that are comparable. The ordering operators `<`, `<=`, `>`, and `>=` apply to operands that are ordered. The behavior of `is` with `==` and `is not` with `!=` is identical. These terms and the result of the comparisons are defined as follows:
+
+- Boolean values are comparable. Two boolean values are equal if they are either both true or both false.
+- Integer values are comparable and ordered, in the usual way.
+- Floating point values are comparable and ordered, as defined by the IEEE-754 standard.
+- An integer compared with floating point value treats the integer as the converted floating point value.
+- String values are comparable and ordered, lexically byte-wise.
+- Lists are comparable. Lists are equal if they are of equal length and their
+ corresponding elements are comparable and equal.
+- Maps are comparable. Maps are equal if they are of equal length and both their
+ corresponding keys and values are comparable and equal.
+
+If either operand is the `undefined` value, the result of the expression is the `undefined` value.
+
+### Emptiness Comparisons
+
+Checking the emptiness of an object can be achieved by using one of the emptiness expressions, `is empty` or `is not empty`. Although similar to [Comparison Operators](#comparison-operators) in that they yield a boolean value and read as though a comparison is taking place, both `is empty` and `is not empty` are evaluated as a multi-word expression.
+
+An emptiness comparison can only be performed against collections, strings or `undefined`, with the same rules as the [built-in length](/sentinel/functions/length) function.
+
+```sentinel
+"" is empty // true
+"foo" is empty // false
+[] is empty // true
+[1] is empty // false
+{} is empty // true
+{"a": "b"} is empty // false
+
+"" is not empty // false
+"foo" is not empty // true
+[] is not empty // false
+[1] is not empty // true
+{} is not empty // false
+{"a": "b"} is not empty // true
+
+undefined is empty // undefined
+undefined is not empty // undefined
+```
+
+### Logical Operators
+
+Logical operators apply to boolean values and yield a boolean result.
+
+Logical operators are evaluated left-to-right and perform short-circuit logic. The right-hand side is not guaranteed to be evaluated if a short-circuit can be performed.
+
+```plaintext
+and conditional AND p and q is "if p then q else false"
+or conditional OR p or q is "if p then true else q"
+xor conditional XOR p xor q is "if p and not q or not p and q then true"
+! NOT !p is "not p"
+not NOT !p is "not p"
+```
+
+### Set Operators
+
+The set operators `contains` and `in` test for set inclusion for lists
+and maps, and substring inclusion for strings.
+
+Set operators may be negated by prefixing the operator with `not`: `not contains` and `not in`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+`contains` tests if the left-hand collection contains the right-hand value. For lists, this tests equality of any value. For maps, this tests equality of any key. For strings, this tests for substring existence.
+
+`in` tests if the right-hand collection contains the left-hand value. The behavior is equivalent to `contains`.
+
+The collection must be a list, map, or string. If it is the `undefined` value, the result is the `undefined` value. For any other value, the result is an error.
+
+```sentinel
+[1, 2, 3] contains 2 // true
+[1, 2, 3] contains 5 // false
+[1, 2, 3] contains "value" // false
+[1, 2, 3] not contains "value" // true
+
+{ "a": 1, "b": 2 } contains "a" // true
+{ "a": 1, "b": 2 } contains "c" // false
+{ "a": 1, "b": 2 } contains 2 // false
+{ "a": 1, "b": 2 } not contains 2 // true
+
+"test" contains "est" // true
+"test" contains "best" // false
+"test" in "testing" // true
+"best" in "testing" // false
+```
+
+### Matches Operator
+
+The matches operator tests if a string matches a regular expression.
+
+The matches operators may be negated by prefixing the operator with `not`: `not matches`. This is equivalent to wrapping the binary expression in a unary `not` but results in a more readable form.
+
+The left-hand value is the string to test. The right-hand value is a string value representing a regular expression.
+
+The syntax of the regular expression is the same general syntax used by Python, Ruby, and other languages. More precisely, it is the syntax accepted by RE2. The regular expression is not anchored by default; any anchoring must be explicitly specified.
+
+```sentinel
+"test" matches "e" // true
+"test" matches "^e" // false
+"TEST" matches "test" // false
+"TEST" matches "(?i)test" // true
+"ABC123" matches "[A-Z]+\\d+" // true
+"test" not matches "e" // false
+```
+
+If either operand is `undefined`, the result is the `undefined` value. For any other non-string value, the result is an error.
+
+### Else Operator
+
+Else operators can be used to provide a default value for an expression that may evaluate to the `undefined` value. Else operators return their left-hand value unless it is `undefined`, otherwise they return their right-hand value.
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else null
+```
+
+### Quantifier Expressions (any, all, filter, map)
+
+Quantifiers are expressions that apply a predicate to a collection (list or map). The purpose of the quantifier is to produce a result based on its particular type.
+
+`any` and `all` expressions are existential and universal quantifiers, respectively. `any` expressions are equivalent to a chain of `or` and `all` expressions are equivalent to a chain of `and`. Both expressions implement short-circuiting equivalent to logical operators.
+
+The `map` expression is an apply-to-all quantifier and returns a new list for all values in the input collection. The output list will be the same length as the input collection.
+
+The `filter` expression is a subset quantifier and returns the subset of all values in the input collection for which the supplied predicate applies. This will be zero or more elements, up to the length of the input.
+
+The body of a quantifier expression is a boolean expression.
+
+`any` returns the boolean value `true` if any value in the collection expression results in the body expression evaluating to `true`. If the body expression evaluates to `false` for all values, the any expression returns `false`.
+
+`all` returns the boolean `true` if all values in the collection expression result in the body expression evaluating to `true`. If any value in the collection expression result in the body expression evaluating to `false`, the all expression returns `false`.
+
+For empty collections, `any` returns false and `all` returns `true`.
+
+`map` always returns a list, regardless of if the input collection is a list itself; maps (as in the data type) also return lists when processed through `map`. Each element is the result of the supplied expression body.
+
+`filter` returns a list or map, the same type as its input collection. Only the elements for which the expression body evaluated to `true` will be returned. If an iteration of the expression body results in `undefined`, the entire result set is `undefined`.
+
+When the collection is a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When the collection is a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+QuantExpr = QuantOp Expression "as" [ identifier "," ] identifier "{" BoolExpr "}" .
+QuantOp = "all" | "any" | "filter" | "map" .
+```
+
+```sentinel
+all group.tasks as t { t.driver is "vmware" } // true if every iterations is true
+any group.tasks as t { t.driver is "vmware" } // true if any iteration is true
+map group.tasks as t { { "driver": t.driver } } // [{"driver": "vmware"}, ...]
+filter group.tasks as t { t.driver is "vmware" } // subset of tasks that is true
+```
+
+## Statements
+
+### Expression Statements
+
+Function call expressions may exist in the statement context.
+
+```ebnf
+ExpressionStmt = Expression .
+```
+
+### Assignments
+
+Assignments set the value of an expression to the value of another expression.
+
+```ebnf
+Assignment = AssignExpr assign_op Expression .
+AssignExpr = identifier | IndexExpr .
+assign_op = [ add_op | mul_op ] "=" .
+```
+
+The assignment `x op= y` is equivalent to `x = x op (y)` for supported values of `op`.
+
+```sentinel
+a = 1
+b[12] = 42
+c["key"] = "value"
+
+a *= 12
+b[12] += 8
+```
+
+Assignments to lists and maps can be carried out using [index expressions](#index-expressions), with the operands of the index expression being evaluated alongside the right hand side expression in a right-to-left (RHS, LHS) order.
+
+In addition to the rules detailed in the section describing their use, the following rules apply when assigning to maps or lists using index expressions:
+
+- The [variable](#variables) must exist and be a list or map. Attempts to use an index assignment on an unknown variable, or a variable that not a list or map, results in a runtime error.
+- Assigning to a list or map index that already exists overwrites that index's data with evaluated right hand side's value.
+- Assigning to an unknown key in a map creates a key for that map with the evaluated right hand side's value assigned to that key.
+- Attempting to assign a value to a list index that is out of range results in a runtime error.
+
+### If Statements
+
+If statements specify the conditional execution of two branches according to the value of a boolean expression. If the expression evaluates to true, the "if" branch is executed, otherwise, if present, the "else" branch is executed.
+
+```ebnf
+IfStmt = "if" BoolExpr Block [ "else" ( IfStmt | Block ) ] .
+```
+
+```sentinel
+if x < y {
+ return x
+} else if x > z {
+ return z
+} else {
+ return y
+}
+```
+
+### Case Statements
+
+Case statements specify a selection control mechanism to conditionally execute a branch based on expression equality.
+
+Each clause that wishes to provide an expression must use the "when" keyword. Multiple expressions can be provided, separated with a comma (",").
+
+There can be one, optional, "else" clause within a `case` statement. The "else" clause is executed if all other clauses failed to run.
+
+The clauses are evaluated in the order they are listed, with the first successful evaluation being executed.
+
+A `case` statement can optionally provide an expression. If no expression is provided, it is the equivalent of writing `case true {`.
+
+```ebnf
+CaseStmt = "case" [ Expression ] "{" { CaseWhenClause } "}" .
+CaseWhenClause = CaseWhenCase ":" StatementList .
+CaseWhenCase = "when" Expression { "," Expression } | "else" .
+```
+
+```sentinel
+case x {
+when y, z:
+ return true
+else:
+ return false
+}
+
+case {
+when x > 42:
+ return true
+else:
+ return false
+}
+```
+
+### For Statements
+
+A `for` statement specifies repeated execution of a block.
+
+The expression must evaluate to a list or a map.
+
+When iterating over a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.
+
+When iterating over a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.
+
+```ebnf
+ForStmt = "for" Expressions "as" [ identifier "," ] identifier Block .
+```
+
+```sentinel
+for [1, 2, 3] as v { count += v } // basic sum
+
+for [1, 2, 3] as idx, v {
+ if idx > 1 { count += v }
+}
+
+data = { "a": 12, "b": 32 }
+for data as k { count += data[k] } // sum map values
+for data as k, v { count += v }
+```
+
+### Break Statements
+
+A `break` statement terminates execution of the innermost `for` statement
+within the same function.
+
+```ebnf
+BreakStmt = "break" .
+```
+
+```sentinel
+// Will only print "1"
+for [1, 2, 3] as v {
+ print(v)
+ break
+}
+```
+
+### Continue Statements
+
+A `continue` statement begins the next iteration of the innermost `for` loop.
+The `for` loop must be within the same function.
+
+```ebnf
+ContinueStmt = "continue" .
+```
+
+```sentinel
+// Will print 1, 3
+for [1, 2, 3] as v {
+ if v == 2 {
+ continue
+ }
+
+ print(v)
+ break
+}
+```
+
+### Return Statements
+
+A return statement in a function F terminates the execution of F and provides the result value.
+
+```ebnf
+ReturnStmt = "return" Expression .
+```
+
+A function must terminate with a return statement.
+
+The return statement can be invoked at any time during a function to terminate execution.
+
+## Imports
+
+An import adds an assignment to the global scope to external definitions.
+
+The import declarations must only appear at the top of the source file, before any other statements. Comments may appear before imports.
+
+```ebnf
+ImportDecl = "import" Name ["as" identifier] .
+Name = string_lit .
+```
+
+An import name and identifier must be distinct. Two names may not repeated even if they're declared with different identifiers.
+
+If an identifier is not specified, the identifier is the name of the import itself.
+
+An import is not a valid value. The identifier representing the import may not be used as an expression, such as in assignment statements or call expressions.
+
+Values in imports are not assignable directly via their selectors. Function calls may be used to alter the state of the external data represented by the import. Whether or not this is supported is specific to the import implementation. The exception to this assignment restriction is the memoized data returned by a call to an import (see below).
+
+Data returned by imports can be memoized, meaning that data is returned by the import in the form of a map which is then used as much as possible without having to return to the import for more data. For example, `t = time.now` returns a map so that `t.hour` will be able to be looked up without having to go back to the import for that data.
+
+Data returned by imports may be callable, meaning that methods may be called on the memoized data returned by an import. For example, given `t = time.now`, `t.after(some_previous_time)` is valid, with the receiver data being set to the local memoized data stored in `t`. It is a valid case to accept modifications to the receiver data (example: assignment to the memoized data via index expressions), as long as all data in the receiver continues to be string-keyed. It is an invalid case to accept receiver data with non-string-typed keys. Methods may also alter the contents of receiver data so that it is different after the call is finished.
+
+As with methods, imports may also have callable keys where the data may not be memoized. Example: in the event of `v = foo.bar`, if `v.baz` is not included in the memoized data, a call will be made to the import to fetch it. The same rules and restrictions to receiver data apply to callable keys as do to method calls.
+
+The support for callable methods and non-memoized keys on data returned by imports is specific to the import implementation.
+
+The handling of import loading is specific to the runtime implementation.
+
+Implicit imports may be added by the runtime, for example for standard library definitions. An explicit import of the same name should override a matching implicit import. For example, if "time" is an implicit import and the program specifies `import "time" as cal`, then only `cal` is defined.
+
+```sentinel
+import "time"
+import "math" as m
+```
+
+## Parameters
+
+```ebnf
+ParamDecl = "param" identifier [ "default" Expression ]
+```
+
+A parameter is a way for a policy to describe variables that are expected to be supplied by the calling application, in addition to any default value.
+
+Parameter declarations must appear at the top of the source file, following imports.
+
+Like imports, comments may appear before parameters; this is mainly pertinent when the policy is not importing anything, which would make parameters the first declarations that appear in a policy.
+
+A parameter declaration describes a valid variable that is added to the scope of a policy at runtime. This variable has the same properties and rules as other variables assigned during the course of policy evaluation as defined by the specification. This includes having a dynamic type and being able to be re-assigned during execution.
+
+Parameters may only be strings, integers, floating point numbers, booleans, lists, or maps. More complex types such as rules or functions are not allowed.
+
+A parameter can specify a default value by adding one in the declaration via use of the "default" keyword, and supplying a literal for the default value. The literal for a default is a very limited expression, comprising of:
+
+- For strings, a simple string literal;
+- For integers and floating-point numbers, either a literal denoting the value, or a unary expression with the literal, and the operators - or +;
+- For booleans, the pre-declared identifiers true or false;
+- For lists and maps, their respective literals composed of values and keys (for maps) of the above values only.
+
+Any other type of expression or identifier is not allowed.
+
+A parameter that does not have a default value defined is required by the policy. Evaluating a policy with a required parameter that has not been supplied at execution results in a runtime error.
+
+Parameters must not conflict with imports or any other identifiers currently defined in the global scope, including any reserved or pre-declared identifiers. Attempting to assign a parameter to an existing value results in a runtime error.
+
+```sentinel
+import "time"
+
+param foo // assigned to foo, required
+param bar default 42 // assigned to bar, optional, default 42
+param time default "11:00" // error, conflicts with "time" import
+param undefined // error, reserved identifier
+```
+
+## Built-in Functions
+
+Built-in functions are predeclared. They are called like any other function.
+
+### Length
+
+The built-in function `length` returns the length of a collection of string.
+
+The length of a string is the number of bytes in the string. It is not necessarilly the number of characters.
+
+The length of a collection is the number of elements in that collection.
+
+The length of `undefined` is `undefined`.
+
+### Collections
+
+#### List Append
+
+The built-in function `append` appends a value to the end of a list in-place.
+
+Appending to a non-list value results in an immediate `fail`.
+
+The return value of `append` is always the `undefined` value.
+
+```sentinel
+append([1,2], 3) // [1, 2, 3]
+append(1, 3) // error()
+append(undefined, 3) // error()
+append([], undefined) // [undefined] (a list containing `undefined`)
+```
+
+#### Map Delete
+
+The built-in function `delete` deletes elements from a map by key. The map is modified in-place.
+
+Deleting a key that does not exist does nothing.
+
+Calling delete for a non-map value results in an immediate `fail`.
+
+The return value of `delete` is always the `undefined` value.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+delete(data, "a") // data is now { "b": 3 }
+delete(data, "c") // data is unchanged
+delete(1, "a") // error()
+delete(undefined, "b") // error()
+```
+
+#### Keys and Values
+
+The built-in function `keys` and `values` return the keys and values of a map, respectively. The return value is a list. Both functions return values in an unspecified order.
+
+The `keys` or `values` of `undefined` is `undefined`.
+
+```sentinel
+data = { "a": 2, "b": 3 }
+keys(data) // ["b", "a"]
+values(data) // [2, 3]
+```
+
+### Range
+
+The built-in function `range` returns a list of numbers in a range.
+
+There are three ways to call this function:
+
+```sentinel
+range(end)
+range(start, end)
+range(start, end, step)
+```
+
+The `start` is inclusive, the `end` is exclusive.
+
+If `start` is not provided, it defaults to `0`. If `step` is not provided, it defaults to `1`.
+
+```sentinel
+range(5) // [0,1,2,3,4]
+range(1, 5) // [1,2,3,4]
+range(1, 5, 2) // [1,3]
+range(0, -3, -1) // [0,-1,-2]
+```
+
+### Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a value of that type according to the rules below.
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
+
+### Printing
+
+The built-in function `print` can be used to output formatted debug information. The runtime may omit print function calls depending on its execution mode. The runtime may choose where the output of a print function goes.
+
+`print` is a variadic function, accepting one or more values to be printed; values are separated by a single whitespace character (`" "`).
+
+The return value of `print` is always `true` to allow it to be used in boolean expressions.
+
+```sentinel
+print("hello") // "hello"
+print("hello", "world") // "hello world"
+print("The", "number", "is", 42) // "The number is 42"
+print([1, 2, 3]) // "[1, 2, 3]"
+one_is_zero = rule { 1 == 0 }
+print(one_is_zero) // "false"
+```
+
+### Errors
+
+The built-in function `error` is equivalent to `print` but immediately causes execution to halt and the main rule to evaluate to `false`. The program execution is considered to have failed.
+
+## Program Execution
+
+Program execution begins by evaluating the source top to bottom. If evaluation is successful, the `main` rule is evaluated and its result is returned. Execution can be interrupted at any time by an error, which can be called explicitly or implicitly through illegal or undefined behavior.
+
+---
+
+> The content of this page is licensed under the [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
+>
+> Based on [The Go Programming Language Specification](https://golang.org/ref/spec) licensed under [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/language/undefined.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/language/undefined.mdx
new file mode 100644
index 0000000000..dc51531783
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/language/undefined.mdx
@@ -0,0 +1,97 @@
+---
+page_title: Sentinel Language - Undefined
+sidebar_current: docs-language-undefined
+description: >-
+ The value `undefined` is a special value representing undefined behavior or an
+ unknown value. Any operation on an `undefined` value results in `undefined`.
+layout: docs
+---
+
+# Language: Undefined
+
+The value `undefined` is a special value representing undefined behavior
+or an unknown value.
+
+Undefined is not an error because there are situations where the undefined
+value can be safely ignored and the language also provides construts for
+recovering from an undefined value.
+
+The undefined value can be created manually with `undefined`. In most cases,
+undefined is created by accessing a non-existent list element or value.
+The undefined value is created in a number of other circumstances. The documentation
+will note when an undefined value may be created.
+
+Almost any operation with `undefined` results in `undefined`. For example,
+performing math on undefined, attempting to call a function that is undefined,
+etc.
+
+## Main and Undefined
+
+If the value `undefined` is returned from the top-level `main` rule, then
+the policy is considered `false`. However, the runtime provides mechanisms
+for the host application to determine the policy failure was caused by
+an undefined value and report that to the user.
+
+The `main` rule returning the undefined value should be considered a logical
+error in most cases and should probably be corrected by the policy writer.
+
+## Debugging Undefined
+
+When an `undefined` value is first created (either explicitly or implicitly),
+the runtime will record the position where the undefined was created. This
+location can be used to find logic that could be erroneous.
+
+## Boolean Logic and Undefined
+
+Boolean logic is one way to safely recover from `undefined`. If boolean
+logic can safely determine a result despite an undefined value, that result
+will be returned.
+
+For example: `undefined or true` will result in `true`, because no matter
+what actual value `undefined` could've been if the policy behaved properly,
+the boolean expression would still be true.
+
+Another scenario where `undefined` can be safely ignored is short-circuit
+logic with `and`. `false and undefined` will result in `false`.
+
+The full table of logical operations on `undefined` is below:
+
+```sentinel
+undefined or true = true
+undefined or false = undefined
+undefined or undefined = undefined
+undefined and true = undefined
+undefined and false = undefined
+undefined and undefined = undefined
+undefined xor true = undefined
+undefined xor false = undefined
+undefined xor undefined = undefined
+
+// Short-circuit examples
+false or true or undefined = true
+false or undefined or true = true
+true and false and undefined = false
+true and undefined and false = undefined
+```
+
+## Else Expressions
+
+The `else` expression allows explicit recovery from undefined and is useful
+for setting default values.
+
+`else` is an operator where the left and right hand side are values. If the
+left-hand value is `undefined`, the right-hand value is returned. Otherwise,
+the left-hand value is returned.
+
+Examples:
+
+```sentinel
+foo() else 42
+foo.bar else ""
+config["bad-key"] else "default"
+
+// In more complex scenarios
+
+foo() else 42 == 12
+a = config.value else "default"
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/language/values.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/language/values.mdx
new file mode 100644
index 0000000000..f932480340
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/language/values.mdx
@@ -0,0 +1,210 @@
+---
+page_title: Sentinel Language - Values
+sidebar_current: docs-language-values
+description: Values are the data that Sentinel policies operate on.
+layout: docs
+---
+
+# Language: Values
+
+Values are the _data_ that Sentinel policies operate on. You can create this
+data yourself, for example by just typing the number `42`, or you may access
+this data from an external source to make policy decisions.
+
+Values have a _type_, which determines what kind of operations can be performed
+on the data. Example types are booleans (true and false), numbers,
+and strings (text).
+
+This page documents all the available value types. You can also
+[convert between types](#type-conversion).
+
+## Boolean
+
+A boolean is a value that is either true or false.
+
+A boolean value is created literally with `true` or `false`.
+
+As a policy language, booleans are central to the behavior of Sentinel.
+Booleans are used as conditions in [if statements](/sentinel/language/conditionals),
+are the result of [rules](/sentinel/language/rules), and more. The ultimate
+result of a Sentinel policy is true or false.
+
+## Integer
+
+An integer is a whole number.
+
+An integer is a 64-bit value. This means it can represent numbers from
+-9,223,372,036,854,775,808 to 9,223,372,036,854,775,808.
+
+Integers are created by typing them out literally with no
+separators (such as a comma). An optional prefix can set a non-decimal base:
+`0` for octal, and `0x` for hexadecimal. In hexadecimal literals, letters
+`a-f` and `A-F` represent values 10 to 15.
+
+Example integers are shown below:
+
+```sentinel
+42
+0600
+0xBadFace
+170141183460469
+```
+
+Integers are used for math and numerical comparison.
+
+## Float
+
+A float is a number with a decimal point. It has an integer part, a decimal
+point, a fractional part, and optionally an exponent part. Example
+floats are shown below:
+
+```sentinel
+0.
+72.40
+072.40 // == 72.40
+2.71828
+1.e+0
+6.67428e-11
+1E6
+.25
+.12345E+5
+```
+
+Floats are IEEE-754 64-bit floating point numbers.
+
+## String
+
+Strings are text values.
+
+Strings are created by wrapping text in double quotes, such as `"hello"`.
+Within the quotes, any character may appear except newline and an unescaped
+double quote. The text between the quotes is the value.
+
+Because Sentinel policies must be UTF-8 encoded text, strings themselves are
+UTF-8 encoded text. This means you can put any value UTF-8 character into
+a string, such as `"日本語"`.
+
+You can have a string with a literal double-quote by _escaping_ it with
+a backslash. For example: `"they said \"hello\""` turns into the value
+`they said "hello"`.
+
+Backslash escapes can be used for much more than only escaping a double quote.
+Newlines are `\n`, a literal backslash is `\\`, and many more. The full list
+of available backslash escapes can be found
+[in the language specification](/sentinel/language/spec#string-literals).
+
+Example strings:
+
+```sentinel
+`abc` // same as "abc"
+`\n
+\n` // same as "\\n\n\\n"
+"\n"
+"\"" // same as `"`
+"Hello, world!\n"
+"日本語"
+"\u65e5本\U00008a9e"
+"\xff\u00FF"
+"\uD800" // illegal: surrogate half
+"\U00110000" // illegal: invalid Unicode code point
+```
+
+Strings do not support indexing, however it is possible to achieve a similar
+result through the combination of the [strings](/sentinel/imports/strings)
+standard import and list indexing.
+
+```sentinel playground
+import "strings"
+
+asciistr = "Hello, world!"
+utf8str = "日本語"
+
+utf8split = rule {
+ strings.split(utf8str, "")[2] == "語"
+}
+
+asciisplit = rule {
+ strings.split(asciistr, "")[5] == ","
+}
+
+main = rule { utf8split and asciisplit }
+```
+
+Strings support [slicing](/sentinel/language/slices) to efficiently create
+substrings.
+
+## List
+
+See [Lists](/sentinel/language/lists).
+
+## Map
+
+See [Maps](/sentinel/language/maps).
+
+## Rule
+
+See [Rules](/sentinel/language/rules).
+
+## Function
+
+See [Functions](/sentinel/language/functions).
+
+## Type Conversion
+
+The built-in functions `int`, `float`, `string`, and `bool` convert a value to a
+value of that type. Some examples are shown below followed by a list of exact
+rules for type conversion.
+
+```sentinel
+int(42) // 42
+int("42") // 42
+int(42.8) // 42
+int(true) // 1
+
+float(1.2) // 1.2
+float(1) // 1.0
+float("4.2") // 4.2
+float(true) // 1.0
+
+string("foo") // "foo"
+string(88) // "88"
+string(0xF) // "15"
+string(true) // "true"
+
+bool("true") // true
+bool(1) // true
+bool(-1) // true
+bool(0.1) // true
+bool("false") // false
+bool(0) // false
+```
+
+For `int`:
+
+- Integer values are unchanged
+- String values are converted according to the syntax of integer literals
+- Float values are rounded down to their nearest integer value
+- Boolean values are converted to `1` for `true`, and `0` for `false`
+
+For `float`:
+
+- Float values are unchanged
+- Integer values are converted to the nearest equivalent floating point value
+- String values are converted according to the syntax of float literals
+- Boolean values are converted to `1.0` for `true`, and `0.0` for `false`
+
+For `string`:
+
+- String values are unchanged
+- Integer values are converted to the base 10 string representation
+- Float values are converted to a string formatted `xxx.xxx` with a precision of 6. This is equivalent to `%f` for C's sprintf.
+- Boolean values are converted to `"true"` for `true`, and `"false"` for `false`
+
+For `bool`:
+
+- The following string values convert to `true`: `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, and `"True"`
+- The following string values convert to `false`: `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, and `"False"`
+- Any non-zero integer or float value converts to `true`
+- Any zero integer or float value converts to `false`
+
+For any other unspecified type, the result is the `undefined` value.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/language/variables.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/language/variables.mdx
new file mode 100644
index 0000000000..7516890212
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/language/variables.mdx
@@ -0,0 +1,99 @@
+---
+page_title: Sentinel Language - Variables
+sidebar_current: docs-language-vars
+description: Variables store values that can be used later.
+layout: docs
+---
+
+# Language: Variables
+
+Variables store values that can be used later.
+
+Most notably, a variable assignment of `main` is required for all
+Sentinel policies. This is the main [rule](/sentinel/language/rules) that
+is executed to determine the result of a policy. The `main` rule may
+use other variables that are assigned in the policy.
+
+Example:
+
+```sentinel
+a = 1
+b = a + 1
+```
+
+In this example, two variables are assigned: `a` and `b`. `a` is given
+the value of "1" and `b` uses the `a` to calculate its own value.
+
+## Syntax
+
+The syntax for assigning a variable is:
+
+```sentinel
+IDENTIFIER = VALUE
+```
+
+On the left of the equal sign is an _identifier_. This is a name for your
+variable and will be how you reference it later. An identifier is any
+combination of letters and digits, but must start with a letter. An
+underscore `_` is also a valid letter.
+
+On the right is any valid Sentinel value or expression. A value is a
+literal value such as a number or string. An expression is some computed
+value such as doing math, calling a function, etc.
+
+## Assignment and Reassignment
+
+A variable is _assigned_ when it is given a value. You can also _reassign_
+variables at any time by setting it to a new value. The new value takes
+effect for any subsequent use of that variable. A variable can be reassigned
+to a different type.
+
+For example:
+
+```sentinel
+a = 1 // a = 1
+b = a // b = 1
+a = "value" // a = "value", b = 1
+c = a // c = "value", b = 1
+```
+
+In the above example you can see that the variables are set and reassigned.
+Notice that the value of a variable is the _current_ value, and that reassigning
+a variable only affects _future_ uses of that variable. You can see this with
+`c` and `b` being different values.
+
+## Unassigned Variables
+
+Using a variable that is _unassigned_ is an error.
+
+In the example below, the first line would result in the policy erroring.
+Sentinel is executed top-down and the value of `c` is not available yet
+on the first line.
+
+```sentinel
+a = c // Error!
+c = 1
+```
+
+## Type Conversion
+
+While a topic more related to [values][lang-values], variables can be assigned
+(or-reassigned) a different value type via type conversion.
+
+[lang-values]: /sentinel/language/values
+
+```sentinel
+s = "1.1"
+a = int(s) // a = 1
+a = float(s) // a = 1.1
+
+a = 1
+s = string(a) // s = "1"
+```
+
+For more details, see the type conversion sections in the
+[values][lang-values-type-conversion] page, or the [Sentinel Language
+Specification][lang-spec-type-conversion].
+
+[lang-values-type-conversion]: /sentinel/language/values#type-conversion
+[lang-spec-type-conversion]: /sentinel/language/spec#type-conversion
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/nomad.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/nomad.mdx
new file mode 100644
index 0000000000..a105b0337d
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/nomad.mdx
@@ -0,0 +1,51 @@
+---
+page_title: Nomad and Sentinel
+sidebar_current: docs-app-nomad
+description: >-
+ Nomad Enterprise uses Sentinel to augment the built-in ACL system to provide
+ advanced policy enforcement. Sentinel policies can currently execute on job
+ submission (creation, update).
+layout: docs
+---
+
+# Nomad
+
+Nomad is a simple, flexible scheduler and workload orchestrator.
+[Nomad Enterprise](https://www.hashicorp.com/products/nomad/) uses Sentinel
+to augment the built-in ACL system to provide advanced policy enforcement.
+Sentinel policies can currently execute on job submission (creation, update).
+
+Sentinel policies have full access to the job structure. This allows the
+Sentinel policy to control behavior based on any attribute within a job,
+such as the driver, resource requests, network configuration, volume
+configuration, and more. The information that Sentinel policies have access
+to will expand over time.
+
+Nomad fully supports all [enforcement levels](/sentinel/concepts/enforcement-levels).
+For soft mandatory policies, the `sentinel-override` capability must be
+available on the user's ACL policy to allow override. Overrides are always
+logged.
+
+The Nomad integration with Sentinel is documented in depth in the
+[Nomad Enterprise documentation](https://www.nomadproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Only allow Docker-based jobs.**
+
+```sentinel
+# Test policy only allows Docker based tasks
+main = rule { all_drivers_docker }
+
+# all_drivers_docker checks that all the drivers in use are Docker
+
+all_drivers_docker = rule {
+ all job.task_groups as tg {
+ all tg.tasks as task {
+ task.driver is "docker"
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/terraform.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/terraform.mdx
new file mode 100644
index 0000000000..9e4c430ddb
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/terraform.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Terraform and Sentinel
+sidebar_current: docs-app-terraform
+description: >-
+ Use Sentinel with HCP Terraform and Terraform Enterprise to enforce policy
+ on Terraform configurations, states, and plans.
+layout: docs
+---
+
+# Terraform
+
+[HCP Terraform and Terraform Enterprise](https://www.hashicorp.com/products/terraform/) use Sentinel to enforce
+policy on [Terraform](https://www.terraform.io) configurations, states, and plans.
+
+The Sentinel integration with Terraform runs within
+HCP Terraform and Terraform Enterprise
+after a `terraform plan` and before a `terraform apply`. The policies
+have access to the created plan, the state at the time of the plan,
+and the configuration at the time of the plan.
+
+The Terraform integration with Sentinel is documented in depth in the [HCP Terraform and Terraform Enterprise documentation](/terraform/cloud-docs/policy-enforcement/sentinel).
+Please read that page for full documentation. This page will only show basic examples.
+
+### Examples
+
+**Example: All AWS instances must have a tag**
+
+```sentinel
+import "tfplan"
+
+main = rule {
+ all tfplan.resources.aws*instance as *, instances {
+ all instances as \_, r {
+ (length(r.applied.tags) else 0) > 0
+ }
+ }
+}
+```
+
+**Example: Only allow GCP instance sizes smaller than `n1-standard-16`**
+
+```sentinel
+import "tfplan"
+
+allowed_machine_types = [
+ "n1-standard-1",
+ "n1-standard-2",
+ "n1-standard-4",
+ "n1-standard-8",
+]
+
+main = rule {
+ all tfplan.resources.google_compute_instance as _, instances {
+ all instances as _, r {
+ r.applied.machine_type in allowed_machine_types
+ }
+ }
+}
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/vault.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/vault.mdx
new file mode 100644
index 0000000000..6db2f8af22
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/vault.mdx
@@ -0,0 +1,64 @@
+---
+page_title: Vault and Sentinel
+sidebar_current: docs-app-vault
+description: >-
+ Vault Enterprise uses Sentinel to augment the built-in policy system to
+ provide Role Governing Policies (RGPs) and Endpoint Governing Policies (EGPs)
+ to enable complex, flexible policies across identities and endpoints.
+layout: docs
+---
+
+# Vault
+
+[Vault Enterprise](https://www.hashicorp.com/products/vault/) uses Sentinel
+to augment the built-in policy system to provide Role Governing Policies (RGPs)
+and Endpoint Governing Policies (EGPs) to enable complex, flexible policies
+across identities and endpoints.
+
+**Role Governing Policies (RGPs)** are Sentinel policies that are tied to
+particular tokens, Identity entities, or Identity groups. They have access to
+a rich set of controls across various aspects of Vault. These are evaluated
+whenever a token they're attached to is used.
+
+**Endpoint Governing Policies (EGPs)** are Sentinel policies that are tied to
+particular paths instead of tokens. They have access to as much request
+information as possible, but they can take effect even on unauthenticated
+paths, such as login paths.
+
+The Vault integration with Sentinel is documented in depth in the
+[Vault Enterprise documentation](https://www.vaultproject.io/docs/enterprise/sentinel/index.html).
+Please read that page for full documentation. This page will only show
+basic examples.
+
+### Examples
+
+**Example: Endpoint policy that requires MFA authentication from a corporate network.**
+
+```sentinel
+import "sockaddr"
+
+# We expect logins to come only from our private IP range
+cidrcheck = rule {
+ sockaddr.is_contained(request.connection.remote_addr, "10.20.0.0/16")
+}
+
+# Require Ping MFA validation to succeed
+ping_valid = rule {
+ mfa.methods.ping.valid
+}
+
+main = rule when request.path is "auth/ldap/login" {
+ ping_valid and cidrcheck
+}
+```
+
+**Example: Endpoint policy that disallows tokens created before a certain time.**
+
+```sentinel
+import "time"
+import "strings"
+
+main = rule when not strings.has_prefix(request.path, "auth/ldap/login") {
+ time.load(token.creation_time).unix > time.load("2017-09-17T13:25:29Z").unix
+}
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/why.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/why.mdx
new file mode 100644
index 0000000000..7e0d42e1ee
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/why.mdx
@@ -0,0 +1,47 @@
+---
+page_title: Why Sentinel?
+sidebar_current: docs-why
+description: >-
+ Welcome to the intro guide to Sentinel! This guide is the best place to start
+ with Sentinel.
+layout: docs
+---
+
+# Why Sentinel?
+
+The growth of infrastructure and applications has been enabled in part
+by an increasing trend towards automation everywhere.
+Configuration as code (such as Chef or Puppet with [Packer](https://www.packer.io))
+has enabled automated machine configuration. Infrastructure
+as code (such as [Terraform](https://www.terraform.io)) has enabled
+automatic infrastructure creation. And schedulers (such as
+[Nomad](https://www.nomadproject.io) and Kubernetes) have enabled
+automatic application deployment.
+Sentinel enables guardrails to be put in place
+on automation while allowing the codification and automatic enforcement
+of business requirements in critical areas of your infrastructure.
+
+Meanwhile, businesses have business requirements and sometimes legal
+requirements which must be expressed in policies. Traditionally, these
+policies are enforced by humans. But in a highly automated world, the
+automation is only as fast as its slowest component. In many cases, this
+is the human verification step.
+
+As an example: before infrastructure as code and autoscaling, if an order
+came through for 5,000 new machines, a human would likely respond to the
+ticket verifying that the user really intended to order 5,000 new machines.
+Today, automation can almost always freely order 5,000 new compute instances
+without any hesitation, which can result in unintended expense or system
+instability.
+
+Sentinel introduces [policy as code](/sentinel/concepts/policy-as-code)
+and a powerful framework built-in to HashiCorp tooling to allow automation
+guardrails, business requirements, legal compliance, and more to be
+actively enforced by the running systems in realtime.
+
+With Sentinel, you can require an override to create certain numbers of
+infrastructure resources. You can disallow unsafe deployment configurations
+with Nomad. You can enforce certain key/value formats in Consul. You
+can restrict secret access by time in Vault. And more.
+
+Sentinel is available today in HashiCorp enterprise products.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/writing/basic.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/writing/basic.mdx
new file mode 100644
index 0000000000..ebcebf2788
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/writing/basic.mdx
@@ -0,0 +1,111 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-basic
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Policy Basics
+
+Sentinel policies are easy to write while still supporting advanced constructs
+for creating complex policies. This page will explain the basics of writing
+Sentinel policies to get started. You don't need any prior Sentinel knowledge,
+but we recommend reading the [language guide](/sentinel/language) after this.
+
+Sentinel policies are text files written using the [Sentinel language](/sentinel/language).
+The policies are evaluated top-to-bottom. The value of `main` after execution
+determines whether a policy passes or fails.
+
+## The Simplest Policy
+
+Sentinel only requires that a policy have a `main` variable that evaluates to
+a boolean value.
+
+A valid example is shown below:
+
+```sentinel playground
+main = 10 > 5
+```
+
+This type of minimal policy is not purely academic. In practice, simple
+policies can often be reduced to a single line logical statement resulting
+in `true` or `false`. However, the expression is usually wrapped in a
+[rule](/sentinel/language/rules) for testing reasons.
+
+You can verify Sentinel will execute this minimal policy using the CLI:
+
+```
+$ sentinel apply minimal.sentinel
+Pass
+```
+
+## Logical Expressions
+
+Policy is at its core a set of logic: you can or can not perform some action
+under a certain set of circumstances. Those circumstances are logical
+expressions. Therefore, Sentinel policies primarily translate into logical
+expressions.
+
+Detailed documentation on boolean expressions is
+[available in the language guide](/sentinel/language/boolexpr).
+
+A simple numerical comparison was seen in the first example on this page.
+Sentinel also provides inclusion operators such as `contains`, `any`, `all`, and
+more. Sentinel allows some operators to have aliases to promote readability
+while remaining programmer-familiar, such as `==` which can equivalently be `is`.
+
+The example below verifies that all numbers in a list are even:
+
+```sentinel playground
+numbers = [6, 22, 8, 4, 12]
+main = all numbers as n { n % 2 is 0 }
+```
+
+## Variables
+
+A policy will very often use variables. Applications such as
+[Nomad](https://www.nomadproject.io) inject variables into the global
+scope of a policy for making policy decisions. For example, Nomad injects
+the job that is being run into the policy scope. Knowing how to use
+variables is critical to effectively using Sentinel.
+
+Detailed documentation on how to define and access variables is
+[available in the language guide](/sentinel/language/variables).
+
+Variables can be defined and used explicitly. For example:
+
+```sentinel playground
+value = 10
+main = value > 5
+```
+
+But they may also be introduced implictly by the host system. Nomad
+injects `job` into policies to describe the job that is being run. The
+policy below is a valid policy that requires a job have two task groups.
+Notice that `job` is not defined anywhere. It is implicitly inserted by
+the host application (in this case Nomad). Refer to the application you're
+writing policy for to determine if it implicitly inserts values.
+
+```json sentinel
+{
+ "policy": "main = length(job.task_groups) is 2",
+ "globals": {
+ "job": {
+ "task_groups": ["foo", "bar"]
+ }
+ }
+}
+```
+
+## And more!
+
+The Sentinel language supports many more features such as functions,
+loops, and more. You can learn about all of this in the
+[complete language guide](/sentinel/language).
+
+The other pages in the [writing policy](/sentinel/writing)
+will cover other information you need to know about writing Sentinel
+policies that isn't simply a language reference.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/writing/debugging.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/writing/debugging.mdx
new file mode 100644
index 0000000000..7dc284d222
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/writing/debugging.mdx
@@ -0,0 +1,59 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-debugging
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Debugging
+
+As policies become more complex, it becomes harder to understand exactly why
+a policy may be behaving differently than you expect.
+
+Prior to actively debugging, there are multiple best practices we recommend
+following. We recommend breaking your policy down into a set of
+[rules](/sentinel/writing/rules). This will make it easier to understand
+[traces](/sentinel/writing/tracing) and make it easier to
+[test](/sentinel/writing/testing) your policies.
+This should help to debug policies up to a significant complexity.
+
+## Print Logging
+
+The built-in [print function](/sentinel/language/logging-errors#print)
+can be used to log data. The print output is only shown during failures
+or when [tracing is explicitly enabled](/sentinel/writing/tracing).
+
+The `print` function outputs each argument, which can be of any type, converted
+to a human-friendly string format. Arguments are separated with a single space.
+
+Example:
+
+```sentinel playground
+value = 42
+print("the number is", value) // the number is 42
+
+object = { "foo": false }
+print(object) // { "foo": false }
+
+main = rule { true }
+```
+
+The `print` function returns the value `true` so that it can also be
+used within rule expressions. Note that the `print` function should be
+used at the beginning of statements otherwise they may never be
+reached. This is due to internal performance techniques within Sentinel
+like [short-circuiting](https://en.wikipedia.org/wiki/Short-circuit_evaluation).
+
+Example:
+
+```sentinel playground
+// rule_a and rule_b will evaluate to false
+rule_a = rule { print("rule_a is printed") and false } // "rule_a is printed"
+rule_b = rule { false and print("rule_b is printed") } // Nothing is printed
+
+// rule_c and rule_d will evaluate to true
+rule_c = rule { print("rule_c is printed") or true } // "rule_c is printed"
+rule_d = rule { true or print("rule_d is printed") } // Nothing is printed
+
+main = rule { rule_a or rule_b or rule_c and rule_d }
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/writing/imports.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/writing/imports.mdx
new file mode 100644
index 0000000000..74ac751ee4
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/writing/imports.mdx
@@ -0,0 +1,117 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-imports
+description: Imports enable policy decision based on arbitrary external information.
+layout: docs
+---
+
+# Imports
+
+Imports enable a Sentinel policy to access reusable libraries and external data
+and functions. Anyone can write their own [custom import](/sentinel/extending).
+Imports are what enable Sentinel policies to do more than look at only local
+context for making policy decisions.
+
+Sentinel also comes with a set of [standard imports](/sentinel/imports).
+Standard imports are available to every Sentinel policy to help policy writers
+with common tasks such as working with the time, network addresses, and more.
+
+-> **This page is about writing policies that _use_ imports.** If you're
+interested in _creating_ a new import, please see the section on [extending
+Sentinel](/sentinel/extending) for information on how to write modules and
+import plugins.
+
+## Using Imports
+
+To use an import, you use the `import` keyword at the top of your policy. This
+specifies the name of the import you want to use. The application you're writing
+the policy for must already be
+[configured](/sentinel/configuration#imports) to provide that
+import.
+
+Details on imports can be found in the
+[import section in the language reference](/sentinel/language/imports).
+
+In the example below, we use the [time](/sentinel/imports/time) import:
+
+```sentinel playground
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+There are two options to develop a policy locally when using imports:
+configure Sentinel to launch the import on `apply`, or mock the import
+using `mock`. The former requires access to the import while the
+latter is faster (doesn't have to launch a process for plugins) and
+doesn't require the import.
+
+## Mocking Imports
+
+The first option to developing policies locally is to mock the import values.
+When mocking an import, you don't need the import to be available. This can be
+useful since some imports may not be available as a plugin and may only be
+available to the application the policy runs in.
+
+Mocks are specified via the [configuration
+file](/sentinel/configuration). Mocks can also be used for
+[testing](/sentinel/writing/testing).
+
+You can supply mock configuration one of two ways, depending on your use case:
+
+- **[Using static data](/sentinel/configuration#mocking-static-data):**
+ Use this method when you can accurately represent your mock data in JSON and
+ do not need to mock complex Sentinel features such as functions.
+- **[Using Sentinel code](/sentinel/configuration#mocking-with-sentinel-code):**
+ Use this method when using a static JSON object is insufficient, such as when
+ you need to mock functions or other complex Sentinel features.
+
+Our example does not require complex data to be mocked, so a static object
+is sufficient:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ hour = 12
+ weekday_name = "Tuesday"
+ }
+ }
+}
+```
+
+This can be used via the CLI:
+
+```
+$ sentinel apply -config=config.hcl policy.sentinel
+Pass
+```
+
+## Launching Import Plugins
+
+If you have access to the plugin binary, you can launch the import. The
+benefit of this is that it is really using the import to test your
+policy. If the import changes, your policies may start failing. If you
+only use mock data and the import changes, your policies will still
+appear to work.
+
+Imports are configured in the configuration file:
+
+```hcl
+import "plugin" "custom_time" {
+ source = "/path/to/sentinel-time-import"
+}
+```
+
+This would require the `sentinel-time-import` binary. For this example
+this doesn't currently exist. We plan on writing one to provide for this
+section of the documentation.
+
+## Custom Imports
+
+You can also [create your own imports](/sentinel/extending).
+
+If your policy decisions could benefit from accessing external information,
+then you can use custom imports as a way to do this.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/writing/index.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/writing/index.mdx
new file mode 100644
index 0000000000..eec0b61fb5
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/writing/index.mdx
@@ -0,0 +1,34 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing
+description: >-
+ Sentinel provides a language and workflow for building policy across any
+ system that embeds Sentinel. By learning Sentinel once, you are able to
+ effectively control access to many systems using Sentinel's powerful features.
+ Basic concepts that are important to understand for Vault usage.
+layout: docs
+---
+
+# Writing Sentinel Policy
+
+This section covers how to write Sentinel policies.
+
+We recommend you complete the Get Started tutorials prior to
+reading this section of the documentation. The Get Started tutorials will
+explain all the basics of writing and testing policies. This section of the
+documentation will serve as more of a reference guide to all available
+features of Sentinel.
+
+Sentinel provides a language and workflow for building policy across any system
+that embeds Sentinel. By learning Sentinel once, you are able to effectively
+control access to many systems using Sentinel's powerful features.
+
+Sentinel also provides a [local CLI](/sentinel/commands) for developing and testing
+Sentinel policies. This CLI can be integrated into a daily developer's workflow
+along with CI to treat [policy as code](/sentinel/concepts/policy-as-code).
+
+Sentinel uses its own [language](/sentinel/language) for writing
+policies. You can view a [language reference](/sentinel/language)
+as well as the [specification](/sentinel/language/spec) for details. You
+don't have to read those documents immediately, since the language should
+be easy enough to pick up throughout this section.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/writing/rules.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/writing/rules.mdx
new file mode 100644
index 0000000000..ab4fc69d08
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/writing/rules.mdx
@@ -0,0 +1,65 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-rules
+description: >-
+ Sentinel policies are easy to write while still supporting advanced constructs
+ for creating complex policies. This page will explain the basics of writing
+ Sentinel policies to get started.
+layout: docs
+---
+
+# Rules
+
+Rules form the basis of a policy by representing behavior that is either passing
+or failing. Rules are a first class language construct in Sentinel. A policy can
+and should be broken down into rules to aid with readability, testability, and
+performance.
+
+Rules provide:
+
+- **Readability:** A policy that is broken down into rules can be read
+ more easily. The logic becomes clearer to see for policy writers when
+ they come back to it.
+
+- **Reportability:** When [tracing](/sentinel/writing/tracing) is enabled,
+ rules form the foundation of the data returned. All evaluated rules are added
+ to the trace with the data the rule evaluated to, and their position within
+ policy or module source files.
+
+- **Testability:** [Policy testing](/sentinel/writing/testing) is
+ built on asserting the values of rules. By breaking logic down into
+ rules, you're able to more effectively test your policies.
+
+- **Performance:** Rules are only evaluated on demand, and are also only
+ evaluated once. This means that a rule referenced multiple times only has a
+ one-time performance cost. For complex logic, this could result in improved
+ performance.
+
+An example usage of rules is shown below:
+
+```json sentinel
+{
+ "policy": "is_sunny = rule { weather is \"sunny\" }\nis_wednesday = rule { day is \"wednesday\" }\nmain = rule { is_sunny and is_wednesday }",
+ "globals": {
+ "weather": "sunny",
+ "day": "wednesday"
+ }
+}
+```
+
+Rules can also contain non-boolean data. This can be useful for passing more
+detail to the caller than an opaque boolean value would be able to communicate.
+
+```sentinel playground
+days = [{"weather": "sunny"}]
+main = rule {
+ filter days as d {
+ d.weather is not "sunny"
+ }
+}
+```
+
+Details about rules and their behavior can be found in
+[the language reference](/sentinel/language/rules). Rules have important
+behavior so we recommend reading the language reference on rules.
+Rules will be used throughout the remainder of the documentation.
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/writing/testing.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/writing/testing.mdx
new file mode 100644
index 0000000000..5d8a522c28
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/writing/testing.mdx
@@ -0,0 +1,495 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-testing
+description: >-
+ Sentinel has a built-in test framework to validate a policy behaves as
+ expected.
+layout: docs
+---
+
+# Testing
+
+Sentinel has a built-in test framework to validate a policy behaves as expected.
+
+With an ever-increasing amount of automation surrounding technology,
+the guardrails provided by policies are a critical piece towards ensuring
+expected behavior. As a reliance on correct policy increases, it is important
+to test and verify policies.
+
+Testing is a necessary step to fully realize
+[policy as code](/sentinel/concepts/policy-as-code). Just as good software
+is well tested, a good set of policies should be equally well tested.
+
+## Test Folder Structure
+
+Policies are tested by asserting that [rules](/sentinel/writing/rules)
+are the expected values given a pre-configured input. Tests are run
+by executing the [test command](/sentinel/commands/test).
+
+Sentinel is opinionated about the folder structure required for tests.
+This opinionated structure allows testing to be as simple as running
+`sentinel test` with no arguments. Additionally, it becomes simple to test
+in a CI or add new policies.
+
+The structure Sentinel expects is `test//*.[hcl|json]` where ``
+is the name of your policy file without the file extension. Within that folder
+is a list of HCL or JSON files. Each file represents a single test case.
+Therefore, each policy can have multiple tests associated with it.
+
+-> Note that the primary configuration file format for Sentinel is HCL. While
+you can write configuration files in a HCL-equivalent JSON, we only discuss the
+use of HCL on this page.
+
+## Test Case Format
+
+Each HCL file within the test folder for a policy is a single test case.
+
+The file is the same configuration format as the [CLI configuration
+file](/sentinel/configuration). The format lets you define mock data, imports to
+use, and more. This mock data is the key piece in being able to test policies:
+you craft a specific scenario and assert your policy behaves as you expect.
+
+Test cases also use the `test` block within the configuration file to assert the
+value of [rules](/sentinel/writing/rules). If the `test` key is omitted, the
+policy is expected to pass. If the test key is specified, only the rules
+specified in the map will be asserted. This means if you omit `main`, then the
+final policy result is not asserted.
+
+Example with assertions:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+
+test {
+ rules = {
+ main = false
+ is_open_hours = false
+ is_weekday = true
+ }
+}
+```
+
+The configuration above specifies some parameter data, and asserts the result of
+some rules. This is the same configuration used in the example section below.
+
+## Example
+
+Lets use the following file as an example. Save this file to a directory
+and name it `policy.sentinel`. It can be named anything with the `sentinel`
+extension, but by naming it `policy.sentinel` your output should match
+the example output on this page.
+
+```sentinel
+// The day of the week.
+param day
+
+// The hour of the day.
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+### A Passing Test
+
+Next, let's define a single test case. Relative to where you saved
+the policy, create a file at the path `test/policy/good.hcl`.
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 14
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/good.hcl
+```
+
+This passed because the policy passed. We didn't assert any specific
+rules. By not specifying any assertions, `test` expects the policy itself
+to fully pass.
+
+### A Failing Test
+
+Define another test case to fail. We want to verify our policy fails when expected, too.
+
+Save the following as `test/policy/7-am.hcl`:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+```
+
+Now run `sentinel test`:
+
+```
+$ sentinel test
+FAIL - policy.sentinel
+ FAIL - test/policy/7-am.hcl
+ expected "main" to be true, got: false
+
+ trace:
+ policy.sentinel:9:1 - Rule "main"
+ bool: false
+
+ policy.sentinel:8:1 - Rule "is_open_hours"
+ bool: false
+
+ PASS - test/policy/good.hcl
+```
+
+As you can see, the test fails because "main" is false. This is good
+because the policy _should_ have failed since we specified an invalid
+hour. But, we expect main to be false and don't want our test to fail!
+Update `7-am.hcl` to add test assertions:
+
+```hcl
+param "day" {
+ value = "monday"
+}
+
+param "hour" {
+ value = 7
+}
+
+test {
+ rules = {
+ main = false
+ is_open_hours = false
+ }
+}
+```
+
+And when we run the tests:
+
+```
+$ sentinel test
+PASS - policy.sentinel
+ PASS - test/policy/7-am.hcl
+ PASS - test/policy/good.hcl
+```
+
+The test passes. We asserted that we expect the `main` rule to be false,
+the `is_open_hours` rule to be false, and the `is_weekday` rule to be
+true. By asserting some rules are true, we can verify that our policy
+is failing for reasons we expect.
+
+## Mocking
+
+The above example demonstrates how to test by supplying different
+[parameters](/sentinel/language/parameters). Parameters in a policy can be
+specifically useful when you want to control user-defined input values to a
+policy.
+
+However, generally, when testing, you will need mimic the conditions you will
+see in production. Production implementations of Sentinel will supply data using
+one of two methods:
+
+- **Global data:** Data is injected directly into the policy's scope and is
+ accessible using normal identifiers, similar to variables.
+- **Imports:** Data is stored behind an [import](/sentinel/language/imports)
+ and loaded on demand as needed by the policy author.
+
+Proper testing of a policy requires that these values be able to be _mocked_ -
+or, in other words, simulated in a way that allows the accurate testing of the
+scenarios that a policy could reasonably pass or fail under.
+
+Mocking both globals and imports can be done by setting various parts of the
+configuration file.
+
+### Mocking Globals
+
+Demonstrating the mocking of globals can be seen by making a few modifications to
+our example policy, removing the `param` declarations:
+
+```sentinel
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+Then, change the `param` section in the configuration file to
+[`global`](/sentinel/configuration#global).
+
+```json
+global "day" {
+ value = "monday"
+}
+
+global "hour" {
+ value = 14
+}
+```
+
+This test should still pass, as if nothing had happened, although what we've
+done is shifted our parameters to globals, simulating an environment where `day`
+and `hour` are already defined for us.
+
+### Mocking Imports
+
+To mock imports, we need to use the
+[`mock`](/sentinel/configuration#mock-imports) section of the
+configuration file.
+
+Let's say the above example is behind an import named `time`.
+
+-> **NOTE:** [`time`](/sentinel/imports/time) is a valid standard import.
+This example may not be accurate to the
+import's syntax.
+
+The code now looks like this:
+
+```sentinel
+import "time"
+
+is_weekday = rule { time.now.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { time.now.hour > 8 and time.now.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To mock this import, we can [mock it as static
+data](/sentinel/configuration#mocking-static-data). The configuration
+file now looks like, without assertions:
+
+```hcl
+mock "time" {
+ data = {
+ now = {
+ weekday_name = "Monday"
+ hour = 14
+ }
+ }
+}
+```
+
+The policy will now pass, with the `time` import mocked.
+
+Data can also be [mocked as Sentinel
+code](/sentinel/configuration#mocking-with-sentinel-code). In this case,
+the above configuration file would look like:
+
+```hcl
+mock "time" {
+ module {
+ source = "mock-time.sentinel"
+ }
+}
+```
+
+And a file named `mock-time.sentinel` would now hold your mock values:
+
+```sentinel
+day = "monday"
+hour = 14
+```
+
+Mocking as Sentinel code allows more complex details to be mocked as well, such
+as functions. Say we wanted to mock the `time.load()` function. To mock this,
+just add it to the `mock-time.sentinel` file:
+
+```sentinel
+load = func(_) {
+ return {
+ "weekday_name": "Monday",
+ "hour": 14,
+ }
+}
+```
+
+Your code can now be written as:
+
+```sentinel
+import "time"
+
+t = time.load("a_mock_timestamp")
+
+is_weekday = rule { t.weekday_name not in ["Saturday", "Sunday"] }
+is_open_hours = rule { t.hour > 8 and t.hour < 17 }
+main = rule { is_open_hours and is_weekday }
+```
+
+To see more details, see the [Mock
+Imports](/sentinel/configuration#mock-imports) section in the
+configuration file.
+
+## Asserting Non-Boolean Rules
+
+Non-boolean rules can also be asserted by `sentinel test`.
+
+To assert non-boolean values, simply enter the expected value into the rule
+contents:
+
+```hcl
+param "maintenance_days" {
+ value = [
+ {
+ day = "wednesday"
+ hour = 9
+ },
+ {
+ day = "friday"
+ hour = 1
+ },
+ {
+ day = "sunday"
+ hour = 1
+ },
+ ]
+}
+
+test {
+ rules = {
+ main = [
+ {
+ day = "wednesday"
+ hour = 9
+ },
+ ]
+ }
+}
+```
+
+This would assert a policy checking for violations of a maintenance policy,
+saying that maintenance hours should happen before 6AM on any given day:
+
+```sentinel
+param maintenance_days
+
+main = rule {
+ filter maintenance_days as d {
+ d.hour >= 6
+ }
+}
+```
+
+## Test JSON Output
+
+Running `sentinel test` with the `-json` flag will give you the test results in
+a JSON output format, suitable for parsing by reporting software.
+
+-> **Note:** The JSON output format is currently under development and the
+format may change at a later time. Additionally, support for other well-known
+formats, such as JUnit, may become available in the future.
+
+The current format is an object with the top-level keys being `policies` and
+`duration`, with each test grouped up by policy being run. `duration` represents
+time taken in milliseconds for all policies to run.
+
+The policy result fields are:
+
+- `path`: The path of the policy and the index of the test result in the
+ `policies` field in the root object.
+- `status`: A string representation of the policy's test status as a whole. Can
+ be one of `PASS`, `FAIL`, `ERROR`, or `?`. The final status, `?`, represents a
+ policy that has no tests to process, and acts like a passing test.
+- `errors`: An array of any error messages encountered during processing the
+ policy for testing. Usually reserved for policy file or parser-related errors.
+ For case-specific errors, see the error field for the particular case.
+- `cases`: A map of case results, indexed by case path.
+- `duration`: Time taken in milliseconds for the policy to run.
+
+The case result fields are:
+
+- `path`: The path of the test case and the result's index in the `cases` field
+ in the policy object.
+- `status`: A string representation of the case's test status. Can be one of
+ `PASS`, `FAIL` or `ERROR`.
+- `errors`: An array of any error messages encountered during running this test
+ case.
+- `trace`: The trace for this policy in JSON format. See the
+ [tracing](/sentinel/writing/tracing) page for more details.
+- `rule_detail`: When the `status` of this test case is `FAIL`, contains an
+ object of assertion failure detail, indexed by rule. Only failures are counted
+ here; any rules not found here can be assumed to have passed or not asserted.
+- `config_warnings`: An array of strings denoting any configuration warnings
+ found while processing the configuration for this test case.
+- `config_legacy`: Denotes whether or not the configuration is a legacy JSON
+ configuration and needs to be modernized. This field may be removed in future
+ releases.
+
+A passing example is shown below:
+
+```json
+{
+ "policies": {
+ "policy.sentinel": {
+ "path": "policy.sentinel",
+ "status": "PASS",
+ "errors": null,
+ "duration": 5,
+ "cases": {
+ "test/policy/pass.hcl": {
+ "path": "test/policy/pass.hcl",
+ "status": "PASS",
+ "errors": null,
+ "trace": {
+ "description": "A very basic policy to determine if a run is within working hours.",
+ "error": null,
+ "print": "",
+ "result": true,
+ "rules": {
+ "is_open_hours": {
+ "desc": "Passes if run during business hours.",
+ "ident": "is_open_hours",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 290,
+ "line": 13,
+ "column": 1
+ },
+ "value": true
+ },
+ "is_weekday": {
+ "desc": "Passes if the day does not fall on the weekend.",
+ "ident": "is_weekday",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 193,
+ "line": 10,
+ "column": 1
+ },
+ "value": true
+ },
+ "main": {
+ "desc": "",
+ "ident": "main",
+ "position": {
+ "filename": "policy.sentinel",
+ "offset": 338,
+ "line": 14,
+ "column": 1
+ },
+ "value": true
+ }
+ }
+ },
+ "rule_detail": {},
+ "config_warnings": null,
+ "config_legacy": false
+ }
+ }
+ }
+ },
+ "duration": 10
+}
+```
diff --git a/content/sentinel/v0.40.x/content/sentinel/docs/writing/tracing.mdx b/content/sentinel/v0.40.x/content/sentinel/docs/writing/tracing.mdx
new file mode 100644
index 0000000000..e98767da00
--- /dev/null
+++ b/content/sentinel/v0.40.x/content/sentinel/docs/writing/tracing.mdx
@@ -0,0 +1,403 @@
+---
+page_title: Writing Sentinel Policy
+sidebar_current: docs-writing-tracing
+description: >-
+ Sentinel provides execution traces to better understand the execution of a
+ policy.
+layout: docs
+---
+
+# Traces
+
+Sentinel provides execution traces to better understand the execution of a policy.
+
+When using the Sentinel [CLI](/sentinel/commands), traces are shown
+on policy failure during `apply` or `test`, or when the `-trace` flag is
+explicitly specified. For Sentinel-enabled applications, traces are optional
+and may require special configuration to enable.
+
+The availability of the trace may vary from application to application. While
+some applications may only log traces on failure, others, such as [Terraform
+Cloud](https://www.terraform.io/cloud), log the trace on every execution. For
+more details, consult your implementation's documentation.
+
+-> **Note:** We're actively improving Sentinel tracing in each release
+to provide better data and improve readability. The exact format of a
+trace may not match the Sentinel version embedded in the application you're
+using, but the data should be similar. The canonical format for a trace should
+get more stable as we approach a 1.0 release.
+
+## Analyzing a Trace
+
+To show traces, let's use the example policy below with the CLI:
+
+```sentinel
+param day
+param hour
+
+is_weekday = rule { day not in ["saturday", "sunday"] }
+is_open_hours = rule { hour > 8 and hour < 17 }
+
+main = rule { is_open_hours and is_weekday }
+```
+
+Let's cause the policy to fail so the trace is more interesting.
+If you're on a terminal that supports it, the trace output will be colored
+so you can more clearly see passes, failures, and rules.
+
+
+
+ $ sentinel apply main.sentinel{'\n'}
+
+ main.sentinel:1:7: requires value for parameter day
+
+ {'\n'}
+ {'\n'}
+ {'\n'}
+ {' '}Values can be strings, floats, or JSON array or object values. To force
+ {'\n'}
+ {' '}strings, use quotes.{'\n'}
+ {'\n'}
+ {' '}Enter a value: sunday{'\n'}
+ {'\n'}
+
+ main.sentinel:2:7: requires value for parameter hour
+
+ {'\n'}
+ {'\n'}
+ {'\n'}
+ {' '}Values can be strings, floats, or JSON array or object values. To force
+ {'\n'}
+ {' '}strings, use quotes.{'\n'}
+ {'\n'}
+ {' '}Enter a value: 14{'\n'}
+ {'\n'}
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+
+ Fail - main.sentinel
+
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:7:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}
+ false
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:5:1 - Rule "is_open_hours"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}true{'\n'}
+ {'\n'}
+
+ main.sentinel:4:1 - Rule "is_weekday"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}false{'\n'}
+
+
+
+We can see all of our rules neatly and clearly displayed along with the result
+of the policy. If you are getting the trace due to a failed policy and not
+because you explicitly used `-trace`, an informational message is displayed.
+
+As we can see from our basic example, the `main` rule has failed and its result
+is highlighted in red. All peripheral rules that were also evaluated as part of
+the policy are also displayed below the main rule. Source positions are also
+displayed so that you can find the references to the rules in your code.
+
+Via the trace, we can clearly see that while `is_open_hours` returned a true
+result, `is_weekday` did not, and per the logic in our code, the policy is
+rejected.
+
+## Non-Boolean Rules and the Trace
+
+The trace is particularly important when working with rules that return
+non-boolean data, such as rules where you want to see violations instead of a
+simple opaque boolean value. Let's take our example from the [rules](./rules)
+section, and write a small static policy for it:
+
+```sentinel playground
+days = [
+ {"weekday": "Sunday", "weather": "clouds"},
+ {"weekday": "Monday", "weather": "rain"},
+ {"weekday": "Tuesday", "weather": "sunny"},
+ {"weekday": "Wednesday", "weather": "sunny"},
+ {"weekday": "Thursday", "weather": "clouds"},
+ {"weekday": "Friday", "weather": "rain"},
+ {"weekday": "Saturday", "weather": "sunny"},
+]
+
+main = rule {
+ filter days as d {
+ d.weather is not "sunny"
+ }
+}
+```
+
+Here is the output of our policy:
+
+
+
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+ {'\n'}
+
+ Fail - weather.sentinel
+
+ {'\n'}
+ {'\n'}
+
+ weather.sentinel:11:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+
+ {' [\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Sunday"'}
+ {'\n'}
+ {' "weather": "clouds"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Monday"'}
+ {'\n'}
+ {' "weather": "rain"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Thursday"'}
+ {'\n'}
+ {' "weather": "clouds"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' {'}
+ {'\n'}
+ {' "weekday": "Friday"'}
+ {'\n'}
+ {' "weather": "rain"'}
+ {'\n'}
+ {' }'}
+ {'\n'}
+ {' ]'}
+
+
+
+
+We can now see all of the weekdays with non-sunny weather.
+
+Note that to prevent formatting issues and excessive output, the human-readable
+trace is limited in the volume of output it will display for collections.
+
+## JSON Output
+
+The trace can also be displayed in JSON by adding `-json` to `sentinel apply`
+and `sentinel test`, and will display the trace in its entirety, unlike the
+standard CLI output.
+
+Here is the result of running `sentinel apply -json` against our weather policy:
+
+```json
+{
+ "can_override": false,
+ "error": null,
+ "duration": 12,
+ "policies": [
+ {
+ "allowed_failure": false,
+ "error": null,
+ "duration": 12,
+ "policy": "weather.sentinel",
+ "result": false,
+ "trace": {
+ "description": "",
+ "error": null,
+ "print": "",
+ "result": false,
+ "rules": {
+ "main": {
+ "desc": "",
+ "ident": "main",
+ "position": {
+ "filename": "weather.sentinel",
+ "offset": 328,
+ "line": 11,
+ "column": 1
+ },
+ "value": [
+ {
+ "weather": "clouds",
+ "weekday": "Sunday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Monday"
+ },
+ {
+ "weather": "clouds",
+ "weekday": "Thursday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Friday"
+ }
+ ]
+ }
+ }
+ }
+ }
+ ],
+ "result": false
+}
+```
+
+Note that the trace is always included with this output, regardless of whether
+or not the policy passes or fails; using `-trace` is unnecessary.
+
+Additionally, with `sentinel apply`, you can display the value of a single rule
+in JSON, instead of the entire trace. This is done with the `-json-rule` flag.
+
+-> **Note:** `sentinel apply -json-rule` needs to be run either against a single
+on-disk policy, or against a single policy within a policy set. It is ignored
+in full policy set executions and functions exactly like `-json` in these
+scenarios.
+
+Here is the result of executing `sentinel apply -json-rule main weather.sentinel`:
+
+```json
+[
+ {
+ "weather": "clouds",
+ "weekday": "Sunday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Monday"
+ },
+ {
+ "weather": "clouds",
+ "weekday": "Thursday"
+ },
+ {
+ "weather": "rain",
+ "weekday": "Friday"
+ }
+]
+```
+
+## Other Trace Details
+
+There are some other details that are good to know when working with the trace:
+
+- Only rules that get executed are added to the trace. Considering our first
+ example where the `main` rule is the expression `is_open_hours and is_weekday`,
+ if our expression was using an `or` operator instead of `and`, in addition to
+ the policy passing, `is_open_hours` would be present in the trace, but
+ `is_weekday` would not be, as the policy would have never evaluated that rule.
+- Descriptions for both rules and policies will show up in the trace as well if
+ present. Here is the output to our first policy with the appropriate
+ annotations:
+
+
+
+ ...{'\n\n'}
+ Execution trace. The information
+ below will show the values of all{'\n'}
+ the rules evaluated. Note that some rules may be missing if{'\n'}
+ short-circuit logic was taken.{'\n'}
+ {'\n'}
+ Note that for collection types and long strings, output may be{'\n'}
+ truncated; re-run "sentinel apply" with the -json flag to see the{'\n'}
+ full contents of these values.{'\n'}
+ {'\n'}
+ The trace is displayed due to a failed policy.{'\n'}
+ {'\n'}
+
+ Fail - main.sentinel
+
+ {'\n'}
+ {'\n'}
+ Description:{'\n'}
+ {' A very basic policy to determine if a run is within working\n'}
+ {' hours.\n'}
+ {'\n'}
+
+ main.sentinel:7:1 - Rule "main"
+
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}
+ false
+ {'\n'}
+ {'\n'}
+
+ main.sentinel:5:1 - Rule "is_open_hours"
+
+ {'\n'}
+ {' '}
+ Description:
+ {'\n'}
+ {' Passes if run during business hours.\n'}
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}true{'\n'}
+ {'\n'}
+
+ main.sentinel:4:1 - Rule "is_weekday"
+
+ {'\n'}
+ {' '}
+ Description:
+ {'\n'}
+ {' Passes if the day does not fall on the weekend.\n'}
+ {'\n'}
+ {' '}
+ Value:
+ {'\n'}
+ {' '}false{'\n'}
+
+